import { db } from '~/drizzle/db' import { schedules, songReplayRequests, songs } from '~/drizzle/schema' import { and, eq, inArray } from 'drizzle-orm' import { createSongSelectedNotification } from '~~/server/services/notificationService' import { cacheService } from '~~/server/services/cacheService' import { getClientIP } from '~~/server/utils/ip-utils' /** * 一键发布「所有日期的草稿」。 * * 与单条 publish.post.ts 的区别: * · 一次事务把所有草稿翻成已发布(不删数据、不影响已发布内容) * · 只清一次缓存、批量发订阅消息 —— 单条 publish 每调一次都会重建整张排期缓存, * 草稿多时前端逐条调用会非常慢,所以这里给小程序一个批量入口。 * * 也刻意不用 bulk-publish:那是「按日期 ± 时段整桶先删后插」的破坏性操作, * 会丢掉 played 标记和原有排期 id,不适合「只是把草稿翻状态」。 */ export default defineEventHandler(async (event) => { const user = event.context.user if (!user) { throw createError({ statusCode: 401, message: '未授权访问' }) } if (!['SONG_ADMIN', 'ADMIN', 'SUPER_ADMIN'].includes(user.role)) { throw createError({ statusCode: 403, message: '只有歌曲管理员及以上权限才能发布排期' }) } const clientIP = getClientIP(event) try { // 取出所有草稿及其歌曲信息(订阅消息要用) const drafts = await db .select({ id: schedules.id, playDate: schedules.playDate, sequence: schedules.sequence, songId: songs.id, songTitle: songs.title, songArtist: songs.artist, songRequesterId: songs.requesterId }) .from(schedules) .innerJoin(songs, eq(schedules.songId, songs.id)) .where(eq(schedules.isDraft, true)) if (drafts.length === 0) { return { success: true, publishedCount: 0, notifiedCount: 0, message: '没有待发布的草稿' } } const publishedAt = new Date() const songIds = Array.from(new Set(drafts.map((d) => d.songId))) await db.transaction(async (tx) => { // 1. 全部草稿翻成已发布 await tx .update(schedules) .set({ isDraft: false, publishedAt, updatedAt: publishedAt }) .where(eq(schedules.isDraft, true)) // 2. 涉及的歌曲:待处理重播申请标记为已完成 await tx .update(songReplayRequests) .set({ status: 'FULFILLED', updatedAt: publishedAt }) .where( and( inArray(songReplayRequests.songId, songIds), eq(songReplayRequests.status, 'PENDING') ) ) }) // 清除缓存(读接口会自行重建) try { await cacheService.clearSchedulesCache() await cacheService.clearSongsCache() } catch (cacheError) { console.error('[Cache] 清除缓存失败(批量发布草稿):', cacheError) } // 每条草稿对应一条订阅消息,异步发送不阻塞响应 Promise.allSettled( drafts.map((d) => createSongSelectedNotification( d.songRequesterId, d.songId, { title: d.songTitle, artist: d.songArtist, playDate: d.playDate, sequence: d.sequence }, clientIP ) ) ) .then((results) => { const ok = results.filter((r) => r.status === 'fulfilled').length console.log(`[Notification] 批量发布全部草稿通知完成: ${ok}/${drafts.length} 成功`) }) .catch((err) => { console.error('[Notification] 批量发布全部草稿时发送通知出错:', err) }) return { success: true, publishedCount: drafts.length, notifiedCount: drafts.length, message: `已发布 ${drafts.length} 条草稿` } } catch (error: any) { console.error('批量发布全部草稿失败:', error) throw createError({ statusCode: 500, message: error.message || '批量发布失败' }) } })