feat: 迁移自定义功能至 1.5.9.9 版本

包含音乐源限制、CI 防 OOM 优化、数据库连接池优化、管理端权限及 UI 细节定制等功能
This commit is contained in:
2026-09-12 19:26:42 +08:00
parent 34f0679117
commit 1106aeea6c
161 changed files with 18963 additions and 27900 deletions
+56 -66
View File
@@ -1,17 +1,10 @@
import { db } from '~/drizzle/db'
import { createSongPlayedNotification } from '~~/server/services/notificationService'
import { CacheService } from '~~/server/services/cacheService'
import { songs, songReplayRequests } from '~/drizzle/schema'
import { eq, and, inArray } from 'drizzle-orm'
import { getBeijingTime } from '~/utils/timeUtils'
import { getClientIP } from '~~/server/utils/ip-utils'
import { restoreReplayRequestsToPending } from '~~/server/utils/scheduleReplayBinding'
import { z } from 'zod'
const markPlayedSchema = z.object({
songId: z.number().optional(),
songIds: z.array(z.number()).max(100, '批量操作单次最多支持 100 首歌曲').optional(),
unmark: z.boolean().optional()
})
export default defineEventHandler(async (event) => {
// 检查用户认证
@@ -25,7 +18,7 @@ export default defineEventHandler(async (event) => {
}
// 检查是否是管理员
if (!['ADMIN', 'SUPER_ADMIN', 'SONG_ADMIN'].includes(user.role)) {
if (!['ADMIN', 'SUPER_ADMIN'].includes(user.role)) {
throw createError({
statusCode: 403,
message: '只有管理员可以标记歌曲为已播放'
@@ -33,24 +26,15 @@ export default defineEventHandler(async (event) => {
}
const body = await readBody(event)
const validatedDataResult = markPlayedSchema.safeParse(body)
if (!validatedDataResult.success) {
throw createError({
statusCode: 400,
message: validatedDataResult.error.issues[0]?.message || '请求参数无效'
})
let songIds: number[] = []
if (body.songId) {
songIds = [body.songId]
} else if (body.songIds && Array.isArray(body.songIds)) {
songIds = body.songIds
}
const validatedData = validatedDataResult.data
const songIds = Array.from(
new Set([
...(validatedData.songId !== undefined ? [validatedData.songId] : []),
...(validatedData.songIds || [])
])
)
if (songIds.length === 0) {
throw createError({
statusCode: 400,
@@ -58,15 +42,19 @@ export default defineEventHandler(async (event) => {
})
}
// 撤回已播放
const isUnmark = validatedData.unmark === true
// 检查是否是撤回操作
const isUnmark = body.unmark === true
// 过滤需要更新的歌曲
// 构建更新条件:只更新状态不符合预期的歌曲
// 如果是标记为已播放(!isUnmark),则只更新 played = false 的歌曲
// 如果是撤回(isUnmark),则只更新 played = true 的歌曲
const condition = !isUnmark
? and(inArray(songs.id, songIds), eq(songs.played, false))
: and(inArray(songs.id, songIds), eq(songs.played, true))
// 使用事务确保数据一致性
const { updatedSongsResult, updatedSongIds } = await db.transaction(async (tx) => {
// 更新歌曲状态
const updatedSongsResult = await tx
.update(songs)
.set({
@@ -74,58 +62,60 @@ export default defineEventHandler(async (event) => {
playedAt: isUnmark ? null : getBeijingTime()
})
.where(condition)
.returning({ id: songs.id })
.returning()
const updatedSongIds = updatedSongsResult.map((s) => s.id)
// 获取实际更新的歌曲ID列表
const updatedSongIds = updatedSongsResult.map(s => s.id)
if (updatedSongIds.length > 0) {
if (!isUnmark) {
// 标记已播放:履行全部待处理的重播申请
const updatedRequests = await tx
.update(songReplayRequests)
.set({
status: 'FULFILLED',
updatedAt: new Date()
})
.where(
and(
inArray(songReplayRequests.songId, updatedSongIds),
eq(songReplayRequests.status, 'PENDING')
)
)
.returning({ id: songReplayRequests.id })
// 根据操作类型更新重播申请状态
const targetStatus = !isUnmark ? 'PENDING' : 'FULFILLED'
const newStatus = !isUnmark ? 'FULFILLED' : 'PENDING'
if (updatedRequests.length > 0) {
console.log(`将 ${updatedRequests.length} 个重播申请状态更新为 FULFILLED(歌曲已播放)`)
}
} else {
// 撤回已播放:每人仅恢复最新一条,避免违反部分唯一索引
const restoredCount = await restoreReplayRequestsToPending({
tx,
songIds: updatedSongIds,
at: new Date()
const updatedRequests = await tx
.update(songReplayRequests)
.set({
status: newStatus,
updatedAt: new Date()
})
.where(
and(inArray(songReplayRequests.songId, updatedSongIds), eq(songReplayRequests.status, targetStatus))
)
.returning()
if (restoredCount > 0) {
console.log(`将 ${restoredCount} 个重播申请状态恢复为 PENDING(撤回已播放)`)
}
if (updatedRequests.length > 0) {
const logMessage = !isUnmark
? `将 ${updatedRequests.length} 个重播申请状态更新为 FULFILLED(歌曲已播放)`
: `将 ${updatedRequests.length} 个重播申请状态恢复为 PENDING(撤回已播放)`
console.log(logMessage)
}
}
return { updatedSongsResult, updatedSongIds }
})
// 异步发送通知
// 清除歌曲和排期相关缓存
try {
const cacheService = CacheService.getInstance()
await cacheService.clearSongsCache()
await cacheService.clearSchedulesCache()
} catch (error) {
console.error('清除缓存失败:', error)
}
// 如果是标记为已播放,则异步发送通知(不阻塞响应)
if (!isUnmark && updatedSongIds.length > 0) {
event.waitUntil(
Promise.allSettled(
updatedSongIds.map((songId) =>
createSongPlayedNotification(songId).catch((err) => {
console.error(`发送歌曲(${songId})已播放通知失败:`, err)
})
)
)
)
// 获取客户端IP地址
const clientIP = getClientIP(event)
// 异步发送通知,不等待完成
setImmediate(() => {
Promise.allSettled(updatedSongIds.map(songId =>
createSongPlayedNotification(songId, clientIP).catch((err) => {
console.error(`发送歌曲(${songId})已播放通知失败:`, err)
})
))
})
}
return {