# 智能体中实现部门知识库隔离方案

## 🎯 业务场景

**需求**：一个智能体（如"校园助手"），不同部门的用户访问时，只能查询到本部门及公共的知识库。

**示例**：
- 教务处用户：可访问"教务知识库" + "公共知识库"
- 财务处用户：可访问"财务知识库" + "公共知识库"
- 学生：只能访问"公共知识库"

---

## 📋 实现方案（推荐）

### 方案架构

```
用户请求
    ↓
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支持部门继承，查询高效
- ✅ 可扩展：可以轻松添加新的权限类型

**这个方案充分利用了您现有的权限系统设计！** 🎉

