feat(排期): 新增批量发布全部草稿接口 publish-drafts
给小程序端「一键全部发布」用:一个事务把所有草稿翻成已发布并处理待处理重播申请,只清一次缓存、批量发订阅消息。避免前端逐条调用单条 publish 时每次都重建整张排期缓存导致的卡顿。
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
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 || '批量发布失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user