import { createError, defineEventHandler, getRouterParam, readBody } from 'h3' import { db } from '~/drizzle/db' import { users, userStatusLogs } from '~/drizzle/schema' import { eq } from 'drizzle-orm' import { updateUserPassword } from '~~/server/services/userService' import { createSystemNotification } from '~~/server/services/notificationService' import { PASSWORD_AUDIT_ACTIONS, getPasswordAuditContext } from '~~/server/services/passwordSecurityService' import { createApiError } from '~~/server/utils/apiError' import { SERVER_ERROR_CODES } from '~~/server/config/constants' import { getAdminPasswordViolation } from '~~/server/utils/admin-password-policy' const normalizeRequiredText = (value: unknown) => String(value || '').trim() const normalizeOptionalText = (value: unknown) => { const normalized = String(value || '').trim() return normalized || null } const roleNames: Record = { USER: '用户', SONG_ADMIN: '歌曲管理员', ADMIN: '管理员', SUPER_ADMIN: '超级管理员' } export default defineEventHandler(async (event) => { try { // 检查认证和权限 const user = event.context.user if (!user || !['ADMIN', 'SUPER_ADMIN'].includes(user.role)) { throw createError({ statusCode: 403, message: '没有权限访问' }) } const userId = getRouterParam(event, 'id') const userIdNum = Number.parseInt(String(userId), 10) if (!Number.isInteger(userIdNum) || userIdNum <= 0) { throw createError({ statusCode: 400, message: '无效的用户ID' }) } const body = await readBody(event) const { name, username, password, role, grade, class: userClass, status } = body || {} const normalizedName = normalizeRequiredText(name) const normalizedUsername = normalizeRequiredText(username) // 验证必填字段 if (!normalizedName || !normalizedUsername) { throw createError({ statusCode: 400, message: '姓名和用户名为必填项' }) } // 检查用户是否存在 const existingUser = await db.select().from(users).where(eq(users.id, userIdNum)).limit(1) if (existingUser.length === 0) { throw createError({ statusCode: 404, message: '用户不存在' }) } const targetUser = existingUser[0]! // 1. 禁止修改系统初始超级管理员 (ID: 1) if (targetUser.id === 1) { throw createError({ statusCode: 403, message: '无法修改系统初始超级管理员' }) } // 2. 禁止对自身进行任何用户管理操作(包括角色、资料、状态、密码等) // 使用 String 转换确保 ID 比较的准确性 if (String(userId) === String(user.id)) { throw createError({ statusCode: 400, message: '禁止在用户管理中修改自己的账户' }) } // 3. 越级修改保护 // 如果目标用户是 SUPER_ADMIN,操作者必须是 SUPER_ADMIN if (targetUser.role === 'SUPER_ADMIN' && user.role !== 'SUPER_ADMIN') { throw createError({ statusCode: 403, message: '权限不足:普通管理员无法修改超级管理员信息' }) } // 检查用户名是否被其他用户使用 if (normalizedUsername !== targetUser.username) { const duplicateUser = await db .select() .from(users) .where(eq(users.username, normalizedUsername)) .limit(1) if (duplicateUser.length > 0) { throw createError({ statusCode: 400, message: '用户名已被其他用户使用' }) } } // 角色权限控制 let validRole = targetUser.role if (role) { if (!['USER', 'ADMIN', 'SONG_ADMIN', 'SUPER_ADMIN'].includes(role)) { throw createError({ statusCode: 400, message: '无效的用户角色' }) } } if (role && role !== targetUser.role) { // 超级管理员可以设置任何角色 if (user.role === 'SUPER_ADMIN') { validRole = role } // 管理员只能设置管理员以下的角色(USER, SONG_ADMIN) else if (user.role === 'ADMIN') { if (['USER', 'SONG_ADMIN'].includes(role)) { validRole = role } else { throw createError({ statusCode: 403, message: '管理员只能设置用户和歌曲管理员角色' }) } } // 其他角色不能设置角色 else { throw createError({ statusCode: 403, message: '没有权限设置用户角色' }) } } // 验证status字段的有效性 if (status && !['active', 'withdrawn', 'graduate'].includes(status)) { throw createError({ statusCode: 400, message: '用户状态只能是 active, withdrawn 或 graduate' }) } // 待审核用户不得通过编辑接口直接置为 active(须走注册审核流程) if (targetUser.status === 'pending' && status === 'active') { throw createApiError(400, SERVER_ERROR_CODES.USER_NOT_PENDING, '待审核用户需通过注册审核流程处理') } // 准备更新数据 const updateData = { name: normalizedName, username: normalizedUsername, role: validRole, grade: normalizeOptionalText(grade), class: normalizeOptionalText(userClass), ...(status && status !== targetUser.status ? { status, statusChangedAt: new Date(), statusChangedBy: user.id } : {}) } // 如果提供了密码,则使用统一服务更新密码 if (password) { const trimmedPassword = String(password).trim() // 管理员重置为临时密码且 forceReset 强制用户登录后修改,仅做基础校验不要求完整复杂度 const violation = getAdminPasswordViolation(trimmedPassword) if (violation) { throw createApiError(400, violation.code, violation.message) } await updateUserPassword(targetUser.id, trimmedPassword, { forceReset: true, auditContext: { action: PASSWORD_AUDIT_ACTIONS.RESET_PASSWORD, actorId: user.id, ...getPasswordAuditContext(event) } }) } // 更新用户其他信息 const updatedUser = await db .update(users) .set(updateData) .where(eq(users.id, userIdNum)) .returning({ id: users.id, name: users.name, username: users.username, role: users.role, grade: users.grade, class: users.class, status: users.status, statusChangedAt: users.statusChangedAt, statusChangedBy: users.statusChangedBy, lastLogin: users.lastLogin, lastLoginIp: users.lastLoginIp, passwordChangedAt: users.passwordChangedAt, createdAt: users.createdAt, updatedAt: users.updatedAt }) // 如果状态发生变更,记录到状态变更日志 if (status && status !== targetUser.status) { await db.insert(userStatusLogs).values({ userId: targetUser.id, oldStatus: targetUser.status, newStatus: status, reason: `管理员${user.name || user.username}修改用户状态`, operatorId: user.id }) } if (validRole !== targetUser.role) { const oldRoleName = roleNames[targetUser.role] || targetUser.role const newRoleName = roleNames[validRole] || validRole const notification = await createSystemNotification( targetUser.id, '权限变更通知', `您的账户权限已由系统更新:${oldRoleName} → ${newRoleName}` ) if (!notification) { console.warn(`未向用户 ${targetUser.id} 发送权限变更通知`) } } return { success: true, user: updatedUser[0]!, message: '用户更新成功' } } catch (error: any) { console.error('更新用户失败:', error) if (error.statusCode) { throw error } throw createError({ statusCode: 500, message: '更新用户失败: ' + error.message }) } })