485 lines
14 KiB
Markdown
485 lines
14 KiB
Markdown
# 智能体中实现部门知识库隔离方案
|
||
|
||
## 🎯 业务场景
|
||
|
||
**需求**:一个智能体(如"校园助手"),不同部门的用户访问时,只能查询到本部门及公共的知识库。
|
||
|
||
**示例**:
|
||
- 教务处用户:可访问"教务知识库" + "公共知识库"
|
||
- 财务处用户:可访问"财务知识库" + "公共知识库"
|
||
- 学生:只能访问"公共知识库"
|
||
|
||
---
|
||
|
||
## 📋 实现方案(推荐)
|
||
|
||
### 方案架构
|
||
|
||
```
|
||
用户请求
|
||
↓
|
||
1. 获取用户部门和角色
|
||
↓
|
||
2. 查询有权限的知识库列表(已实现✅)
|
||
↓
|
||
3. 在Dify对话时动态指定知识库
|
||
↓
|
||
4. 返回结果(只包含授权知识库的内容)
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 技术实现
|
||
|
||
### 1. 知识库分类(数据库层)
|
||
|
||
#### 1.1 创建知识库时设置权限
|
||
|
||
```java
|
||
@Service
|
||
public class AiKnowledgeServiceImpl {
|
||
|
||
@Transactional
|
||
public ResultDomain<TbAiKnowledge> createKnowledge(
|
||
TbAiKnowledge knowledge,
|
||
KnowledgePermissionType permissionType) {
|
||
|
||
// 1. 获取当前登录用户信息(通过LoginUtil)⭐
|
||
TbSysUser currentUser = LoginUtil.getCurrentUser();
|
||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||
String deptId = userDeptRoles.isEmpty() ? null : userDeptRoles.get(0).getDeptID();
|
||
|
||
// 2. 保存知识库
|
||
knowledge.setCreator(currentUser.getId());
|
||
knowledge.setCreatorDept(deptId);
|
||
knowledgeMapper.insert(knowledge);
|
||
|
||
// 3. 根据权限类型创建权限记录
|
||
switch (permissionType) {
|
||
case PUBLIC:
|
||
// 公开知识库:所有人可读
|
||
createPublicPermission(knowledge.getId());
|
||
break;
|
||
|
||
case DEPARTMENT:
|
||
// 部门知识库:本部门所有人可读写
|
||
createDepartmentPermission(knowledge.getId(), deptId);
|
||
break;
|
||
|
||
case DEPARTMENT_INHERIT:
|
||
// 部门继承:本部门及子部门可读
|
||
createDepartmentInheritPermission(knowledge.getId(), deptId);
|
||
break;
|
||
|
||
case ROLE:
|
||
// 角色知识库:特定角色可读(跨部门)
|
||
createRolePermission(knowledge.getId(), roleIds);
|
||
break;
|
||
|
||
case PRIVATE:
|
||
// 私有知识库:仅创建者所在部门的特定角色
|
||
createPrivatePermission(knowledge.getId(), deptId, roleIds);
|
||
break;
|
||
}
|
||
|
||
return ResultDomain.success(knowledge);
|
||
}
|
||
|
||
// 创建公开权限
|
||
private void createPublicPermission(String knowledgeId) {
|
||
TbResourcePermission permission = new TbResourcePermission();
|
||
permission.setId(UUID.randomUUID().toString());
|
||
permission.setResourceType(10); // AI_KNOWLEDGE
|
||
permission.setResourceId(knowledgeId);
|
||
permission.setDeptId(null); // NULL表示不限部门
|
||
permission.setRoleId(null); // NULL表示不限角色
|
||
permission.setCanRead(true);
|
||
permission.setCanWrite(false);
|
||
permission.setCanExecute(false);
|
||
resourcePermissionMapper.insert(permission);
|
||
}
|
||
|
||
// 创建部门权限
|
||
private void createDepartmentPermission(String knowledgeId, String deptId) {
|
||
TbResourcePermission permission = new TbResourcePermission();
|
||
permission.setId(UUID.randomUUID().toString());
|
||
permission.setResourceType(10);
|
||
permission.setResourceId(knowledgeId);
|
||
permission.setDeptId(deptId); // 指定部门
|
||
permission.setRoleId(null); // 部门内所有角色
|
||
permission.setCanRead(true);
|
||
permission.setCanWrite(true); // 部门成员可编辑
|
||
permission.setCanExecute(false);
|
||
resourcePermissionMapper.insert(permission);
|
||
}
|
||
|
||
// 创建部门继承权限(利用dept_path)
|
||
private void createDepartmentInheritPermission(String knowledgeId, String deptId) {
|
||
// 查询部门信息
|
||
TbSysDept dept = deptMapper.selectById(deptId);
|
||
|
||
// 为本部门创建权限(已通过dept_path自动继承给子部门)
|
||
TbResourcePermission permission = new TbResourcePermission();
|
||
permission.setId(UUID.randomUUID().toString());
|
||
permission.setResourceType(10);
|
||
permission.setResourceId(knowledgeId);
|
||
permission.setDeptId(deptId);
|
||
permission.setRoleId(null);
|
||
permission.setCanRead(true);
|
||
permission.setCanWrite(false); // 子部门只读
|
||
permission.setCanExecute(false);
|
||
resourcePermissionMapper.insert(permission);
|
||
|
||
// dept_path机制会自动让子部门继承此权限
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 1.2 权限类型枚举
|
||
|
||
```java
|
||
public enum KnowledgePermissionType {
|
||
PUBLIC, // 公开(所有人可读)
|
||
DEPARTMENT, // 部门(本部门可读写)
|
||
DEPARTMENT_INHERIT, // 部门继承(本部门及子部门可读)
|
||
ROLE, // 角色(特定角色跨部门可读)
|
||
PRIVATE // 私有(特定部门+角色)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 2. 对话时动态过滤知识库(Service层)
|
||
|
||
```java
|
||
@Service
|
||
public class AiChatServiceImpl {
|
||
|
||
@Autowired
|
||
private AiKnowledgeMapper knowledgeMapper;
|
||
|
||
@Autowired
|
||
private DifyApiClient difyApiClient;
|
||
|
||
/**
|
||
* 流式对话(带知识库隔离)
|
||
*/
|
||
public void streamChat(
|
||
String message,
|
||
String conversationId,
|
||
String userId,
|
||
SseEmitter emitter) {
|
||
|
||
// 1. 获取当前登录用户的部门角色信息(通过LoginUtil)⭐
|
||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||
|
||
// 2. 查询用户有权限的知识库列表(自动权限过滤✅)
|
||
TbAiKnowledge filter = new TbAiKnowledge();
|
||
filter.setStatus(1); // 只查询启用的知识库
|
||
|
||
List<TbAiKnowledge> availableKnowledges = knowledgeMapper.selectAiKnowledges(
|
||
filter,
|
||
userDeptRoles // 自动根据用户部门角色过滤
|
||
);
|
||
|
||
// 3. 提取Dify Dataset IDs
|
||
List<String> datasetIds = availableKnowledges.stream()
|
||
.map(TbAiKnowledge::getDifyDatasetId)
|
||
.filter(Objects::nonNull)
|
||
.collect(Collectors.toList());
|
||
|
||
// 4. 调用Dify API(指定知识库列表)
|
||
DifyChatRequest request = DifyChatRequest.builder()
|
||
.query(message)
|
||
.conversationId(conversationId)
|
||
.user(userId)
|
||
.datasets(datasetIds) // ⭐ 动态指定知识库
|
||
.stream(true)
|
||
.build();
|
||
|
||
// 5. 流式响应
|
||
difyApiClient.streamChat(request, new StreamCallback() {
|
||
@Override
|
||
public void onChunk(String chunk) {
|
||
emitter.send(chunk);
|
||
}
|
||
|
||
@Override
|
||
public void onComplete(DifyChatResponse response) {
|
||
// 保存消息记录
|
||
saveMessage(conversationId, userId, message, response);
|
||
emitter.complete();
|
||
}
|
||
|
||
@Override
|
||
public void onError(Throwable error) {
|
||
emitter.completeWithError(error);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3. Dify API请求参数
|
||
|
||
```java
|
||
@Data
|
||
@Builder
|
||
public class DifyChatRequest {
|
||
private String query; // 用户问题
|
||
private String conversationId; // 会话ID
|
||
private String user; // 用户ID
|
||
|
||
@JsonProperty("datasets")
|
||
private List<String> datasets; // ⭐ 指定知识库ID列表
|
||
|
||
private Boolean stream; // 是否流式
|
||
private Map<String, Object> inputs; // 额外输入
|
||
}
|
||
```
|
||
|
||
**Dify API调用示例:**
|
||
```json
|
||
POST /v1/chat-messages
|
||
{
|
||
"query": "如何申请奖学金?",
|
||
"conversation_id": "conv-123",
|
||
"user": "user-001",
|
||
"datasets": [
|
||
"dataset-edu-001", // 教务知识库
|
||
"dataset-public-001" // 公共知识库
|
||
],
|
||
"stream": true
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 4. 前端展示(可选)
|
||
|
||
可以在前端显示用户当前可访问的知识库:
|
||
|
||
```typescript
|
||
interface ChatPageState {
|
||
availableKnowledges: Knowledge[]; // 用户可访问的知识库
|
||
selectedKnowledges: string[]; // 用户选择的知识库(可多选)
|
||
}
|
||
|
||
// 用户可以手动选择使用哪些知识库
|
||
async function sendMessage(message: string) {
|
||
const response = await axios.post('/ai/chat', {
|
||
message,
|
||
conversationId,
|
||
knowledgeIds: selectedKnowledges // 从可用列表中选择
|
||
});
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔐 权限控制流程
|
||
|
||
### 创建知识库流程
|
||
|
||
```
|
||
1. 用户创建知识库
|
||
↓
|
||
2. 选择权限类型(公开/部门/角色/私有)
|
||
↓
|
||
3. 系统创建知识库记录
|
||
↓
|
||
4. 自动创建权限记录(tb_resource_permission)
|
||
↓
|
||
5. 同步到Dify(创建Dataset)
|
||
```
|
||
|
||
### 对话查询流程
|
||
|
||
```
|
||
1. 用户发起对话
|
||
↓
|
||
2. 获取用户部门角色(UserDeptRoleVO)
|
||
↓
|
||
3. 查询有权限的知识库(Mapper自动过滤)
|
||
↓
|
||
4. 提取Dify Dataset IDs
|
||
↓
|
||
5. 调用Dify API(指定datasets参数)
|
||
↓
|
||
6. Dify只从指定知识库中检索
|
||
↓
|
||
7. 返回结果
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 权限矩阵示例
|
||
|
||
| 知识库 | 类型 | 教务处-管理员 | 教务处-教师 | 财务处-管理员 | 学生 |
|
||
|--------|------|---------------|-------------|---------------|------|
|
||
| 公共知识库 | PUBLIC | ✅ 读 | ✅ 读 | ✅ 读 | ✅ 读 |
|
||
| 教务知识库 | DEPARTMENT | ✅ 读写 | ✅ 读写 | ❌ | ❌ |
|
||
| 财务知识库 | DEPARTMENT | ❌ | ❌ | ✅ 读写 | ❌ |
|
||
| 教师手册 | ROLE | ✅ 读 | ✅ 读 | ❌ | ❌ |
|
||
| 管理规范 | PRIVATE | ✅ 读写执行 | ❌ | ✅ 读写执行 | ❌ |
|
||
|
||
---
|
||
|
||
## 🎨 前端交互优化
|
||
|
||
### 1. 知识库选择器
|
||
|
||
```vue
|
||
<template>
|
||
<div class="knowledge-selector">
|
||
<h3>可用知识库</h3>
|
||
<el-checkbox-group v-model="selectedKnowledges">
|
||
<el-checkbox
|
||
v-for="kb in availableKnowledges"
|
||
:key="kb.id"
|
||
:label="kb.id">
|
||
{{ kb.title }}
|
||
<el-tag size="small">{{ kb.category }}</el-tag>
|
||
</el-checkbox>
|
||
</el-checkbox-group>
|
||
</div>
|
||
</template>
|
||
```
|
||
|
||
### 2. 知识库来源标注
|
||
|
||
在AI回答中标注知识来源:
|
||
|
||
```json
|
||
{
|
||
"answer": "申请奖学金需要...",
|
||
"sources": [
|
||
{
|
||
"knowledge_id": "kb-001",
|
||
"knowledge_title": "奖学金管理办法",
|
||
"department": "教务处",
|
||
"snippet": "第三条..."
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## ⚡ 性能优化
|
||
|
||
### 1. 缓存用户可访问的知识库
|
||
|
||
```java
|
||
// 注意:缓存key应该使用用户ID + 部门角色组合,确保权限变更后缓存失效
|
||
@Cacheable(value = "user:knowledges", key = "#root.target.getCurrentUserCacheKey()")
|
||
public List<TbAiKnowledge> getUserAvailableKnowledges() {
|
||
// 通过LoginUtil获取当前用户的部门角色信息⭐
|
||
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
||
|
||
return knowledgeMapper.selectAiKnowledges(
|
||
new TbAiKnowledge(),
|
||
userDeptRoles
|
||
);
|
||
}
|
||
|
||
// 生成缓存key(包含用户ID和部门角色信息)
|
||
private String getCurrentUserCacheKey() {
|
||
TbSysUser user = LoginUtil.getCurrentUser();
|
||
List<UserDeptRoleVO> roles = LoginUtil.getCurrentDeptRole();
|
||
String roleIds = roles.stream()
|
||
.map(r -> r.getDeptID() + ":" + r.getRoleID())
|
||
.collect(Collectors.joining(","));
|
||
return user.getId() + ":" + roleIds;
|
||
}
|
||
```
|
||
|
||
### 2. 知识库预加载
|
||
|
||
在用户登录时预加载知识库列表,缓存到Redis:
|
||
|
||
```java
|
||
// 登录时
|
||
String cacheKey = "user:" + userId + ":knowledges";
|
||
List<String> datasetIds = getDatasetIds(userId);
|
||
redisTemplate.opsForValue().set(cacheKey, datasetIds, 1, TimeUnit.HOURS);
|
||
|
||
// 对话时直接使用
|
||
List<String> datasetIds = redisTemplate.opsForValue().get(cacheKey);
|
||
```
|
||
|
||
---
|
||
|
||
## 🔄 知识库共享场景
|
||
|
||
### 场景1:跨部门协作知识库
|
||
|
||
```java
|
||
// 教务处和学工处共享的"学生管理"知识库
|
||
createKnowledge("学生管理知识库", Arrays.asList(
|
||
new Permission("dept_academic", null, true, false, false),
|
||
new Permission("dept_student", null, true, false, false)
|
||
));
|
||
```
|
||
|
||
### 场景2:角色知识库(跨部门)
|
||
|
||
```java
|
||
// 所有"教师"角色可访问(不限部门)
|
||
createKnowledge("教师手册", Arrays.asList(
|
||
new Permission(null, "teacher", true, false, false)
|
||
));
|
||
```
|
||
|
||
### 场景3:临时授权
|
||
|
||
```java
|
||
// 给特定用户临时授权
|
||
@Transactional
|
||
public void grantTemporaryAccess(String knowledgeId, String userId, Integer hours) {
|
||
// 创建临时权限记录
|
||
TbResourcePermission permission = new TbResourcePermission();
|
||
permission.setResourceId(knowledgeId);
|
||
permission.setUserId(userId); // 扩展字段:用户级权限
|
||
permission.setExpireTime(LocalDateTime.now().plusHours(hours));
|
||
permissionMapper.insert(permission);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 实现清单
|
||
|
||
### ✅ 已完成
|
||
- [x] 数据库表设计(creator_dept字段)
|
||
- [x] 权限表设计(tb_resource_permission)
|
||
- [x] Mapper权限过滤(selectAiKnowledges)
|
||
|
||
### 🔄 需要实现
|
||
- [ ] KnowledgePermissionType枚举
|
||
- [ ] 创建知识库时的权限创建逻辑
|
||
- [ ] 对话时的知识库过滤逻辑
|
||
- [ ] Dify API Client(支持datasets参数)
|
||
- [ ] 前端知识库选择器
|
||
- [ ] Redis缓存优化
|
||
|
||
---
|
||
|
||
## 🎯 总结
|
||
|
||
**核心思路**:
|
||
1. **数据库层**:通过`tb_resource_permission`控制知识库访问权限(已实现✅)
|
||
2. **应用层**:对话时根据用户权限动态查询可用知识库
|
||
3. **Dify层**:通过API的`datasets`参数限制检索范围
|
||
|
||
**优势**:
|
||
- ✅ 灵活:支持公开、部门、角色、私有等多种权限模型
|
||
- ✅ 安全:数据库层权限控制,无法绕过
|
||
- ✅ 性能:利用dept_path支持部门继承,查询高效
|
||
- ✅ 可扩展:可以轻松添加新的权限类型
|
||
|
||
**这个方案充分利用了您现有的权限系统设计!** 🎉
|
||
|