From d3418ef31bcbe0416748950b988344bd63c490a3 Mon Sep 17 00:00:00 2001 From: Wenyan Date: Mon, 8 Dec 2025 15:39:39 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=9D=83=E9=99=90?= =?UTF-8?q?=E6=98=A0=E5=B0=84=E7=9A=84=E5=8F=8D=E5=90=91=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:权限映射只在显示层面进行,但保存时还需要反向映射回数据库权限键 解决方案: 1. 分离显示权限和原始权限: - originalRoutePermissionsMap:存储未映射的原始权限(用于保存) - routePermissionsMap:存储映射后的权限(用于显示) - originalAllPermissions:存储所有原始权限的列表 2. 加载角色权限时: - 从API获取角色已分配的权限ID(原始ID) - 直接存储到 selectedPermissionIds - 不做任何映射转换 3. 显示权限列表时: - 从原始权限构建映射后的权限(合并相同的) - 用户看到的就是映射后的权限(如dify:dataset:manage) - 但勾选状态基于原始权限ID 4. 保存权限时: - 直接使用 selectedPermissionIds(原始ID) - 无需反向映射 验证方式: 1. 取消勾选 dify:dataset:manage → 数据库中4个bind权限被DENY → 接口返回403 2. 重新勾选 dify:dataset:manage → 数据库中4个bind权限被GRANT → 接口可访问 --- app/routes/role-permissions._index.tsx | 64 +++++++++++++++----------- app/utils/permission-mapper.ts | 56 ++++++++++++++++++++++ 2 files changed, 94 insertions(+), 26 deletions(-) diff --git a/app/routes/role-permissions._index.tsx b/app/routes/role-permissions._index.tsx index 96d40a9..add48cc 100644 --- a/app/routes/role-permissions._index.tsx +++ b/app/routes/role-permissions._index.tsx @@ -945,12 +945,17 @@ export default function RolePermissions() { } }; + // ==================== 权限状态管理 ==================== + // 存储原始的、未映射的权限(用于保存时) + const [originalRoutePermissionsMap, setOriginalRoutePermissionsMap] = useState>(new Map()); + const [originalAllPermissions, setOriginalAllPermissions] = useState([]); + // 选择角色 const handleSelectRole = async (role: RoleInfo) => { setSelectedRole(role); // 动态导入权限映射工具 - const { mapPermissions, mapPermissionKey, findDbPermissionKeys } = await import('~/utils/permission-mapper'); + const { mapPermissions } = await import('~/utils/permission-mapper'); // v3.0: 并行加载数据 const [routesResult, rolePermissions, users] = await Promise.all([ @@ -961,42 +966,49 @@ export default function RolePermissions() { const { routes: routesWithPerms, selectedRouteIds: routeIds } = routesResult; - // 构建 routePermissionsMap:从返回的路由中提取每个路由的可用 permissions - // 并应用权限键映射(dify:bind:* -> dify:dataset:*, dify:file:*) - const permMap = new Map(); - const extractPermissions = (routes: RouteInfo[]) => { + // 构建原始权限映射(未映射的,用于保存) + const originalPermMap = new Map(); + // 存储所有原始权限的列表 + const allOriginalPerms: ApiPermission[] = []; + const extractOriginalPermissions = (routes: RouteInfo[]) => { routes.forEach(route => { if (route.permissions && route.permissions.length > 0) { - // 应用权限键映射 - const mappedPermissions = mapPermissions(route.permissions); - permMap.set(route.id, mappedPermissions); + originalPermMap.set(route.id, route.permissions); + allOriginalPerms.push(...route.permissions); } if (route.children) { - extractPermissions(route.children); + extractOriginalPermissions(route.children); } }); }; - extractPermissions(routesWithPerms); + extractOriginalPermissions(routesWithPerms); - // 从 getRolePermissions 结果中提取已分配的权限ID - // 需要将数据库权限键映射为实际权限键进行对比 - const assignedPermissionIds = rolePermissions.map(p => { - // 查找该权限对应的所有数据库权限键 - const dbKeys = findDbPermissionKeys(p.permission_key); - // 如果使用映射,找到对应的权限ID - const mappedKey = mapPermissionKey(p.permission_key); + // 存储原始权限 + setOriginalRoutePermissionsMap(originalPermMap); + setOriginalAllPermissions(allOriginalPerms); - // 在 permMap 中查找对应权限的ID - for (const [routeId, permissions] of permMap.entries()) { - const foundPerm = permissions.find(perm => perm.permission_key === mappedKey); - if (foundPerm) return foundPerm.id; - } - return p.permission_id; - }); + // 构建映射后的权限映射(用于显示) + const displayPermMap = new Map(); + const extractDisplayPermissions = (routes: RouteInfo[]) => { + routes.forEach(route => { + if (route.permissions && route.permissions.length > 0) { + const mappedPermissions = mapPermissions(route.permissions); + displayPermMap.set(route.id, mappedPermissions); + } + if (route.children) { + extractDisplayPermissions(route.children); + } + }); + }; + extractDisplayPermissions(routesWithPerms); - setRoutePermissionsMap(permMap); + // 从 getRolePermissions 结果中提取已分配的权限ID(原始ID) + const assignedPermissionIds = rolePermissions.map(p => p.permission_id); + + // 存储状态 + setRoutePermissionsMap(displayPermMap); // 用于显示 setSelectedRouteIds(routeIds); - setSelectedPermissionIds(assignedPermissionIds); // 使用映射后的权限ID + setSelectedPermissionIds(assignedPermissionIds); // 使用原始权限ID setExpandedRouteIds([]); // 重置展开状态 setRoleUsers(users); }; diff --git a/app/utils/permission-mapper.ts b/app/utils/permission-mapper.ts index ea4ad8b..fd49873 100644 --- a/app/utils/permission-mapper.ts +++ b/app/utils/permission-mapper.ts @@ -122,6 +122,62 @@ export function mapPermissions(permissions: Permission[]): Permission[] { return Array.from(mappedMap.values()); } +/** + * 反向转换:将显示权限键转换为数据库权限键列表(用于保存权限) + * @param permissionKey 显示权限键(如 'dify:dataset:manage') + * @returns 对应的数据库权限键列表 + * + * @example + * reverseMapPermissionKey('dify:dataset:manage') + * // 返回 ['dify:bind:list', 'dify:bind:create', 'dify:bind:update', 'dify:bind:delete'] + * + * reverseMapPermissionKey('dify:file:read') + * // 返回 ['dify:file:read'](无映射) + */ +export function reverseMapPermissionKey(permissionKey: string): string[] { + // 查找所有映射到该权限键的原始权限键 + const originalKeys: string[] = []; + for (const [key, mappedKey] of Object.entries(PERMISSION_KEY_MAP)) { + if (mappedKey === permissionKey) { + originalKeys.push(key); + } + } + return originalKeys.length > 0 ? originalKeys : [permissionKey]; +} + +/** + * 根据显示的权限ID查找对应的数据库权限ID(用于保存) + * 这是一个复杂操作,因为前端显示的权限ID是经过映射的 + * + * @param displayPermissionId 前端显示的权限ID(映射后的) + * @param allMappedPermissions 所有映射后的权限列表(从路由获取的) + * @param originalPermissions 原始的权限列表(未映射的) + * @returns 对应的数据库权限ID列表 + */ +export function findDbPermissionIds( + displayPermissionId: number, + allMappedPermissions: Permission[], + originalPermissions: Permission[] +): number[] { + // 查找显示权限对应的显示权限对象 + const displayPerm = allMappedPermissions.find(p => p.id === displayPermissionId); + if (!displayPerm) return []; + + // 将显示权限键反向映射为数据库权限键列表 + const dbKeys = reverseMapPermissionKey(displayPerm.permission_key); + + // 在原始权限列表中查找这些数据库权限键对应的ID + const dbIds: number[] = []; + for (const key of dbKeys) { + const originalPerm = originalPermissions.find(p => p.permission_key === key); + if (originalPerm) { + dbIds.push(originalPerm.id); + } + } + + return dbIds; +} + /** * 检查权限是否受映射影响 * @param permissionKey 权限键