v3.5.8和v3.5.9版本更新
CI / verify (pull_request) Has been cancelled

This commit is contained in:
2026-08-25 09:20:32 +08:00
parent 7304c7a8e1
commit 5935024ea8
101 changed files with 7640 additions and 5364 deletions
@@ -1,9 +1,12 @@
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import {
HQ_ADMIN_ROLE_VALUES,
HQ_PERMISSION_CATALOG,
HQ_ROLE_DEFAULT_PERMISSIONS,
LEGACY_SYSTEM_SETTINGS_KEY,
computeHqEffectivePermissionKeys,
expandHqPermissionKeys,
type HqAdminRoleValue,
type HqPermissionKey,
} from '@dukang/shared-types';
import { PrismaService } from '../../common/prisma/prisma.module';
@@ -14,6 +17,10 @@ const VALID_PERMISSION_KEYS = new Set<string>([
LEGACY_SYSTEM_SETTINGS_KEY,
]);
const EDITABLE_ROLES = new Set<HqAdminRoleValue>(
HQ_ADMIN_ROLE_VALUES.filter((r) => r !== 'SUPER_ADMIN'),
);
function assertPermissionKeys(keys: string[]) {
const invalid = keys.filter((key) => !VALID_PERMISSION_KEYS.has(key));
if (invalid.length) {
@@ -21,6 +28,13 @@ function assertPermissionKeys(keys: string[]) {
}
}
function asAdminRole(role: string): HqAdminRoleValue {
if (!(HQ_ADMIN_ROLE_VALUES as readonly string[]).includes(role)) {
throw new BadRequestException(`无效角色: ${role}`);
}
return role as HqAdminRoleValue;
}
@Injectable()
export class AdminHqPermissionsService {
constructor(private readonly prisma: PrismaService) {}
@@ -28,32 +42,36 @@ export class AdminHqPermissionsService {
catalog() {
return {
permissions: HQ_PERMISSION_CATALOG,
roles: Object.entries(HQ_ROLE_DEFAULT_PERMISSIONS).map(([role, permissionKeys]) => ({
roles: HQ_ADMIN_ROLE_VALUES.map((role) => ({
role,
permissionKeys,
permissionKeys: HQ_ROLE_DEFAULT_PERMISSIONS[role],
})),
};
}
async getRolePermissions(role: string) {
const adminRole = asAdminRole(role);
const rows = await this.prisma.hqRolePermission.findMany({
where: { adminRole: role as 'SUPER_ADMIN' | 'OPS' | 'FINANCE' | 'CUSTOMER_SERVICE' },
where: { adminRole },
select: { permissionKey: true },
});
const permissionKeys =
rows.length > 0
? expandHqPermissionKeys(rows.map((r) => r.permissionKey))
: [...(HQ_ROLE_DEFAULT_PERMISSIONS[role] ?? [])];
return { role, permissionKeys };
: [...(HQ_ROLE_DEFAULT_PERMISSIONS[adminRole] ?? [])];
return { role: adminRole, permissionKeys };
}
async saveRolePermissions(role: string, permissionKeys: string[]) {
if (role === 'SUPER_ADMIN') {
const adminRole = asAdminRole(role);
if (adminRole === 'SUPER_ADMIN') {
throw new BadRequestException('超级管理员基础权限固定,危险操作请按用户单独授权');
}
if (!EDITABLE_ROLES.has(adminRole)) {
throw new BadRequestException(`无效角色: ${role}`);
}
assertPermissionKeys(permissionKeys);
const normalized = expandHqPermissionKeys(permissionKeys);
const adminRole = role as 'OPS' | 'FINANCE' | 'CUSTOMER_SERVICE';
await this.prisma.$transaction([
this.prisma.hqRolePermission.deleteMany({ where: { adminRole } }),
...(normalized.length
@@ -76,51 +94,84 @@ export class AdminHqPermissionsService {
const userPerms = await this.prisma.hqAccountPermission.findMany({
where: { hqAccountId: accountId },
select: { permissionKey: true },
select: { permissionKey: true, effect: true },
});
const userPermissionKeys = expandHqPermissionKeys(userPerms.map((p) => p.permissionKey));
const grantKeys = expandHqPermissionKeys(
userPerms.filter((p) => p.effect !== 'DENY').map((p) => p.permissionKey),
);
const denyKeys = expandHqPermissionKeys(
userPerms.filter((p) => p.effect === 'DENY').map((p) => p.permissionKey),
);
if (account.adminRole === 'SUPER_ADMIN') {
const rolePermissionKeys = HQ_PERMISSION_CATALOG.map(
(p) => p.key,
) as HqPermissionKey[];
const effectivePermissionKeys = [
...new Set([...rolePermissionKeys, ...userPermissionKeys]),
] as HqPermissionKey[];
const rolePermissionKeys = HQ_PERMISSION_CATALOG.map((p) => p.key) as HqPermissionKey[];
const effectivePermissionKeys = computeHqEffectivePermissionKeys(
rolePermissionKeys,
grantKeys,
[],
);
return serializeBigInt({
account,
permissionKeys: userPermissionKeys,
permissionKeys: grantKeys,
rolePermissionKeys,
userPermissionKeys,
grantKeys,
denyKeys: [] as HqPermissionKey[],
userPermissionKeys: grantKeys,
effectivePermissionKeys,
});
}
const rolePerms = await this.getRolePermissions(account.adminRole);
const effectivePermissionKeys = [
...new Set([...rolePerms.permissionKeys, ...userPermissionKeys]),
] as HqPermissionKey[];
const effectivePermissionKeys = computeHqEffectivePermissionKeys(
rolePerms.permissionKeys,
grantKeys,
denyKeys,
);
return serializeBigInt({
account,
permissionKeys: userPermissionKeys,
permissionKeys: grantKeys,
rolePermissionKeys: rolePerms.permissionKeys,
userPermissionKeys,
grantKeys,
denyKeys,
userPermissionKeys: grantKeys,
effectivePermissionKeys,
});
}
async saveAccountPermissions(accountId: bigint, permissionKeys: string[]) {
async saveAccountPermissions(
accountId: bigint,
grantKeysInput: string[],
denyKeysInput: string[] = [],
) {
const account = await this.prisma.hqAccount.findUnique({ where: { id: accountId } });
if (!account) throw new NotFoundException('HQ 账号不存在');
assertPermissionKeys(permissionKeys);
const normalized = expandHqPermissionKeys(permissionKeys);
assertPermissionKeys(grantKeysInput);
assertPermissionKeys(denyKeysInput);
const grantKeys = expandHqPermissionKeys(grantKeysInput);
const denyKeys =
account.adminRole === 'SUPER_ADMIN' ? [] : expandHqPermissionKeys(denyKeysInput);
const overlap = grantKeys.filter((k) => denyKeys.includes(k));
if (overlap.length) {
throw new BadRequestException(`同一权限不能同时追加和撤销: ${overlap.join(', ')}`);
}
await this.prisma.$transaction([
this.prisma.hqAccountPermission.deleteMany({ where: { hqAccountId: accountId } }),
...(normalized.length
...(grantKeys.length || denyKeys.length
? [
this.prisma.hqAccountPermission.createMany({
data: normalized.map((permissionKey) => ({ hqAccountId: accountId, permissionKey })),
data: [
...grantKeys.map((permissionKey) => ({
hqAccountId: accountId,
permissionKey,
effect: 'GRANT' as const,
})),
...denyKeys.map((permissionKey) => ({
hqAccountId: accountId,
permissionKey,
effect: 'DENY' as const,
})),
],
}),
]
: []),