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

14 KiB
Raw Blame History

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

🎯 业务场景

需求:一个智能体(如"校园助手"),不同部门的用户访问时,只能查询到本部门及公共的知识库。

示例

  • 教务处用户:可访问"教务知识库" + "公共知识库"
  • 财务处用户:可访问"财务知识库" + "公共知识库"
  • 学生:只能访问"公共知识库"

📋 实现方案(推荐)

方案架构

用户请求
    ↓
1. 获取用户部门和角色
    ↓
2. 查询有权限的知识库列表(已实现✅)
    ↓
3. 在Dify对话时动态指定知识库
    ↓
4. 返回结果(只包含授权知识库的内容)

🔧 技术实现

1. 知识库分类(数据库层)

1.1 创建知识库时设置权限

@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 权限类型枚举

public enum KnowledgePermissionType {
    PUBLIC,              // 公开(所有人可读)
    DEPARTMENT,          // 部门(本部门可读写)
    DEPARTMENT_INHERIT,  // 部门继承(本部门及子部门可读)
    ROLE,                // 角色(特定角色跨部门可读)
    PRIVATE              // 私有(特定部门+角色)
}

2. 对话时动态过滤知识库Service层

@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请求参数

@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调用示例

POST /v1/chat-messages
{
  "query": "如何申请奖学金?",
  "conversation_id": "conv-123",
  "user": "user-001",
  "datasets": [
    "dataset-edu-001",    // 教务知识库
    "dataset-public-001"  // 公共知识库
  ],
  "stream": true
}

4. 前端展示(可选)

可以在前端显示用户当前可访问的知识库:

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. 知识库选择器

<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回答中标注知识来源

{
  "answer": "申请奖学金需要...",
  "sources": [
    {
      "knowledge_id": "kb-001",
      "knowledge_title": "奖学金管理办法",
      "department": "教务处",
      "snippet": "第三条..."
    }
  ]
}

性能优化

1. 缓存用户可访问的知识库

// 注意缓存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

// 登录时
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跨部门协作知识库

// 教务处和学工处共享的"学生管理"知识库
createKnowledge("学生管理知识库", Arrays.asList(
    new Permission("dept_academic", null, true, false, false),
    new Permission("dept_student", null, true, false, false)
));

场景2角色知识库跨部门

// 所有"教师"角色可访问(不限部门)
createKnowledge("教师手册", Arrays.asList(
    new Permission(null, "teacher", true, false, false)
));

场景3临时授权

// 给特定用户临时授权
@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);
}

📝 实现清单

已完成

  • 数据库表设计creator_dept字段
  • 权限表设计tb_resource_permission
  • Mapper权限过滤selectAiKnowledges

🔄 需要实现

  • KnowledgePermissionType枚举
  • 创建知识库时的权限创建逻辑
  • 对话时的知识库过滤逻辑
  • Dify API Client支持datasets参数
  • 前端知识库选择器
  • Redis缓存优化

🎯 总结

核心思路

  1. 数据库层:通过tb_resource_permission控制知识库访问权限(已实现
  2. 应用层:对话时根据用户权限动态查询可用知识库
  3. Dify层通过API的datasets参数限制检索范围

优势

  • 灵活:支持公开、部门、角色、私有等多种权限模型
  • 安全:数据库层权限控制,无法绕过
  • 性能利用dept_path支持部门继承查询高效
  • 可扩展:可以轻松添加新的权限类型

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