记一次RAG知识库混乱与前端连环bug的救火之旅
开场:一颗定时炸弹
事情从一个看似简单的测试开始——
学生 QQ 里问”龙空灵喜欢什么?”,结果 RAG 检索到了龙空灵私人知识库里”爱玩 CS2、想吃 Java 实习”的内容。
私人知识库居然对学生可见。 这不叫知识库,这叫朋友圈。
第一关:全库检索,来者不拒
问题
检查 VectorStoreService 的 SQL:
1 | SELECT ... FROM document_chunk ORDER BY embedding_vec <=> ?::vector LIMIT ? |
没有 WHERE,没有 user_id,没有 kb_id。所有人敞开大门,随便搜。
解决
document_chunk加user_id列,迁移脚本回填存量数据VectorStoreService.similaritySearch()/keywordSearch()加权限过滤 SQL:
1 | WHERE (user_id = ? AND kb_id IS NULL) -- 私人文档 |
DocumentServiceImpl.searchRelevantContent()加userId参数,查shared_kb_member获取用户加入的共享库ChatController从 JWT 取userId传入
改完后 Web 端隔离完毕。但 OneBot 那路呢?
第二关:学生≠老师
问题
OneBot 学生 QQ 提问,没有登录态,传了个 null,还是全库检索。
最初脑子短路想建 QQ→userId 映射,把学生当用户。被纠正:知识库是老师上传的,学生提问不应该碰老师的私人库。
解决
建 ActiveTeacherService,Redis 存当前活跃老师:
1 | 老师前端操作 → ChatController → Redis: "rag:active_teacher" = userId (TTL 10min) |
切换老师立即覆盖,10 分钟没人刷新自动过期。
第三关:僵尸消息永动机
问题
日志反复刷 认领僵尸消息: count=1,每 30 秒一次。查代码发现 GradingStreamConsumer.claimPendingMessages() 里:
1 | stream.claim(group, consumerName, idleTime, TimeUnit.MILLISECONDS, msgId); |
claim 只是改了消息的 owner,但 consumeLoop 用 neverDelivered() 模式读不到已投递过的消息。于是消息在 Pending 队列里来回改姓但永远不被处理——Redis Stream 版孤儿怨。
解决
1 | Map<StreamMessageId, Map<String, String>> claimed = stream.claim(...); |
claim 返回被认领的消息数据,直接提交处理 → 处理完 ACK → 消息从 PEL 移除。
第四关:柱状图去哪了
问题
TaskDetail 页面的成绩分布 ECharts 柱状图怎么都不显示。Console 没报错、接口有数据、echarts 已安装。
排查
加诊断日志后发现 chartRef 容器找不到。
模板结构:
1 | <div v-if="loading">加载中...</div> |
renderChart() 在 try 块里用 $nextTick 调用时,loading 还是 true,v-if 优先级高于 v-else-if,chartRef 根本没渲染。
解决
1 | finally { |
Y 轴也加了 min: 0 + minInterval: 1,数据再少柱子也看得见。
第五关:新页签的各种毛病
需求
点”查看”按钮看学生提交的原文件。
选型
弹窗 vs 新页签——选了新页签,代码作业通常长,全屏舒坦。
踩坑
submissionId是undefined:后端TaskController没返回这个字段。补上si.put("submissionId", s.getId()),前端加空值兜底window.close()报错:Vue 模板里不能直接调window.close(),改成methods里的closeWindow()
今日战果
| 问题 | 根因 | 改了几处 |
|---|---|---|
| 私人KB对学生可见 | VectorStoreService 全表扫描 | 6 个文件 |
| 僵尸消息反复报警 | claim 返回值被丢弃 | 1 个文件 |
| OneBot 无身份 | 没有活跃教师机制 | 3 个文件 |
| 柱状图不显示 | v-if/v-else-if 顺序bug | 1 个文件 |
| 文件查看 | 新需求 | 4 个文件 |
| window.close 报错 | Vue模板限制 | 1 个文件 |
总计:16 个文件改动,0 次回滚到初始方案。
教训:写代码时觉得自己在造火箭,debug 时发现自己在拆盲盒。




