- 平台管理员模型:super_admin=平台超管、tenant_super_admin=租户顶层(保留码),tenant:* 仅平台可授予 - 软删改名回收唯一键 + P2002→409 - 删除角色级联清理关联 + 聚合过滤已删/停用角色资源 - tokenVersion 会话吊销(重置密码/停用/删除即时踢下线) - data_scope self 按 userId 过滤 + 详情数据权限校验 - 组织树防环、seed 不重置密码、日志脱敏补 phone/email - JWT 密钥强校验、pageSize 上限、DTO 枚举校验、Prisma 错误映射、授权事务化 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
74 lines
2.4 KiB
TypeScript
74 lines
2.4 KiB
TypeScript
import type { PrismaClient } from '@prisma/client';
|
||
import { DataScope } from './enums.js';
|
||
|
||
/** 数据权限档位(从大到小)的优先级,用于多角色取最高权限。 */
|
||
const RANK: Record<DataScope, number> = {
|
||
all: 5,
|
||
dept_and_child: 4,
|
||
dept: 3,
|
||
custom: 2,
|
||
self: 1,
|
||
};
|
||
|
||
/** 从多个角色的 data_scope 中取最高权限档。 */
|
||
export function resolveDataScope(scopes: DataScope[]): DataScope {
|
||
if (scopes.length === 0) return 'self';
|
||
return scopes.reduce((a, b) => (RANK[a] >= RANK[b] ? a : b));
|
||
}
|
||
|
||
/**
|
||
* 计算数据权限可见的部门 code 集合。
|
||
* 返回空数组表示「全部可见」(不受数据权限限制)。
|
||
*/
|
||
export async function resolveVisibleOrgs(opts: {
|
||
prisma: PrismaClient;
|
||
tenantCode: string;
|
||
dataScope: DataScope;
|
||
orgCode: string | null;
|
||
roleCodes: string[];
|
||
}): Promise<string[]> {
|
||
const { prisma, tenantCode, dataScope, orgCode, roleCodes } = opts;
|
||
switch (dataScope) {
|
||
case 'all':
|
||
return [];
|
||
case 'self':
|
||
// 仅本人:不走组织过滤,由调用方按 userId 过滤(返回哨兵避免误放大为全量)
|
||
return ['__none__'];
|
||
case 'dept':
|
||
return orgCode ? [orgCode] : ['__none__'];
|
||
case 'dept_and_child':
|
||
return orgCode ? collectDescendants(prisma, tenantCode, orgCode) : ['__none__'];
|
||
case 'custom': {
|
||
const rows = await prisma.roleOrg.findMany({
|
||
where: { tenantCode, roleCode: { in: roleCodes } },
|
||
select: { orgCode: true },
|
||
});
|
||
const codes: string[] = [...new Set(rows.map((r) => r.orgCode))];
|
||
return codes.length ? codes : ['__none__'];
|
||
}
|
||
}
|
||
}
|
||
|
||
/** 收集某部门及其全部后代部门的 code(含自身),带 visited 防环。 */
|
||
async function collectDescendants(prisma: PrismaClient, tenantCode: string, rootCode: string): Promise<string[]> {
|
||
const result: string[] = [];
|
||
const visited = new Set<string>();
|
||
let frontier = [rootCode];
|
||
while (frontier.length > 0) {
|
||
const next: string[] = [];
|
||
for (const code of frontier) {
|
||
if (visited.has(code)) continue;
|
||
visited.add(code);
|
||
result.push(code);
|
||
next.push(code);
|
||
}
|
||
if (next.length === 0) break;
|
||
const children = await prisma.org.findMany({
|
||
where: { tenantCode, parentCode: { in: next }, deletedAt: null },
|
||
select: { code: true },
|
||
});
|
||
frontier = children.map((c) => c.code);
|
||
}
|
||
return result;
|
||
}
|