import { db } from '~/drizzle/db' import { schedules, songBlacklists, songs, votes, requestTimes } from '~/drizzle/schema' import { and, eq, inArray, sql } from 'drizzle-orm' import { cacheService } from '../../../services/cacheService' import { createSongRejectedNotification } from '../../../services/notificationService' import { requireSongAdmin } from '~~/server/utils/requireSongAdmin' import { createApiError } from '~~/server/utils/apiError' import { SERVER_ERROR_CODES } from '~~/server/config/constants' const MAX_BATCH_REJECT_COUNT = 1000 export default defineEventHandler(async (event) => { const user = requireSongAdmin(event) const body = await readBody(event) const songIds = Array.isArray(body?.songIds) ? [...new Set(body.songIds.filter((id: any) => Number.isInteger(id) && id > 0))] : [] const reason = typeof body?.reason === 'string' ? body.reason.trim() : '' const addToBlacklist = body?.addToBlacklist === true if (songIds.length === 0) { throw createApiError(400, SERVER_ERROR_CODES.SONG_BATCH_REJECT_IDS_REQUIRED, '歌曲ID列表不能为空') } if (songIds.length > MAX_BATCH_REJECT_COUNT) { throw createApiError( 400, SERVER_ERROR_CODES.SONG_BATCH_REJECT_LIMIT_EXCEEDED, `单次最多驳回 ${MAX_BATCH_REJECT_COUNT} 首歌曲`, { params: [MAX_BATCH_REJECT_COUNT] } ) } if (!reason) { throw createApiError(400, SERVER_ERROR_CODES.SONG_BATCH_REJECT_REASON_REQUIRED, '驳回原因不能为空') } try { let rejectedSongs: any[] = [] await db.transaction(async (tx) => { const existingSongs = await tx.select().from(songs).where(inArray(songs.id, songIds)) if (existingSongs.length === 0) { throw createApiError(404, SERVER_ERROR_CODES.SONG_NOT_FOUND, '歌曲不存在或已被删除') } const existingIds = existingSongs.map((song) => song.id) console.log(`开始批量驳回歌曲,共 ${existingIds.length} 首`) if (addToBlacklist) { const blacklistValues = [...new Set(existingSongs.map((song: any) => `${song.title} - ${song.artist}`))] const existingBlacklist = await tx .select({ value: songBlacklists.value }) .from(songBlacklists) .where(and(eq(songBlacklists.type, 'SONG'), inArray(songBlacklists.value, blacklistValues))) const existingSet = new Set(existingBlacklist.map((item) => item.value)) const valuesToInsert = blacklistValues.filter((value) => !existingSet.has(value)) for (const value of valuesToInsert) { try { await tx.insert(songBlacklists).values({ type: 'SONG', value, reason: `歌曲驳回: ${reason}`, createdBy: user.id }) } catch (error: any) { console.log(`黑名单添加失败,可能已存在: ${error.message}`) } } console.log(`黑名单添加处理完成`) } await tx.delete(votes).where(inArray(votes.songId, existingIds)) console.log(`删除了投票记录`) await tx.delete(schedules).where(inArray(schedules.songId, existingIds)) console.log(`删除了排期记录`) const hitRequestIds = [...new Set(existingSongs.map((song: any) => song.hitRequestId).filter(Boolean))] for (const hitRequestId of hitRequestIds) { const count = existingSongs.filter((song: any) => song.hitRequestId === hitRequestId).length try { await tx .update(requestTimes) .set({ accepted: sql`GREATEST(0, accepted - ${count})` }) .where(eq(requestTimes.id, hitRequestId as any)) } catch (error: any) { console.error(`减少投稿时段 ${hitRequestId} 接纳数量失败: ${error.message}`) } } rejectedSongs = await tx.delete(songs).where(inArray(songs.id, existingIds)).returning() console.log(`歌曲批量删除完成`) }) const songsToNotify = rejectedSongs.filter((song) => song.requesterId) for (const song of songsToNotify) { createSongRejectedNotification( song.requesterId, { title: song.title, artist: song.artist }, reason ).catch((error: any) => { console.error(`发送驳回通知失败: ${error.message}`) }) } await cacheService.clearSongsCache() await cacheService.clearSchedulesCache() console.log(`批量驳回操作完成,共 ${rejectedSongs.length} 首`) return { success: true, message: '歌曲批量驳回成功', data: { rejected: rejectedSongs.length, missing: songIds.length - rejectedSongs.length, notificationSent: songsToNotify.length } } } catch (error: any) { console.error('批量驳回歌曲失败:', error) if (error.statusCode) { throw error } throw createApiError(500, SERVER_ERROR_CODES.SONG_OPERATION_FAILED, '批量驳回失败: ' + error.message) } })