feat: 新增客服备用金台账与后台用户中文显示名

- 新增备用金账户、流水、支出、对账与权限
- 后台用户支持中文显示名并同步备用金姓名
- 审计、登录记录、任务及工单操作人优先展示中文名
- 补充备用金与人工支出方案文档
This commit is contained in:
yml2213
2026-10-18 13:55:24 +08:00
parent 7bacb7d5eb
commit 7542d547bf
55 changed files with 6557 additions and 105 deletions
@@ -28,6 +28,7 @@ export type AdminSession = {
sessionId: string
userId: number
username: string
displayName: string
role: AdminRole
expiresAt: string
sessionVersion: number
@@ -48,6 +49,7 @@ export async function ensureAdminUsersBootstrapped(): Promise<void> {
const username = String(configuredUser?.username || '')
.trim()
.toLowerCase()
const displayName = normalizeDisplayName(configuredUser?.displayName, username) || username
const password = String(configuredUser?.password || '').trim()
const role = normalizeAdminRole(configuredUser?.role)
@@ -62,6 +64,7 @@ export async function ensureAdminUsersBootstrapped(): Promise<void> {
const now = nowIso()
await createAdminUser({
username,
displayName,
passwordHash: hashAdminPassword(password),
role,
status: 'active',
@@ -229,6 +232,7 @@ export async function verifyAdminSessionToken(token: unknown): Promise<AdminSess
sessionId,
userId: Number(user.id),
username: String(user.username || ''),
displayName: String(user.display_name || user.username || ''),
role: normalizeAdminRole(user.role),
expiresAt,
sessionVersion: currentSessionVersion,
@@ -251,6 +255,7 @@ export async function getAdminSessionSummary(token: unknown): Promise<JsonObject
user: {
userId: session.userId,
username: session.username,
displayName: session.displayName,
role: session.role,
permissions: session.permissions,
},
@@ -277,7 +282,7 @@ export async function getAdminUserList(query: JsonObject = {}): Promise<JsonObje
const { items, total } = await listAdminUsers({
page,
pageSize,
username: String(query.username || '').trim(),
keyword: String(query.keyword || query.username || '').trim(),
role: normalizeRoleQuery(query.role),
status: normalizeStatusQuery(query.status),
})
@@ -290,6 +295,7 @@ export async function getAdminUserList(query: JsonObject = {}): Promise<JsonObje
export async function createManagedAdminUser(payload: JsonObject = {}): Promise<JsonObject> {
const username = normalizeUsername(payload.username)
const displayName = normalizeDisplayName(payload.displayName, username) || username
const password = normalizePassword(payload.password)
if (!username) {
@@ -319,6 +325,7 @@ export async function createManagedAdminUser(payload: JsonObject = {}): Promise<
const now = nowIso()
const created = await createAdminUser({
username,
displayName,
passwordHash: hashAdminPassword(password),
role: normalizeAdminRole(payload.role),
status: normalizeAdminUserStatus(payload.status || 'active'),
@@ -401,6 +408,28 @@ export async function updateManagedAdminUserStatus(
}
}
export async function updateManagedAdminUserDisplayName(
userId: number | string,
payload: JsonObject = {},
): Promise<JsonObject> {
const user = await getRequiredAdminUser(userId)
const displayName = normalizeDisplayName(payload.displayName, user.username) || user.username
const updated = await updateAdminUser(user.id, {
display_name: displayName,
updated_at: nowIso(),
})
if (!updated) {
throw createHttpError('后台用户更新失败', {
statusCode: 500,
errorCode: 'admin_user_update_failed',
})
}
return {
user: mapAdminUser(updated),
}
}
export async function resetManagedAdminUserPassword(
userId: number | string,
payload: JsonObject = {},
@@ -472,6 +501,7 @@ function createAdminSession(user: AdminUserRow): JsonObject {
user: {
userId: Number(user.id),
username: String(user.username || ''),
displayName: String(user.display_name || user.username || ''),
role: normalizeAdminRole(user.role),
},
}
@@ -559,6 +589,27 @@ function normalizeUsername(username: unknown): string {
.toLowerCase()
}
function normalizeDisplayName(displayName: unknown, fallback = ''): string {
const normalized = String(displayName ?? '')
.trim()
.replace(/\s+/g, ' ')
if (!normalized) {
return fallback
}
const characters = Array.from(normalized)
const hasControlCharacter = characters.some((character) => {
const codePoint = character.codePointAt(0) || 0
return codePoint <= 0x1f || codePoint === 0x7f
})
if (characters.length > 32 || hasControlCharacter) {
throw createHttpError('姓名格式无效,最多 32 个字符且不能包含控制字符', {
statusCode: 400,
errorCode: 'admin_user_display_name_invalid',
})
}
return normalized
}
function normalizePassword(password: unknown): string {
return String(password || '').trim()
}
@@ -634,6 +685,7 @@ function mapAdminUser(user: AdminUserRow): JsonObject {
return {
userId: Number(user.id),
username: String(user.username || ''),
displayName: String(user.display_name || user.username || ''),
role: normalizeAdminRole(user.role),
status: normalizeAdminUserStatus(user.status),
createdAt: user.created_at,