feat(auth): 为数据管理和RAG服务增加资源访问控制

- 在DatasetApplicationService中注入ResourceAccessService并添加所有权验证
- 在KnowledgeSetApplicationService中注入ResourceAccessService并添加所有权验证
- 修改DatasetRepository接口和实现类,增加按创建者过滤的方法
- 修改KnowledgeSetRepository接口和实现类,增加按创建者过滤的方法
- 在RAG索引器服务中添加知识库访问权限检查和作用域过滤
- 更新实体元对象处理器以使用请求用户上下文获取当前用户
- 在前端设置页面添加用户权限管理功能和角色权限控制
- 为Python标注服务增加用户上下文和数据集访问权限验证
This commit is contained in:
2026-02-06 14:58:46 +08:00
parent 056cee11cc
commit 6a4c4ae3d7
28 changed files with 1063 additions and 158 deletions

View File

@@ -1,12 +1,51 @@
import { useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { Menu } from "antd";
import { SettingOutlined } from "@ant-design/icons";
import { SettingOutlined, TeamOutlined } from "@ant-design/icons";
import { Component } from "lucide-react";
import SystemConfig from "./SystemConfig";
import ModelAccess from "./ModelAccess";
import UserPermissionManagement from "./UserPermissionManagement";
import { useAppSelector } from "@/store/hooks";
import { hasPermission, PermissionCodes } from "@/auth/permissions";
export default function SettingsPage() {
const [activeTab, setActiveTab] = useState("model-access");
const permissions = useAppSelector((state) => state.auth.permissions);
const canManageUsers = hasPermission(permissions, PermissionCodes.userManage);
const canViewRoles = hasPermission(permissions, PermissionCodes.roleManage);
const canViewPermissions = hasPermission(
permissions,
PermissionCodes.permissionManage
);
const tabs = useMemo(() => {
const nextTabs = [
{
key: "model-access",
icon: <Component className="w-4 h-4" />,
label: "模型接入",
},
{
key: "system-config",
icon: <SettingOutlined />,
label: "参数配置",
},
];
if (canManageUsers || canViewRoles || canViewPermissions) {
nextTabs.push({
key: "user-permission",
icon: <TeamOutlined />,
label: "用户与权限",
});
}
return nextTabs;
}, [canManageUsers, canViewPermissions, canViewRoles]);
const [activeTab, setActiveTab] = useState<string>(tabs[0]?.key ?? "model-access");
useEffect(() => {
const hasActiveTab = tabs.some((tab) => tab.key === activeTab);
if (!hasActiveTab && tabs.length > 0) {
setActiveTab(tabs[0].key);
}
}, [activeTab, tabs]);
return (
<div className="h-screen flex">
@@ -18,21 +57,10 @@ export default function SettingsPage() {
<div className="h-full">
<Menu
mode="inline"
items={[
{
key: "model-access",
icon: <Component className="w-4 h-4" />,
label: "模型接入",
},
{
key: "system-config",
icon: <SettingOutlined />,
label: "参数配置",
},
]}
items={tabs}
selectedKeys={[activeTab]}
onClick={({ key }) => {
setActiveTab(key);
setActiveTab(String(key));
}}
/>
</div>
@@ -41,6 +69,13 @@ export default function SettingsPage() {
{/* 内容区域,根据 activeTab 渲染不同的组件 */}
{activeTab === "system-config" && <SystemConfig />}
{activeTab === "model-access" && <ModelAccess />}
{activeTab === "user-permission" && (
<UserPermissionManagement
canManageUsers={canManageUsers}
canViewRoles={canViewRoles}
canViewPermissions={canViewPermissions}
/>
)}
</div>
</div>
);

View File

@@ -0,0 +1,321 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import {
Button,
Card,
Empty,
message,
Modal,
Select,
Space,
Table,
Tag,
Typography,
} from "antd";
import type { ColumnsType } from "antd/es/table";
import {
assignUserRolesUsingPut,
listAuthPermissionsUsingGet,
listAuthRolesUsingGet,
listAuthUsersUsingGet,
} from "./settings.apis";
import type {
AuthPermissionInfo,
AuthRoleInfo,
AuthUserWithRoles,
} from "./settings.apis";
interface ApiResponse<T> {
code: string;
message: string;
data: T;
}
interface UserPermissionManagementProps {
canManageUsers: boolean;
canViewRoles: boolean;
canViewPermissions: boolean;
}
export default function UserPermissionManagement({
canManageUsers,
canViewRoles,
canViewPermissions,
}: UserPermissionManagementProps) {
const [loading, setLoading] = useState(false);
const [users, setUsers] = useState<AuthUserWithRoles[]>([]);
const [roles, setRoles] = useState<AuthRoleInfo[]>([]);
const [permissions, setPermissions] = useState<AuthPermissionInfo[]>([]);
const [editingUser, setEditingUser] = useState<AuthUserWithRoles | null>(null);
const [selectedRoleCodes, setSelectedRoleCodes] = useState<string[]>([]);
const [submitting, setSubmitting] = useState(false);
const canShowAnything = canManageUsers || canViewRoles || canViewPermissions;
const canAssignRoles = canManageUsers && roles.length > 0;
const roleNameMap = useMemo(
() => new Map(roles.map((role) => [role.roleCode, role.roleName || role.roleCode])),
[roles]
);
const roleCodeToIdMap = useMemo(
() => new Map(roles.map((role) => [role.roleCode, role.id])),
[roles]
);
const loadData = useCallback(async () => {
setLoading(true);
try {
const requestTasks: Array<Promise<unknown>> = [];
if (canManageUsers || canViewRoles || canViewPermissions) {
requestTasks.push(listAuthUsersUsingGet());
}
if (canManageUsers || canViewRoles) {
requestTasks.push(listAuthRolesUsingGet());
}
if (canViewPermissions) {
requestTasks.push(listAuthPermissionsUsingGet());
}
const responses = await Promise.all(requestTasks);
let index = 0;
if (canManageUsers || canViewRoles || canViewPermissions) {
const userResponse = responses[index++] as ApiResponse<AuthUserWithRoles[]>;
setUsers(userResponse?.data ?? []);
}
if (canManageUsers || canViewRoles) {
const roleResponse = responses[index++] as ApiResponse<AuthRoleInfo[]>;
setRoles(roleResponse?.data ?? []);
} else {
setRoles([]);
}
if (canViewPermissions) {
const permissionResponse = responses[index++] as ApiResponse<AuthPermissionInfo[]>;
setPermissions(permissionResponse?.data ?? []);
} else {
setPermissions([]);
}
} catch (error) {
message.error("加载用户权限信息失败");
console.error("加载用户权限信息失败:", error);
} finally {
setLoading(false);
}
}, [canManageUsers, canViewPermissions, canViewRoles]);
useEffect(() => {
if (!canShowAnything) {
return;
}
void loadData();
}, [canShowAnything, loadData]);
const userColumns: ColumnsType<AuthUserWithRoles> = [
{
title: "用户名",
dataIndex: "username",
key: "username",
width: 180,
},
{
title: "姓名",
dataIndex: "fullName",
key: "fullName",
width: 180,
render: (value?: string) => value || "-",
},
{
title: "邮箱",
dataIndex: "email",
key: "email",
render: (value?: string) => value || "-",
},
{
title: "状态",
dataIndex: "enabled",
key: "enabled",
width: 120,
render: (enabled?: boolean) =>
enabled ? <Tag color="green"></Tag> : <Tag color="default"></Tag>,
},
{
title: "角色",
dataIndex: "roleCodes",
key: "roleCodes",
render: (roleCodes: string[]) => (
<Space wrap>
{(roleCodes ?? []).map((roleCode) => (
<Tag key={roleCode}>{roleNameMap.get(roleCode) || roleCode}</Tag>
))}
</Space>
),
},
{
title: "操作",
key: "actions",
width: 120,
render: (_, record) => (
<Button
type="link"
disabled={!canAssignRoles}
onClick={() => {
setEditingUser(record);
setSelectedRoleCodes(record.roleCodes ?? []);
}}
>
</Button>
),
},
];
const roleColumns: ColumnsType<AuthRoleInfo> = [
{ title: "角色编码", dataIndex: "roleCode", key: "roleCode", width: 220 },
{ title: "角色名称", dataIndex: "roleName", key: "roleName", width: 180 },
{
title: "状态",
dataIndex: "enabled",
key: "enabled",
width: 120,
render: (enabled?: boolean) =>
enabled ? <Tag color="green"></Tag> : <Tag color="default"></Tag>,
},
{
title: "描述",
dataIndex: "description",
key: "description",
render: (value?: string) => value || "-",
},
];
const permissionColumns: ColumnsType<AuthPermissionInfo> = [
{
title: "权限编码",
dataIndex: "permissionCode",
key: "permissionCode",
width: 260,
},
{
title: "权限名称",
dataIndex: "permissionName",
key: "permissionName",
width: 200,
},
{
title: "模块",
dataIndex: "module",
key: "module",
width: 140,
render: (value?: string) => value || "-",
},
{
title: "动作",
dataIndex: "action",
key: "action",
width: 120,
render: (value?: string) => value || "-",
},
{
title: "接口",
key: "api",
render: (_, record) =>
record.pathPattern ? `${record.method || "ALL"} ${record.pathPattern}` : "-",
},
];
const handleAssignRoles = async () => {
if (!editingUser) {
return;
}
if (selectedRoleCodes.length === 0) {
message.warning("请至少选择一个角色");
return;
}
const roleIds = selectedRoleCodes
.map((roleCode) => roleCodeToIdMap.get(roleCode))
.filter((roleId): roleId is string => Boolean(roleId));
if (roleIds.length !== selectedRoleCodes.length) {
message.error("角色映射失败,请刷新后重试");
return;
}
setSubmitting(true);
try {
await assignUserRolesUsingPut(editingUser.id, roleIds);
message.success("角色分配成功");
setEditingUser(null);
setSelectedRoleCodes([]);
await loadData();
} catch (error) {
message.error("角色分配失败");
console.error("角色分配失败:", error);
} finally {
setSubmitting(false);
}
};
if (!canShowAnything) {
return <Empty description="当前账号无用户与权限管理权限" />;
}
return (
<Space direction="vertical" size={16} className="w-full">
<Card title="用户管理">
<Table
loading={loading}
rowKey="id"
dataSource={users}
columns={userColumns}
pagination={{ pageSize: 10, showSizeChanger: false }}
/>
</Card>
{canViewRoles && (
<Card title="角色列表">
<Table
loading={loading}
rowKey="id"
dataSource={roles}
columns={roleColumns}
pagination={{ pageSize: 8, showSizeChanger: false }}
/>
</Card>
)}
{canViewPermissions && (
<Card title="权限列表">
<Table
loading={loading}
rowKey="id"
dataSource={permissions}
columns={permissionColumns}
pagination={{ pageSize: 10, showSizeChanger: false }}
/>
</Card>
)}
<Modal
title={`分配角色 - ${editingUser?.username || ""}`}
open={Boolean(editingUser)}
confirmLoading={submitting}
onOk={() => {
void handleAssignRoles();
}}
onCancel={() => {
setEditingUser(null);
setSelectedRoleCodes([]);
}}
>
{roles.length === 0 ? (
<Typography.Text type="secondary"></Typography.Text>
) : (
<Select
mode="multiple"
className="w-full"
placeholder="请选择角色"
value={selectedRoleCodes}
onChange={(values) => setSelectedRoleCodes(values)}
options={roles.map((role) => ({
value: role.roleCode,
label: `${role.roleName} (${role.roleCode})`,
}))}
/>
)}
</Modal>
</Space>
);
}

View File

@@ -1,11 +1,11 @@
import { get, post, put, del } from "@/utils/request";
// 模型相关接口
export function queryModelProvidersUsingGet(params?: any) {
export function queryModelProvidersUsingGet(params?: Record<string, unknown>) {
return get("/api/models/providers", params);
}
export function queryModelListUsingGet(data: any) {
export function queryModelListUsingGet(data: Record<string, unknown>) {
return get("/api/models/list", data);
}
@@ -15,12 +15,12 @@ export function queryModelDetailByIdUsingGet(id: string | number) {
export function updateModelByIdUsingPut(
id: string | number,
data: any
data: Record<string, unknown>
) {
return put(`/api/models/${id}`, data);
}
export function createModelUsingPost(data: any) {
export function createModelUsingPost(data: Record<string, unknown>) {
return post("/api/models/create", data);
}
@@ -28,13 +28,60 @@ export function deleteModelByIdUsingDelete(id: string | number) {
return del(`/api/models/${id}`);
}
// 获取系统参数列表
export function getSysParamList() {
return get('/api/sys-param/list');
return get("/api/sys-param/list");
}
// 更新系统参数值
export const updateSysParamValue = async (params: { id: string; paramValue: string }) => {
export const updateSysParamValue = async (params: {
id: string;
paramValue: string;
}) => {
return put(`/api/sys-param/${params.id}`, params);
};
};
export interface AuthUserWithRoles {
id: number;
username: string;
fullName?: string;
email?: string;
enabled?: boolean;
roleCodes: string[];
}
export interface AuthRoleInfo {
id: string;
roleCode: string;
roleName: string;
description?: string;
enabled?: boolean;
}
export interface AuthPermissionInfo {
id: string;
permissionCode: string;
permissionName: string;
module?: string;
action?: string;
pathPattern?: string;
method?: string;
enabled?: boolean;
}
// 用户与权限管理接口
export function listAuthUsersUsingGet() {
return get("/api/auth/users");
}
export function listAuthRolesUsingGet() {
return get("/api/auth/roles");
}
export function listAuthPermissionsUsingGet() {
return get("/api/auth/permissions");
}
export function assignUserRolesUsingPut(userId: number, roleIds: string[]) {
return put(`/api/auth/users/${userId}/roles`, { roleIds });
}