Files
schoolNews/schoolNewsServ/ai/知识库隔离方案.md
2025-11-04 18:49:37 +08:00

485 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 智能体中实现部门知识库隔离方案
## 🎯 业务场景
**需求**:一个智能体(如"校园助手"),不同部门的用户访问时,只能查询到本部门及公共的知识库。
**示例**
- 教务处用户:可访问"教务知识库" + "公共知识库"
- 财务处用户:可访问"财务知识库" + "公共知识库"
- 学生:只能访问"公共知识库"
---
## 📋 实现方案(推荐)
### 方案架构
```
用户请求
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支持部门继承查询高效
- ✅ 可扩展:可以轻松添加新的权限类型
**这个方案充分利用了您现有的权限系统设计!** 🎉