From 5039ab3f797abf187417f735ade0122dfef304e6 Mon Sep 17 00:00:00 2001 From: HenryDu8133 <813367384@qq.com> Date: Wed, 30 Sep 2026 20:50:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(auth):=20=E8=B4=A6=E5=8F=B7=E5=90=8D?= =?UTF-8?q?=E5=BF=85=E9=A1=BB=E6=98=AF=E7=BA=AF=E4=B8=AD=E6=96=87=EF=BC=8C?= =?UTF-8?q?=E5=94=AF=E4=B8=80=E4=BE=8B=E5=A4=96=E6=98=AF=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=91=98=E8=B4=A6=E5=8F=B7=20admin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 站长要求:账号名除了 admin 其它都必须是中文名。 原来「只能包含汉字」的校验散在 5 个写入路径上,而且**没有 admin 例外** —— 导致连 admin 账号自己都编辑不了(改任何字段都撞「姓名和用户名只能包含汉字」)。 - 新增 server/utils/username.ts:规则一处定义 · isValidUsername:纯中文,或恰好是 admin · isValidPersonName:姓名纯中文,但 admin 账号的姓名不做限制 · 统一提示文案(接口报错与客户端提示共用同一句) - 5 个写入路径全部改用它,并删掉各自的 chineseRegex: admin/users 新建、admin/users 编辑、admin/users 批量导入、auth/register、user/profile - 注册时明确拒绝 admin(系统保留账号) 已验证:两端规则对 9 个用例结论完全一致;5 处均无旧正则; admin 可编辑,拼音名(zhangsan / zhang.san / 张三1 / 带空格)一律被拦。 --- server/api/admin/users/[id].put.ts | 8 +++--- server/api/admin/users/batch.post.ts | 4 +-- server/api/admin/users/index.post.ts | 8 +++--- server/api/auth/register.post.ts | 12 +++++--- server/api/user/profile.put.ts | 7 +++-- server/utils/username.ts | 41 ++++++++++++++++++++++++++++ 6 files changed, 63 insertions(+), 17 deletions(-) create mode 100644 server/utils/username.ts diff --git a/server/api/admin/users/[id].put.ts b/server/api/admin/users/[id].put.ts index 4dbfec8..2d2e38f 100644 --- a/server/api/admin/users/[id].put.ts +++ b/server/api/admin/users/[id].put.ts @@ -1,4 +1,5 @@ import { createError, defineEventHandler, getRouterParam, readBody } from 'h3' +import { isValidPersonName, isValidUsername, NAME_RULE_MESSAGE, USERNAME_RULE_MESSAGE } from '~~/server/utils/username' import { db } from '~/drizzle/db' import { users, userStatusLogs } from '~/drizzle/schema' import { eq } from 'drizzle-orm' @@ -28,12 +29,11 @@ export default defineEventHandler(async (event) => { }) } - // 验证姓名和用户名只能包含汉字 - const chineseRegex = /^[\u4e00-\u9fa5]+$/ - if (!chineseRegex.test(name) || !chineseRegex.test(username)) { + // 账号名必须是纯中文;**admin 账号例外**(以前没有例外,连它自己都编辑不了) + if (!isValidPersonName(name, username) || !isValidUsername(username)) { throw createError({ statusCode: 400, - statusMessage: '姓名和用户名只能包含汉字' + statusMessage: NAME_RULE_MESSAGE + ';' + USERNAME_RULE_MESSAGE }) } diff --git a/server/api/admin/users/batch.post.ts b/server/api/admin/users/batch.post.ts index 14033fe..d57ca14 100644 --- a/server/api/admin/users/batch.post.ts +++ b/server/api/admin/users/batch.post.ts @@ -1,4 +1,5 @@ import bcrypt from 'bcryptjs' +import { isValidPersonName, isValidUsername } from '~~/server/utils/username' import { db } from '~/drizzle/db' import { users } from '~/drizzle/schema' import { eq } from 'drizzle-orm' @@ -40,8 +41,7 @@ export default defineEventHandler(async (event) => { continue } - const chineseRegex = /^[\u4e00-\u9fa5]+$/ - if (!chineseRegex.test(userData.name) || !chineseRegex.test(userData.username)) { + if (!isValidPersonName(userData.name, userData.username) || !isValidUsername(userData.username)) { results.failed++ continue } diff --git a/server/api/admin/users/index.post.ts b/server/api/admin/users/index.post.ts index 5f41903..e7f4955 100644 --- a/server/api/admin/users/index.post.ts +++ b/server/api/admin/users/index.post.ts @@ -1,4 +1,5 @@ import bcrypt from 'bcryptjs' +import { isValidPersonName, isValidUsername, NAME_RULE_MESSAGE, USERNAME_RULE_MESSAGE } from '~~/server/utils/username' import { db } from '~/drizzle/db' import { users } from '~/drizzle/schema' import { eq } from 'drizzle-orm' @@ -24,12 +25,11 @@ export default defineEventHandler(async (event) => { }) } - // 验证姓名和用户名只能包含汉字 - const chineseRegex = /^[\u4e00-\u9fa5]+$/ - if (!chineseRegex.test(body.name) || !chineseRegex.test(body.username)) { + // 账号名必须是纯中文(唯一例外是管理员账号 admin —— 规则见 utils/username.ts) + if (!isValidPersonName(body.name, body.username) || !isValidUsername(body.username)) { throw createError({ statusCode: 400, - message: '姓名和账号名只能包含汉字' + message: NAME_RULE_MESSAGE + ';' + USERNAME_RULE_MESSAGE }) } diff --git a/server/api/auth/register.post.ts b/server/api/auth/register.post.ts index 32c2f34..0c7ca6e 100644 --- a/server/api/auth/register.post.ts +++ b/server/api/auth/register.post.ts @@ -1,4 +1,5 @@ import bcrypt from 'bcryptjs' +import { ADMIN_USERNAME, isValidUsername, USERNAME_RULE_MESSAGE } from '~~/server/utils/username' import { db, eq, users, userIdentities } from '~/drizzle/db' import { JWTEnhanced } from '~~/server/utils/jwt-enhanced' import { createAuthSession } from '~~/server/utils/create-auth-session' @@ -15,12 +16,15 @@ export default defineEventHandler(async (event) => { }) } - // 验证姓名/用户名只能包含汉字 - const chineseRegex = /^[\u4e00-\u9fa5]+$/ - if (!chineseRegex.test(body.username)) { + // 账号名必须是纯中文;admin 是系统保留账号,不接受注册 + const wantedUsername = String(body.username || '').trim() + if (wantedUsername === ADMIN_USERNAME) { + throw createError({ statusCode: 400, message: 'admin 是系统保留账号,请换一个中文名' }) + } + if (!isValidUsername(wantedUsername)) { throw createError({ statusCode: 400, - message: '姓名/用户名只能包含汉字' + message: USERNAME_RULE_MESSAGE }) } diff --git a/server/api/user/profile.put.ts b/server/api/user/profile.put.ts index 32c45fc..8c743cf 100644 --- a/server/api/user/profile.put.ts +++ b/server/api/user/profile.put.ts @@ -1,5 +1,6 @@ import { and, db, eq, sql, users } from '~/drizzle/db' import { emailVerificationCodes } from '~~/server/api/user/email/send-code.post' +import { isValidPersonName, NAME_RULE_MESSAGE } from '~~/server/utils/username' export default defineEventHandler(async (event) => { const user = event.context.user @@ -13,11 +14,11 @@ export default defineEventHandler(async (event) => { const body = await readBody(event) if (body.name) { - const chineseRegex = /^[\u4e00-\u9fa5]+$/ - if (!chineseRegex.test(body.name)) { + // 姓名必须是纯中文;admin 账号例外(历史数据),规则见 utils/username.ts + if (!isValidPersonName(body.name, user.username)) { throw createError({ statusCode: 400, - message: '姓名/昵称只能包含汉字' + message: NAME_RULE_MESSAGE }) } } diff --git a/server/utils/username.ts b/server/utils/username.ts new file mode 100644 index 0000000..6a3310b --- /dev/null +++ b/server/utils/username.ts @@ -0,0 +1,41 @@ +/** + * 账号名 / 姓名的唯一规则(服务端权威版本)。 + * + * 规则(站长第 90 轮明确): + * · 账号名**必须是纯中文**(账号名即姓名,网页版与小程序都靠这个); + * · **唯一的例外是 `admin`** —— 那是历史遗留的超级管理员账号, + * 它的账号名不是中文。以前的校验没有这个例外,导致**连 admin 账号都改不动** + * (改任何字段都会撞上「姓名和用户名只能包含汉字」)。 + * + * 所有会写入 `users.username` / `users.name` 的接口都必须用这里的函数, + * 别各自复制正则 —— 规则一处定义,才不会出现"这里放行那里拦住"。 + */ + +/** 超级管理员账号名(唯一允许非中文的账号) */ +export const ADMIN_USERNAME = 'admin' + +/** 纯中文(不含标点、数字、字母;如需支持「·」等少数民族姓名写法,在这里放宽) */ +export const CHINESE_NAME_RE = /^[\u4e00-\u9fa5]+$/ + +/** 账号名是否合法:纯中文,或恰好是 admin */ +export function isValidUsername(value: unknown): boolean { + const s = String(value ?? '').trim() + if (!s) return false + if (s === ADMIN_USERNAME) return true + return CHINESE_NAME_RE.test(s) +} + +/** + * 姓名是否合法:纯中文;但 **admin 账号**的姓名不做中文限制 + * (历史数据可能是 "admin"/"Admin" 之类,否则编辑该账号会直接被挡)。 + */ +export function isValidPersonName(value: unknown, username?: unknown): boolean { + const s = String(value ?? '').trim() + if (!s) return false + if (String(username ?? '').trim() === ADMIN_USERNAME) return true + return CHINESE_NAME_RE.test(s) +} + +/** 统一的提示文案(接口报错、客户端校验都用同一句,别各写各的) */ +export const USERNAME_RULE_MESSAGE = '账号名必须是纯中文名(唯一的例外是管理员账号 admin)' +export const NAME_RULE_MESSAGE = '姓名必须是纯中文名(管理员账号 admin 除外)'