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
+57 -107
View File
@@ -1,10 +1,10 @@
import { db, songs, songReplayRequests, users } from '~/drizzle/db'
import { desc, eq, sql, and } from 'drizzle-orm'
import { db, songs, songReplayRequests, users, semesters } from '~/drizzle/db'
import { desc, eq, sql, and, or } from 'drizzle-orm'
export default defineEventHandler(async (event) => {
// 1. 检查权限
const user = event.context.user
if (!user || !['SONG_ADMIN', 'ADMIN', 'SUPER_ADMIN'].includes(user.role)) {
if (!user || !['ADMIN', 'SUPER_ADMIN'].includes(user.role)) {
throw createError({ statusCode: 403, message: '权限不足' })
}
@@ -20,49 +20,33 @@ export default defineEventHandler(async (event) => {
conditions.push(eq(songReplayRequests.status, status as any))
}
if (semesterName) {
if (semesterName && semesterName !== 'all') {
conditions.push(eq(songs.semester, semesterName))
}
// 2. 查询
// 获取歌曲信息及重播申请数量,同时关联最新申请人信息
// 获取歌曲信息及重播申请数量,同时关联用户信息
const requestsQuery = db
.select({
song: {
id: songs.id,
createdAt: songs.createdAt,
updatedAt: songs.updatedAt,
title: songs.title,
artist: songs.artist,
requesterId: songs.requesterId,
played: songs.played,
playedAt: songs.playedAt,
semester: songs.semester,
preferredPlayTimeId: songs.preferredPlayTimeId,
cover: songs.cover,
playUrl: songs.playUrl,
musicPlatform: songs.musicPlatform,
musicId: songs.musicId,
durationSeconds: songs.durationSeconds,
submissionNote: songs.submissionNote,
submissionNotePublic: songs.submissionNotePublic,
hitRequestId: songs.hitRequestId
song: songs,
requester: {
id: users.id,
name: users.name,
grade: users.grade,
class: users.class
},
// insert-only 模型下同一用户可能有多条申请,按申请人去重计数
requestCount: sql<number>`count(distinct ${songReplayRequests.userId})`
requestCount: sql<number>`count(${songReplayRequests.id})`
.mapWith(Number)
.as('request_count'),
lastRequestedAt: sql<string>`max(${songReplayRequests.createdAt})`.as('last_requested_at')
})
.from(songs)
.innerJoin(songReplayRequests, eq(songs.id, songReplayRequests.songId))
.groupBy(songs.id)
.leftJoin(users, eq(songs.requesterId, users.id))
.where(conditions.length > 0 ? and(...conditions) : undefined)
.groupBy(songs.id, users.id, users.name, users.grade, users.class)
.orderBy(desc(sql`request_count`))
if (conditions.length > 0) {
requestsQuery.where(and(...conditions))
}
const requests = await requestsQuery
// 获取所有用户姓名,用于处理同名情况
@@ -83,53 +67,47 @@ export default defineEventHandler(async (event) => {
}
})
const formatDisplayName = (userObj: any) => {
if (!userObj || !userObj.name) return '未知用户'
let displayName = userObj.name
const sameNameUsers = nameToUsers.get(displayName)
if (sameNameUsers && sameNameUsers.length > 1) {
if (userObj.grade) {
displayName = `${displayName}(${userObj.grade})`
}
}
return displayName
const formatClassInfo = (userObj: any) => {
if (!userObj) return ''
const parts: string[] = []
if (userObj.grade) parts.push(String(userObj.grade))
if (userObj.class) parts.push(String(userObj.class))
return parts.join(' ').trim()
}
// 获取详细的重播申请人信息
const requestDetailsQuery = db
.select({
id: songReplayRequests.id,
songId: songReplayRequests.songId,
user: {
id: users.id,
name: users.name,
grade: users.grade,
class: users.class
},
createdAt: songReplayRequests.createdAt,
status: songReplayRequests.status,
preferredPlayTimeId: songReplayRequests.preferredPlayTimeId,
submissionNote: songReplayRequests.submissionNote,
submissionNotePublic: songReplayRequests.submissionNotePublic,
submissionNotePublicStatus: songReplayRequests.submissionNotePublicStatus
})
.from(songReplayRequests)
.innerJoin(users, eq(songReplayRequests.userId, users.id))
.innerJoin(songs, eq(songReplayRequests.songId, songs.id))
.orderBy(desc(songReplayRequests.createdAt))
const formatDisplayName = (userObj: any) => {
if (!userObj) return '未知用户'
const name = userObj.name ? String(userObj.name).trim() : ''
const classInfo = formatClassInfo(userObj)
if (!name) return classInfo || '未知用户'
return classInfo ? `${classInfo} ${name}` : name
}
const subConditions = []
if (status !== 'ALL') {
subConditions.push(eq(songReplayRequests.status, status as any))
}
if (semesterName) {
if (semesterName && semesterName !== 'all') {
subConditions.push(eq(songs.semester, semesterName))
}
if (subConditions.length > 0) {
requestDetailsQuery.where(and(...subConditions))
}
// 获取详细的重播申请人信息
let requestDetailsQuery = db
.select({
songId: songReplayRequests.songId,
user: {
name: users.name,
grade: users.grade,
class: users.class
},
createdAt: songReplayRequests.createdAt,
status: songReplayRequests.status
})
.from(songReplayRequests)
.innerJoin(users, eq(songReplayRequests.userId, users.id))
.innerJoin(songs, eq(songReplayRequests.songId, songs.id))
.where(subConditions.length > 0 ? and(...subConditions) : undefined)
.orderBy(desc(songReplayRequests.createdAt))
const requestDetails = await requestDetailsQuery
@@ -137,50 +115,22 @@ export default defineEventHandler(async (event) => {
requestDetails.forEach((d) => {
if (!detailsMap.has(d.songId)) detailsMap.set(d.songId, [])
detailsMap.get(d.songId).push({
id: d.id,
userId: d.user.id,
name: formatDisplayName(d.user),
grade: d.user.grade,
class: d.user.class,
createdAt: d.createdAt,
status: d.status,
preferredPlayTimeId: d.preferredPlayTimeId,
submissionNote: d.submissionNote,
submissionNotePublic: d.submissionNotePublic
status: d.status
})
})
return requests.map((item) => {
const requestDetails = detailsMap.get(item.song.id) || []
const latestRequest = requestDetails[0]
return {
...item.song,
requesterId: latestRequest?.userId || null,
preferredPlayTimeId: latestRequest?.preferredPlayTimeId || null,
submissionNote: latestRequest?.submissionNote || null,
submissionNotePublic: latestRequest?.submissionNotePublic === true,
submissionNotePublicStatus: latestRequest?.submissionNotePublicStatus || null,
hasSubmissionNote: !!latestRequest?.submissionNote,
requester: latestRequest?.name || '未知用户',
requesterGrade: latestRequest?.grade || null,
requesterClass: latestRequest?.class || null,
replayRequestId: latestRequest?.id || null,
replayRequestCount: item.requestCount,
replayRequesters: requestDetails.map((request: any) => ({
id: request.userId,
name: request.name,
displayName: request.name,
grade: request.grade,
class: request.class,
status: request.status,
createdAt: request.createdAt
})),
isReplay: true,
voteCount: item.requestCount,
requestCount: item.requestCount,
lastRequestedAt: item.lastRequestedAt,
requestDetails
}
})
return requests.map((item) => ({
...item.song,
requester: formatDisplayName(item.requester),
requesterGrade: item.requester?.grade,
requesterClass: item.requester?.class,
voteCount: item.requestCount,
requestCount: item.requestCount,
lastRequestedAt: item.lastRequestedAt,
requestDetails: detailsMap.get(item.song.id) || []
}))
})