feat(users): 初始管理员可改显示名(登录名锁死 admin)+ 新增「提升为超级管理员」

站长诉求:想自定义 admin 账号的资料,但**必须保留 admin/admin123 作为应急登录**;
同时想用自己绑了微信的中文账号做日常管理。

按"哪些能改、哪些必须锁死"拆开处理:

1) server/api/admin/users/[id].put.ts —— 原来对 ID=1 **一律 403**(连显示名都改不了),
   现在:
   · 允许改:姓名(显示名)、年级、班级、状态;
   · 锁死:登录名必须保持 admin(那就是 admin/admin123 的应急通道,改了就登不进去);
   · 锁死:角色不能动(防止把自己降权导致没人能管站点);
   · 锁死:密码不在这里改(避免被其它管理员顺手重置;要改用它登录后去「设置」里改)。

2) 新增 POST /api/admin/users/{id}/promote-super —— 把某个账号提升为超级管理员。
   之所以单开接口而不是放开通用编辑接口的 role:原 `canChangeRoleOf()` 明确禁止提升为
   SUPER_ADMIN,两个前端的角色下拉也故意不给这一项。这里是**显式、可审计**的通道:
   仅 SUPER_ADMIN 可调、不能提升自己、已经是超管的会提示、成功后清该用户认证缓存。

前端(小程序)配套:用户管理操作菜单改成 {label, run} 数组(有权限时才出现"提升为超级管理员",
不再靠写死 tapIndex),点击有二次确认并列出会拿到的权限;编辑初始管理员时登录名输入框 disabled
并显示原因,姓名照常可改。
This commit is contained in:
2026-09-30 21:42:59 +08:00
parent 5039ab3f79
commit 792b3d4ef6
2 changed files with 96 additions and 5 deletions
+27 -2
View File
@@ -55,13 +55,38 @@ export default defineEventHandler(async (event) => {
const currentLevel = getRoleLevel(user.role)
const targetLevel = getRoleLevel(targetUser.role)
// 1. 禁止修改系统初始超级管理员 (ID: 1)
/*
* 系统初始超级管理员(ID=1,登录名 admin)的处理 —— 第 90 轮按站长要求放宽。
*
* 原来这里是"一律禁止修改"(403 无法修改系统初始超级管理员),
* 结果是连它的**显示名都改不了**(站长想把界面上的 admin 改成自己的名字)。
* 现在按"哪些能改、哪些必须锁死"拆开:
* · 允许:姓名(显示名)、年级、班级、状态;
* · 锁死:**登录名必须保持 admin** —— 那是 admin/admin123 的应急登录通道,改了就没了;
* · 锁死:角色不能动(不能把自己降权,否则没人能管站点);
* · 锁死:密码不在这里改(避免被其它管理员顺手重置)。
*/
if (targetUser.id === 1) {
const nextUsername = String(username || '').trim()
if (nextUsername && nextUsername !== targetUser.username) {
throw createError({
statusCode: 400,
statusMessage: '初始超级管理员的登录名必须保持 admin(应急登录通道,改了就无法用它登录)'
})
}
if (role && role !== targetUser.role) {
throw createError({
statusCode: 403,
statusMessage: '无法修改系统初始超级管理员'
statusMessage: '不能修改初始超级管理员的角色'
})
}
if (password) {
throw createError({
statusCode: 403,
statusMessage: '不能在用户管理中重置初始超级管理员的密码(请用它登录后在「设置」里自行修改)'
})
}
}
// 2. 禁止对自身进行任何用户管理操作(包括角色、资料、状态、密码等)
if (String(userId) === String(user.id)) {
@@ -0,0 +1,66 @@
import { eq } from 'drizzle-orm'
import { db } from '~/drizzle/db'
import { users } from '~/drizzle/schema'
import { getRoleLevel } from '~~/server/utils/permissions'
/**
* 把某个账号**提升为超级管理员**(第 90 轮新增)。
*
* 为什么单独开一个接口,而不是放开 `[id].put.ts` 的 role 字段:
* 那里面 `canChangeRoleOf()` **明确禁止**把任何人提升为 SUPER_ADMIN
* (报错原文:"超级管理员不能将目标用户提升为 SUPER_ADMIN"),
* 而且用户管理的角色下拉也**故意不给** SUPER_ADMIN 选项(网页版同样如此)。
* 站长的诉求是"用我自己绑了微信的中文账号做日常管理",所以要给一条**显式、可审计**的通道:
* 单独接口 + 前端二次确认,而不是把通用编辑接口的口子放大。
*
* 权限:只有 SUPER_ADMIN 能调;不能提升自己(已经是了);不能对已经是超管的账号重复提升。
* 注意:初始超级管理员(ID=1)本身就是 SUPER_ADMIN,会命中"已经是超级管理员"。
*/
export default defineEventHandler(async (event) => {
const user = event.context.user
if (!user) {
throw createError({ statusCode: 401, message: '未登录' })
}
if (user.role !== 'SUPER_ADMIN') {
throw createError({ statusCode: 403, message: '仅超级管理员可以提升其他账号为超级管理员' })
}
const userId = Number(getRouterParam(event, 'id'))
if (!userId || Number.isNaN(userId)) {
throw createError({ statusCode: 400, message: '无效的用户ID' })
}
if (userId === user.id) {
throw createError({ statusCode: 400, message: '你自己已经是超级管理员了' })
}
const rows = await db.select().from(users).where(eq(users.id, userId)).limit(1)
if (rows.length === 0) {
throw createError({ statusCode: 404, message: '用户不存在' })
}
const target = rows[0]
if (target.role === 'SUPER_ADMIN') {
throw createError({ statusCode: 400, message: `「${target.name || target.username}」已经是超级管理员` })
}
// 只允许"向上提升",不允许借此降权(降权仍走通用编辑接口)
if (getRoleLevel(target.role) > getRoleLevel('SUPER_ADMIN')) {
throw createError({ statusCode: 400, message: '目标账号角色异常,无法提升' })
}
await db.update(users).set({ role: 'SUPER_ADMIN' }).where(eq(users.id, userId))
// 角色变了 → 清掉它的认证缓存,避免旧权限继续生效
try {
const { cache } = await import('~~/server/utils/cache-helpers')
await cache.delete(`auth:user:${userId}`)
} catch {
// 清缓存失败不影响提升结果
}
console.log(`[Users] ${user.username} 把 ${target.username}(#${userId}) 提升为 SUPER_ADMIN`)
return {
success: true,
message: `已把「${target.name || target.username}」提升为超级管理员`,
user: { id: target.id, name: target.name, username: target.username, role: 'SUPER_ADMIN' }
}
})