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
+65 -155
View File
@@ -1,28 +1,8 @@
import bcrypt from 'bcryptjs'
import { db } from '~/drizzle/db'
import { users } from '~/drizzle/schema'
import { eq, inArray } from 'drizzle-orm'
import { getAdminPasswordViolation } from '~~/server/utils/admin-password-policy'
interface UserData {
name: string
username: string
password: string
role?: string
status?: 'active' | 'withdrawn' | 'graduate'
grade?: string | null
class?: string | null
}
interface ValidUserData extends UserData {
index: number
}
const normalizeRequiredText = (value: unknown) => String(value || '').trim()
const normalizeOptionalText = (value: unknown) => {
const normalized = String(value || '').trim()
return normalized || null
}
import { eq } from 'drizzle-orm'
import { cacheService } from '../../../services/cacheService'
export default defineEventHandler(async (event) => {
// 检查认证和权限
@@ -46,129 +26,56 @@ export default defineEventHandler(async (event) => {
const results = {
created: 0,
failed: 0,
total: body.users.length,
errors: [] as Array<{ index: number; username?: string; name?: string; reason: string }>
total: body.users.length
}
// 1. 预处理数据:过滤必填字段,提取要查询的用户名
const validUsers: ValidUserData[] = []
const usernamesToQuery: string[] = []
const usernamesInBatch = new Set<string>()
// 批量添加用户
for (let i = 0; i < body.users.length; i++) {
const userData = body.users[i]
const normalizedUserData = {
...userData,
name: normalizeRequiredText(userData.name),
username: normalizeRequiredText(userData.username),
grade: normalizeOptionalText(userData.grade),
class: normalizeOptionalText(userData.class)
}
if (
!normalizedUserData.name ||
!normalizedUserData.username ||
typeof userData.password !== 'string' ||
!userData.password
) {
results.failed++
results.errors.push({
index: i,
username: userData.username,
name: userData.name,
reason: '缺少必填字段(姓名、账号或密码)'
})
continue
}
try {
// 验证必填字段
if (!userData.name || !userData.username || !userData.password) {
results.failed++
continue
}
const passwordViolation = getAdminPasswordViolation(userData.password)
if (passwordViolation) {
results.failed++
results.errors.push({
index: i,
username: normalizedUserData.username,
name: normalizedUserData.name,
reason: passwordViolation.message
})
continue
}
const chineseRegex = /^[\u4e00-\u9fa5]+$/
if (!chineseRegex.test(userData.name) || !chineseRegex.test(userData.username)) {
results.failed++
continue
}
if (usernamesInBatch.has(normalizedUserData.username)) {
results.failed++
results.errors.push({
index: i,
username: normalizedUserData.username,
name: normalizedUserData.name,
reason: '批处理中存在重复的账号'
})
continue
}
// 检查用户名是否已存在
const existingUser = await db
.select()
.from(users)
.where(eq(users.username, userData.username))
.limit(1)
usernamesInBatch.add(normalizedUserData.username)
// 限制单次查询 inArray 的长度,如果需要的话。前端分批已经是 50,所以没问题
validUsers.push({ index: i, ...normalizedUserData })
usernamesToQuery.push(normalizedUserData.username)
}
if (existingUser.length > 0) {
results.failed++
continue
}
if (validUsers.length === 0) {
return results
}
// 加密密码
const hashedPassword = await bcrypt.hash(userData.password, 10)
// 2. 批量查询已存在的用户名,减少数据库交互
let existingUsernames = new Set<string>()
try {
const existingUsersList = await db
.select({ username: users.username })
.from(users)
.where(inArray(users.username, usernamesToQuery))
existingUsernames = new Set(existingUsersList.map((u) => u.username))
} catch (error: any) {
console.error('批量查询用户失败:', error)
throw createError({
statusCode: 500,
message: '数据库查询错误'
})
}
// 3. 过滤掉已存在的用户
const usersToInsertData: ValidUserData[] = []
for (const userData of validUsers) {
if (existingUsernames.has(userData.username)) {
results.failed++
results.errors.push({
index: userData.index,
username: userData.username,
name: userData.name,
reason: '账号已存在'
})
continue
}
usersToInsertData.push(userData)
}
if (usersToInsertData.length === 0) {
return results
}
try {
// 4. 并发加密密码 (由于使用 bcrypt.hash 异步方法,使用 Promise.all 并发处理可以显著提速)
const hashedPasswords = await Promise.all(
usersToInsertData.map((userData) => bcrypt.hash(userData.password, 10))
)
// 5. 准备批量插入的数据
const insertValues = usersToInsertData.map((userData, idx) => {
// 角色权限控制
let validRole = 'USER' // 默认角色
if (userData.role) {
const rolePermissions: Record<string, string[]> = {
SUPER_ADMIN: ['USER', 'ADMIN', 'SONG_ADMIN', 'SUPER_ADMIN'],
ADMIN: ['USER', 'SONG_ADMIN']
}
const allowedRoles = rolePermissions[user.role]
if (allowedRoles?.includes(userData.role)) {
validRole = userData.role
if (user.role === 'SUPER_ADMIN') {
// 超级管理员可以设置任何角色
const allowedRoles = ['USER', 'TEACHER', 'ADMIN', 'SONG_ADMIN', 'SUPER_ADMIN']
if (allowedRoles.includes(userData.role)) {
validRole = userData.role
}
} else if (user.role === 'ADMIN') {
// 普通管理员只能设置 USER 和 SONG_ADMIN 角色
const allowedRoles = ['USER', 'TEACHER', 'SONG_ADMIN']
if (allowedRoles.includes(userData.role)) {
validRole = userData.role
}
}
}
@@ -178,31 +85,34 @@ export default defineEventHandler(async (event) => {
validStatus = userData.status
}
return {
name: userData.name,
username: userData.username,
password: hashedPasswords[idx]!,
role: validRole,
status: validStatus as 'active' | 'withdrawn' | 'graduate',
grade: userData.grade,
class: userData.class
}
})
// 创建用户
const newUser = await db
.insert(users)
.values({
name: userData.name,
username: userData.username,
password: hashedPassword,
role: validRole,
status: validStatus as 'active' | 'withdrawn' | 'graduate',
grade: userData.grade,
class: userData.class
})
.returning()
// 6. 执行批量插入
await db.insert(users).values(insertValues)
results.created += insertValues.length
} catch (error: any) {
console.error(`批量插入用户失败:`, error)
// 如果批量插入整体失败,则全部标记为失败
for (const userData of usersToInsertData) {
results.created++
} catch (error) {
console.error(`用户创建失败:`, error)
results.failed++
results.errors.push({
index: userData.index,
username: userData.username,
name: userData.name,
reason: error.message || '数据库批量写入失败'
})
}
}
// 如果有用户创建成功,清除相关缓存
if (results.created > 0) {
try {
await cacheService.clearSongsCache()
console.log('批量用户创建后缓存清除成功')
} catch (cacheError) {
console.error('批量用户创建后缓存清除失败:', cacheError)
}
}