diff --git a/app/hooks/use-area-dataset-config.ts b/app/hooks/use-area-dataset-config.ts index f964ca1..23b8bfe 100644 --- a/app/hooks/use-area-dataset-config.ts +++ b/app/hooks/use-area-dataset-config.ts @@ -222,7 +222,12 @@ export function useAreaDatasetConfig(): UseAreaDatasetConfigReturn { } } catch (error: any) { console.error('创建知识库绑定失败:', error); - message.error('创建失败,请稍后重试'); + // 检查是否为403权限不足错误 + if (error?.response?.status === 403 || error?.status === 403 || error?.code === 403) { + message.error('无权限操作:您没有创建知识库绑定的权限'); + } else { + message.error('创建失败,请稍后重试'); + } return false; } finally { setSubmitLoading(false); @@ -255,7 +260,12 @@ export function useAreaDatasetConfig(): UseAreaDatasetConfigReturn { } } catch (error: any) { console.error('更新知识库绑定失败:', error); - message.error('更新失败,请稍后重试'); + // 检查是否为403权限不足错误 + if (error?.response?.status === 403 || error?.status === 403 || error?.code === 403) { + message.error('无权限操作:您没有编辑知识库绑定的权限'); + } else { + message.error('更新失败,请稍后重试'); + } return false; } finally { setSubmitLoading(false); @@ -287,7 +297,12 @@ export function useAreaDatasetConfig(): UseAreaDatasetConfigReturn { } } catch (error: any) { console.error('删除知识库绑定失败:', error); - message.error('删除失败,请稍后重试'); + // 检查是否为403权限不足错误 + if (error?.response?.status === 403 || error?.status === 403 || error?.code === 403) { + message.error('无权限操作:您没有删除知识库绑定的权限'); + } else { + message.error('删除失败,请稍后重试'); + } return false; } }, diff --git a/app/routes/api.v3.dify.area-datasets.tsx b/app/routes/api.v3.dify.area-datasets.tsx index 74342b8..b3f7315 100644 --- a/app/routes/api.v3.dify.area-datasets.tsx +++ b/app/routes/api.v3.dify.area-datasets.tsx @@ -1,6 +1,10 @@ /** * GET /api/v3/dify/area-datasets - 获取所有知识库绑定列表(管理员) * POST /api/v3/dify/area-datasets - 创建知识库绑定 + * + * 权限说明: + * GET: @require_permission_v2("dify:dataset:manage") + * POST: @require_permission_v2("dify:dataset:manage") */ import { type LoaderFunctionArgs, json } from '@remix-run/node'; diff --git a/app/routes/role-permissions._index.tsx b/app/routes/role-permissions._index.tsx index 9588c6d..add48cc 100644 --- a/app/routes/role-permissions._index.tsx +++ b/app/routes/role-permissions._index.tsx @@ -945,10 +945,18 @@ export default function RolePermissions() { } }; + // ==================== 权限状态管理 ==================== + // 存储原始的、未映射的权限(用于保存时) + const [originalRoutePermissionsMap, setOriginalRoutePermissionsMap] = useState>(new Map()); + const [originalAllPermissions, setOriginalAllPermissions] = useState([]); + // 选择角色 const handleSelectRole = async (role: RoleInfo) => { setSelectedRole(role); + // 动态导入权限映射工具 + const { mapPermissions } = await import('~/utils/permission-mapper'); + // v3.0: 并行加载数据 const [routesResult, rolePermissions, users] = await Promise.all([ getRoleRoutesWithPermissions(role.id), @@ -958,26 +966,49 @@ export default function RolePermissions() { const { routes: routesWithPerms, selectedRouteIds: routeIds } = routesResult; - // 构建 routePermissionsMap:从返回的路由中提取每个路由的可用 permissions - 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) { - permMap.set(route.id, route.permissions); + 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 + // 存储原始权限 + setOriginalRoutePermissionsMap(originalPermMap); + setOriginalAllPermissions(allOriginalPerms); + + // 构建映射后的权限映射(用于显示) + 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); + + // 从 getRolePermissions 结果中提取已分配的权限ID(原始ID) const assignedPermissionIds = rolePermissions.map(p => p.permission_id); - setRoutePermissionsMap(permMap); + // 存储状态 + 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 new file mode 100644 index 0000000..7f747b7 --- /dev/null +++ b/app/utils/permission-mapper.ts @@ -0,0 +1,150 @@ +/** + * 权限键映射工具 + * 用于处理数据库权限键与路由装饰器检查权限键不一致的情况 + */ + +/** + * 权限键映射表 + * key: 数据库中的权限键 + * value: 前端显示和实际生效的权限键 + * + * 当前配置:不做映射,直接显示数据库权限键 + * 因为权限已经细分为CRUD操作,不需要合并显示 + */ +const PERMISSION_KEY_MAP: Record = { + // 知识库绑定相关 - 直接显示数据库权限键,不映射 + // 'dify:bind:list': 'dify:bind:list', + // 'dify:bind:create': 'dify:bind:create', + // 'dify:bind:update': 'dify:bind:update', + // 'dify:bind:delete': 'dify:bind:delete', +}; + +/** + * 反向映射表:实际权限键 -> 数据库权限键列表 + * 当前配置:不进行反向映射 + */ +const REVERSE_PERMISSION_MAP: Record = { + // 'dify:bind:list': ['dify:bind:list'], +}; + +/** + * 将数据库权限键转换为显示权限键 + * @param permissionKey 数据库中的权限键 + * @returns 前端显示的权限键(无映射时返回原值) + */ +export function mapPermissionKey(permissionKey: string): string { + return PERMISSION_KEY_MAP[permissionKey] || permissionKey; +} + +/** + * 批量转换权限键列表 + * @param permissionKeys 权限键数组 + * @returns 转换后的权限键数组(去重) + */ +export function mapPermissionKeys(permissionKeys: string[]): string[] { + const mappedKeys = permissionKeys.map(key => mapPermissionKey(key)); + return [...new Set(mappedKeys)]; // 去重 +} + +/** + * 反向查找:根据显示权限键找到对应的数据库权限键 + * @param effectivePermissionKey 显示权限键 + * @returns 对应的数据库权限键列表(无映射时返回原值) + */ +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]; +} + +/** + * 转换权限对象:将数据库权限对象转换为显示权限对象 + */ +export interface Permission { + id: number; + permission_key: string; + display_name: string; + api_method?: string; + api_path?: string; + [key: string]: any; +} + +export function mapPermission(permission: Permission): Permission { + const mappedKey = mapPermissionKey(permission.permission_key); + + // 如果权限键被映射,更新显示名称(可根据业务逻辑调整) + let displayName = permission.display_name; + if (mappedKey !== permission.permission_key) { + // 这里可以添加自定义的显示名称映射逻辑 + } + + return { + ...permission, + permission_key: mappedKey, + display_name: displayName, + }; +} + +/** + * 批量转换权限列表 + * @param permissions 权限对象数组 + * @returns 转换后的权限对象数组(去重) + */ +export function mapPermissions(permissions: Permission[]): Permission[] { + const mappedMap = new Map(); + + permissions.forEach(permission => { + const mapped = mapPermission(permission); + + // 如果映射后的权限键已存在,合并(保留第一个) + if (!mappedMap.has(mapped.permission_key)) { + mappedMap.set(mapped.permission_key, mapped); + } + }); + + return Array.from(mappedMap.values()); +} + +/** + * 根据显示的权限ID查找对应的数据库权限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); + + 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; +} + +/** + * 检查权限是否受映射影响 + */ +export function hasPermissionMapping(permissionKey: string): boolean { + return PERMISSION_KEY_MAP[permissionKey] !== undefined; +} + +/** + * 获取权限映射的说明信息 + */ +export function getPermissionMappingInfo(): string { + return `权限映射说明:\n` + + `当前未启用权限键映射,直接使用数据库权限键`; +}