Files
order_site/apps/backend/src/services/admin/admin-rbac-service.ts
T
yml2213 3df271b30c 后台新增RBAC权限模型支持按角色配置接单管理权限
- 新增 admin_roles/admin_permissions/admin_role_permissions 三表与种子数据(059迁移)
- 接单平台全部管理路由从固定角色切换为细粒度权限校验
- 撤单按动作二次校验:退回大厅与取消订单分别要求对应权限
- 管理员账号页新增角色权限配置卡片(admin专属,带审计日志)
- 权限更新走事务保证原子性;session实时解析权限确保修改立即生效
2026-08-23 16:04:10 +08:00

166 lines
4.5 KiB
TypeScript

import { query, withTransaction } from '../../db/client.js'
import { createHttpError } from '../../utils/http.js'
export const ADMIN_PERMISSION_KEYS = [
'worker_order.view',
'worker_order.edit',
'worker_order.configure_share',
'worker_order.assign',
'worker_order.return_to_hall',
'worker_order.cancel',
'worker_order.cancel_share',
'worker_order.delete',
'worker_order.publish',
'worker_order.accept',
'worker_order.reopen',
'worker_order.update_material',
'worker_order.manage_problem',
'worker_order.deduct_deposit',
'worker_cancel_request.review',
] as const
export type AdminPermissionKey = (typeof ADMIN_PERMISSION_KEYS)[number]
type PermissionRow = {
permission_key: string
permission_name: string
permission_group: string
description: string
}
export async function listAdminPermissionKeysForRole(role: string): Promise<string[]> {
const result = await query<{ permission_key: string }>(
`
SELECT arp.permission_key
FROM admin_role_permissions arp
WHERE arp.role_key = $1
ORDER BY arp.permission_key
`,
[
String(role || '')
.trim()
.toLowerCase(),
],
)
return result.rows.map((row) => String(row.permission_key || '').trim()).filter(Boolean)
}
export async function listAdminRolesWithPermissions() {
const result = await query<
PermissionRow & { role_key: string; role_name: string; role_description: string }
>(
`
SELECT
ar.role_key,
ar.role_name,
ar.description AS role_description,
ap.permission_key,
ap.permission_name,
ap.permission_group,
ap.description
FROM admin_roles ar
CROSS JOIN admin_permissions ap
ORDER BY ar.role_key, ap.permission_group, ap.permission_key
`,
)
const roleMap = new Map<
string,
{
roleKey: string
roleName: string
description: string
permissions: Array<{
permissionKey: string
permissionName: string
permissionGroup: string
description: string
enabled: boolean
}>
}
>()
const enabled = await query<{ role_key: string; permission_key: string }>(
'SELECT role_key, permission_key FROM admin_role_permissions',
)
const enabledSet = new Set(enabled.rows.map((row) => `${row.role_key}:${row.permission_key}`))
for (const row of result.rows) {
let role = roleMap.get(row.role_key)
if (!role) {
role = {
roleKey: row.role_key,
roleName: row.role_name,
description: row.role_description,
permissions: [],
}
roleMap.set(row.role_key, role)
}
role.permissions.push({
permissionKey: row.permission_key,
permissionName: row.permission_name,
permissionGroup: row.permission_group,
description: row.description,
enabled: enabledSet.has(`${row.role_key}:${row.permission_key}`),
})
}
return { roles: Array.from(roleMap.values()) }
}
export async function updateAdminRolePermissions(roleKey: unknown, permissionKeys: unknown) {
const normalizedRole = String(roleKey || '')
.trim()
.toLowerCase()
if (!['admin', 'operator', 'support'].includes(normalizedRole)) {
throw createHttpError('角色不存在或不可修改', {
statusCode: 400,
errorCode: 'admin_role_invalid',
})
}
if (!Array.isArray(permissionKeys)) {
throw createHttpError('权限列表格式无效', {
statusCode: 400,
errorCode: 'admin_permissions_invalid',
})
}
const requested = Array.from(
new Set(
permissionKeys
.map((item) => String(item || '').trim())
.filter((item): item is AdminPermissionKey =>
(ADMIN_PERMISSION_KEYS as readonly string[]).includes(item),
),
),
)
if (normalizedRole === 'admin' && requested.length !== ADMIN_PERMISSION_KEYS.length) {
throw createHttpError('管理员角色必须保留全部权限', {
statusCode: 409,
errorCode: 'admin_role_full_permissions_required',
})
}
await withTransaction(async (client) => {
await client.query('DELETE FROM admin_role_permissions WHERE role_key = $1', [normalizedRole])
if (requested.length > 0) {
await client.query(
`
INSERT INTO admin_role_permissions (role_key, permission_key)
SELECT $1, permission_key
FROM admin_permissions
WHERE permission_key = ANY($2::text[])
ON CONFLICT DO NOTHING
`,
[normalizedRole, requested],
)
}
})
return (await listAdminRolesWithPermissions()).roles.find(
(role) => role.roleKey === normalizedRole,
)
}