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