feat(auth): 实现平台超管与多租户角色权限深度隔离、前端动态路由白名单及DeepSeek配额守卫

这个提交包含在:
wikiboss
2026-09-21 11:21:54 +08:00
父节点 0f1a318f95
当前提交 13752f9906
共有 11 个文件被更改,包括 380 次插入 和 28 次删除

查看文件

@@ -9,8 +9,20 @@ export type IRoute = AuthParams & {
children?: IRoute[];
// 当前路由是否渲染菜单项,为 true 的话不会在菜单中显示,但可通过路由地址访问。
ignore?: boolean;
// 是否仅平台超级管理员可见
superAdminOnly?: boolean;
// 是否仅租户可见
tenantOnly?: boolean;
// 角色白名单访问限制
roles?: string[];
};
export interface RoleContext {
isSuper?: boolean;
role?: string;
tenantId?: string | number;
}
export const routes: IRoute[] = [
{
name: 'menu.dashboard',
@@ -27,6 +39,7 @@ export const routes: IRoute[] = [
{
name: 'menu.dashboard.financial',
key: 'dashboard/financial',
superAdminOnly: true,
},
],
},
@@ -73,22 +86,39 @@ export const routes: IRoute[] = [
key: 'team',
children: [
{ name: 'menu.team.members', key: 'team/members' },
{ name: 'menu.team.brandKit', key: 'team/brand-kit' },
{ name: 'menu.team.handover', key: 'team/handover' },
{
name: 'menu.team.brandKit',
key: 'team/brand-kit',
roles: ['super_admin', 'tenant_owner', 'admin'],
},
{
name: 'menu.team.handover',
key: 'team/handover',
roles: ['super_admin', 'tenant_owner', 'admin'],
},
],
},
{
name: 'menu.settingsBiz',
key: 'settings-biz',
children: [
{ name: 'menu.settings.tenant', key: 'settings/tenant' },
{ name: 'menu.settings.billing', key: 'settings/billing' },
{
name: 'menu.settings.tenant',
key: 'settings/tenant',
roles: ['super_admin', 'tenant_owner', 'admin'],
},
{
name: 'menu.settings.billing',
key: 'settings/billing',
roles: ['super_admin', 'tenant_owner', 'admin'],
},
{ name: 'menu.settings.auditLogs', key: 'settings/audit-logs' },
],
},
{
name: 'menu.platform',
key: 'platform',
superAdminOnly: true,
children: [
{ name: 'menu.platform.subscribers', key: 'platform/subscribers' },
{ name: 'menu.platform.renewals', key: 'platform/renewals' },
@@ -127,8 +157,57 @@ export const getName = (path: string, routes) => {
});
};
export const filterRoutesByRole = (
routesList: IRoute[],
roleCtx?: RoleContext
): IRoute[] => {
if (!routesList || !routesList.length) {
return [];
}
const isSuper = Boolean(roleCtx?.isSuper || roleCtx?.role === 'super_admin');
const role = roleCtx?.role || (isSuper ? 'super_admin' : 'sales_rep');
const result: IRoute[] = [];
for (const route of routesList) {
// 1. 平台超级管理员专属路由隔离
if (route.superAdminOnly && !isSuper) {
continue;
}
// 2. 租户专属路由隔离
if (route.tenantOnly && isSuper) {
continue;
}
// 3. 角色白名单访问限制
if (route.roles && route.roles.length > 0) {
const isAllowed = isSuper || route.roles.includes(role);
if (!isAllowed) {
continue;
}
}
// 4. 递归处理子路由
if (route.children && route.children.length > 0) {
const filteredChildren = filterRoutesByRole(route.children, roleCtx);
if (filteredChildren.length === 0) {
continue;
}
result.push({
...route,
children: filteredChildren,
});
} else {
result.push({ ...route });
}
}
return result;
};
export const generatePermission = (role: string) => {
const actions = role === 'admin' ? ['*'] : ['read'];
const actions = role === 'admin' || role === 'super_admin' ? ['*'] : ['read'];
const result = {};
routes.forEach((item) => {
if (item.children) {
@@ -140,7 +219,10 @@ export const generatePermission = (role: string) => {
return result;
};
const useRoute = (userPermission): [IRoute[], string] => {
const useRoute = (
userPermission?: Record<string, string[]>,
roleCtx?: RoleContext
): [IRoute[], string] => {
const filterRoute = (routes: IRoute[], arr = []): IRoute[] => {
if (!routes.length) {
return [];
@@ -148,7 +230,7 @@ const useRoute = (userPermission): [IRoute[], string] => {
for (const route of routes) {
const { requiredPermissions, oneOfPerm } = route;
let visible = true;
if (requiredPermissions) {
if (requiredPermissions && userPermission) {
visible = auth({ requiredPermissions, oneOfPerm }, userPermission);
}
@@ -172,9 +254,10 @@ const useRoute = (userPermission): [IRoute[], string] => {
const [permissionRoute, setPermissionRoute] = useState(routes);
useEffect(() => {
const newRoutes = filterRoute(routes);
const roleFiltered = filterRoutesByRole(routes, roleCtx);
const newRoutes = filterRoute(roleFiltered);
setPermissionRoute(newRoutes);
}, [JSON.stringify(userPermission)]);
}, [JSON.stringify(userPermission), JSON.stringify(roleCtx)]);
const defaultRoute = useMemo(() => {
const first = permissionRoute[0];