# 菌群知识并入 dan_knowledge_base 实施计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 将 809 篇菌群文章从 `health_knowledge_base` 并入 `dan_knowledge_base`,成为唯一知识条目表,管理端统一入口,langgraph 三源仍按 `metadata.source` 分流。 **Architecture:** `dan_knowledge_base` 增加 `source_type='article'` 标记(该字段已存在且 39 条案例全为 `'file'`,无 title 唯一约束,809 篇零冲突);后端 list 接口增加 `sourceType`/`excludeSourceType` 参数;langgraph `dan_knowledge` 源用 `excludeSourceType='article'` 排除、`microbiome` 源用 `sourceType='article'` 拉取;前端知识库管理页增强后删除健康知识库页。 **Tech Stack:** Spring Boot 2.7.18 + MyBatis-Plus(lambda wrapper)+ Vue 2.7 / Element UI + Python FastAPI / Chroma / httpx + MySQL 8.0.34 **Spec:** `docs/superpowers/specs/2026-10-20-knowledge-unify-design.md` **关键事实(已勘察确认):** - `dan_knowledge_base` 唯一键:仅 PRIMARY(id),`title varchar(200)`,`content mediumtext`,现有 39 条全 `source_type='file'` - 809 篇文章 title 全部 ≤ 200 字符(无截断风险) - `DanKnowledgeBaseService.list` 的 `wrapper.orderByAsc(getSort)` **无 id tie-break**——所有文章 sort=0,重建索引可能漏块(与旧 `microbiome_article` 分页 bug 同类) - `knowledge_sync` 首次延迟 120s、之后每 3600s 定时全量重建三源,容器重启即重置计时 ## Global Constraints - 代码在 `111.228.6.214:/app/cfc`(git cfclub 分支),部署按 AGENTS.md:备份 → 构建 → 部署 → 验证 → 失败回滚 - DB:`mysql -h mysql-internet-cn-north-1-23feae22680e4dfa.rds.jdcloud.com -P 3306 -u cfc -pcfc@1314 zxyj` - 后端 systemd `cfc-backend`(9082),生产 jar `/app/pro/cfc/backend/cfc-backend-1.0.0.jar`;langgraph `docker compose`(9000,容器 `cfc-langgraph-langgraph-svc-1`) - 管理端 API 统一 POST;管理端 axios 用 `import request from '@/utils/request'`(**不要用 `this.$axios`**,项目未注册该原型) - **category 字段丢弃**(不 ALTER 表)——Chroma metadata.category 写了从不读/过滤;`health_butler_graph` 读 category 走 `/api/health/knowledge/query`(读原表,不读 Chroma) - langgraph 新镜像 **必须先构建好(不部署)**,DB INSERT 后才 force-recreate 替换容器(避免窗口期双重索引) - 保留 `created_at`/`updated_at` 原始值(INSERT 显式指定列,不依赖默认 CURRENT_TIMESTAMP) - 生产目录 `/app/pro/cfc` 改动前必须先备份 jar;验证失败必须回滚 + 复验 - **远程工作树可能含他人 WIP**(2026-10-20 实施时发现另一会话正在编辑 Activity 模块):只允许 `git add <本任务文件>`,**禁止** `git add -A` / `git stash` / `git checkout .` / `git restore .` / `git clean`;构建在隔离 worktree 进行:`git worktree add --detach /tmp/ku-build HEAD` → 在其中 `mvn package`,确保生产 jar 不含 WIP;任务结束 `git worktree remove /tmp/ku-build --force` ## Review Focus 1. **langgraph 窗口期双重索引**(最高风险):DB INSERT 后若旧镜像仍在运行且定时同步触发,会把 848 条全部索引为 dan_knowledge。→ Task 2 先构建镜像,Task 3 立即 INSERT,Task 4 立即 force-recreate 替换容器。验证:重建日志 by_source 仅三源且块数与设计一致,`dan_knowledge` ≈ 1885。 2. **分页稳定性**:`wrapper.orderByAsc(getSort)` 缺 id tie-break,809 篇 sort 全 0 → 重建漏块或重复。→ Task 1 加 `.orderByAsc(DanKnowledgeBase::getId)`。验证:重建后 microbiome 块数与 809 篇实际分块数一致,无重复 id。 3. **dan_knowledge 源被误排除**:`excludeSourceType` 必须排除的是 `article`,不是 `file`。验证:`/api/admin/knowledge-base/list` 不传 sourceType 返回 848 条;传 excludeSourceType='article' 返回 39 条。 4. **时间戳被覆盖**:INSERT 未显式指定 created_at/updated_at 会用 CURRENT_TIMESTAMP,809 篇 pub_date 全变 2026-10-20。验证:抽查 1 篇 pub_date = 原始健康表时间。 5. **清理误删**:`DELETE` 必须限定 `item_type='article'`(表保留给未来指标条目)。验证:清理后表存在且 `COUNT(*)=0`,其他 item_type 行未动。 6. **权限共享**:删路由与菜单共享 `knowledge:base` perm,删路由不应影响「知识库管理」页访问。验证:Layout.vue 与 router 均无 `health-knowledge`,知识库管理页仍可进入。 --- ### Task 1: 后端 list 接口支持 sourceType/excludeSourceType + id tie-break **Files:** - Modify: `cfc-backend/src/main/java/com/etotem/cfc/service/DanKnowledgeBaseService.java:58-90` - Modify: `cfc-backend/src/main/java/com/etotem/cfc/controller/admin/KnowledgeBaseController.java:39-50` **Interfaces:** - Produces: `DanKnowledgeBaseService.list(String keyword, Integer status, int page, int size, Long tagId, Long dimensionId, String sourceType, String excludeSourceType)` — 8 参重载;保留 4 参/6 参重载委托 8 参(传 null),向后兼容现有调用方 - Produces: API `POST /api/admin/knowledge-base/list` 接受 `sourceType`(精确匹配)与 `excludeSourceType`(排除匹配),不传则返回全部 **Steps:** - [ ] **Step 1: Service 加 8 参重载与条件** 在 `DanKnowledgeBaseService.java`:6 参 `list` 改为委托新 8 参方法(`list(keyword, status, page, size, tagId, dimensionId, null, null)`)。8 参方法内新增: ```java if (sourceType != null && !sourceType.isEmpty()) { wrapper.eq(DanKnowledgeBase::getSourceType, sourceType); } if (excludeSourceType != null && !excludeSourceType.isEmpty()) { wrapper.ne(DanKnowledgeBase::getSourceType, excludeSourceType); } ``` 排序改为 `wrapper.orderByAsc(DanKnowledgeBase::getSort).orderByAsc(DanKnowledgeBase::getId)`(id 作为稳定 tie-break)。 - [ ] **Step 2: Controller 解析新参数** `KnowledgeBaseController.list` 在解析 dimensionId 后追加: ```java String sourceType = (String) params.get("sourceType"); String excludeSourceType = (String) params.get("excludeSourceType"); ``` 调用改为 `danKnowledgeBaseService.list(keyword, status, page, size, tagId, dimensionId, sourceType, excludeSourceType)`。 - [ ] **Step 3: 删除 /article/list 死端点** langgraph 改用 `/api/admin/knowledge-base/list` 后,`HealthKnowledgeBaseController.article/list` 无人调用。同文件删除该端点方法及 `HealthKnowledgeBaseService.pageArticles`(**保留** `/query` `/list` `/import` `/delete` 等其他端点——`health_butler_graph` 依赖 `/query`)。 - [ ] **Step 4: 提交本任务改动(只 add 这 4 个文件)** ```bash cd /app/cfc && git add cfc-backend/src/main/java/com/etotem/cfc/service/DanKnowledgeBaseService.java cfc-backend/src/main/java/com/etotem/cfc/service/HealthKnowledgeBaseService.java cfc-backend/src/main/java/com/etotem/cfc/controller/admin/KnowledgeBaseController.java cfc-backend/src/main/java/com/etotem/cfc/controller/HealthKnowledgeBaseController.java git commit -m "feat(knowledge): list 接口支持 sourceType/excludeSourceType 过滤 + id 稳定分页,移除废弃 /article/list 端点" git rev-parse HEAD ``` ⚠️ 禁止 `git add -A` / `git stash` / `git checkout .` / `git restore .`(工作树含他人 WIP)。 - [ ] **Step 5: 隔离 worktree 编译打包 + 备份 + 部署 + 验证** ```bash cd /app/cfc && git worktree add --detach /tmp/ku-task1-build HEAD cd /tmp/ku-task1-build/cfc-backend && mvn compile -DskipTests -q && mvn package -DskipTests -q BACKUP=/app/pro/cfc/backend/cfc-backend-$(date +%Y%m%d_%H%M%S)_b6_knowledge_unify.jar cp /app/pro/cfc/backend/cfc-backend-1.0.0.jar $BACKUP cp /tmp/ku-task1-build/cfc-backend/target/cfc-backend-1.0.0.jar /app/pro/cfc/backend/ systemctl restart cfc-backend sleep 20 && systemctl is-active cfc-backend cd /app/cfc && git worktree remove /tmp/ku-task1-build --force ``` Expected: BUILD SUCCESS + jar 产出 + active=active。 验证(全部通过后才能进 Task 2): ```bash curl -s -X POST http://localhost:9082/api/admin/knowledge-base/list -H 'Content-Type: application/json' -d '{"page":1,"size":1}' | jq '.data.total' # 期望 39 curl -s -X POST http://localhost:9082/api/admin/knowledge-base/list -H 'Content-Type: application/json' -d '{"page":1,"size":1,"excludeSourceType":"article"}' | jq '.data.total' # 期望 39 ``` Expected: 均 39(INSERT 前 article 为 0 条)。同时确认 `systemctl is-active cfc-backend` = active。 - [ ] **Step 6: 提交状态确认** 提交已在 Step 4 完成。确认 `cd /app/cfc && git log --oneline -1` 为本任务提交,且 `git status --short` 中本任务 4 个文件已不在未暂存列表(他人 WIP 文件仍在,属预期)。 --- ### Task 2: langgraph 数据源改造 + 镜像构建(不部署) **Files:** - Modify: `cfc-langgraph/app/tools/java_client.py:146-166`(get_dan_knowledge_base) - Modify: `cfc-langgraph/app/tools/java_client.py:145-172`(get_microbiome_articles,当前调 `/api/health/knowledge/article/list`) **Interfaces:** - Consumes: Task 1 的 `POST /api/admin/knowledge-base/list`(`sourceType` / `excludeSourceType`) - Produces: `get_dan_knowledge_base()` → 仅案例报告(排除 article);`get_microbiome_articles()` → 仅菌群文章(article),返回结构与 `_build_chunks` 期望一致:`{id, title, content, category, source_url, sourceUrl, pubDate, createdAt, updatedAt}` **Steps:** - [ ] **Step 1: get_dan_knowledge_base 加排除** 请求体改为 `{"page": page, "size": size, "status": 1, "excludeSourceType": "article"}`,size 保持 1000。docstring 补注:案例报告源,排除 article(菌群文章)。 - [ ] **Step 2: get_microbiome_articles 改数据源** 改为分页拉 `POST /api/admin/knowledge-base/list`,请求体 `{"page": page, "size": size, "status": 1, "sourceType": "article"}`(size 50,ORDER BY id 由后端保证)。字段映射对齐 `_build_chunks` 期望键(title/content/category 为空串/source_url 留空/pubDate 取 createdAt 或 updatedAt)。 - [ ] **Step 3: 镜像构建(后台,不部署)** ```bash cd /app/cfc/cfc-langgraph && nohup bash -c 'docker compose build langgraph-svc > /tmp/langgraph-build5.log 2>&1' & ``` 等待完成(检查 `/tmp/langgraph-build5.log` 末行含 `Successfully built` 或 `DONE`,`docker images` 确认新镜像 tag)。**不要执行 `docker compose up`**。 - [ ] **Step 4: 提交** ```bash cd /app/cfc && git add cfc-langgraph/app/tools/java_client.py && git commit -m "feat(langgraph): microbiome 源改拉 dan_knowledge_base(source_type=article),dan 源排除 article" ``` **验收(进 Task 3 前):** 后端新接口可用 + langgraph 镜像已构建但容器未替换。 --- ### Task 3: DB 迁移(备份 → INSERT 809 篇 → 校验) **Files:** - Create: `/tmp/migrate_articles.sql`(迁移脚本,上传远程执行) **Interfaces:** - Consumes: Task 1 的后端新接口(验证用) - Produces: `dan_knowledge_base` 含 848 条(39 file + 809 article),source_type 区分 **Steps:** - [ ] **Step 1: 备份源表** ```bash MY="mysql -h mysql-internet-cn-north-1-23feae22680e4dfa.rds.jdcloud.com -P 3306 -u cfc -pcfc@1314 zxyj" $MY -e "DROP TABLE IF EXISTS dan_knowledge_base_bak_20261020; CREATE TABLE dan_knowledge_base_bak_20261020 LIKE dan_knowledge_base; INSERT INTO dan_knowledge_base_bak_20261020 SELECT * FROM dan_knowledge_base;" $MY -e "DROP TABLE IF EXISTS health_knowledge_base_bak_20261020; CREATE TABLE health_knowledge_base_bak_20261020 LIKE health_knowledge_base; INSERT INTO health_knowledge_base_bak_20261020 SELECT * FROM health_knowledge_base;" ``` Expected: 两备份表行数 = 39 / 809。 - [ ] **Step 2: INSERT 809 篇(保留原始时间戳)** ```sql INSERT INTO dan_knowledge_base (title, content, sort, status, source_url, source_type, created_at, updated_at) SELECT item_name, description, 0, 1, source, 'article', created_at, updated_at FROM health_knowledge_base WHERE item_type = 'article'; ``` Expected: `INSERT` 影响 809 行。 - [ ] **Step 3: 校验** ```bash $MY -e "SELECT source_type, COUNT(*) FROM dan_knowledge_base GROUP BY source_type;" # 期望 file=39, article=809 $MY -e "SELECT COUNT(*) FROM dan_knowledge_base WHERE CHAR_LENGTH(title) > 200;" # 期望 0 $MY -e "SELECT MIN(CHAR_LENGTH(content)), MAX(CHAR_LENGTH(content)) FROM dan_knowledge_base WHERE source_type='article';" $MY -e "SELECT MIN(created_at), MAX(created_at) FROM dan_knowledge_base WHERE source_type='article';" # 期望为原始时间,非 2026-10-20 curl -s -X POST http://localhost:9082/api/admin/knowledge-base/list -H 'Content-Type: application/json' -d '{"page":1,"size":1}' | jq '.data.total' # 期望 848 ``` 若任一校验不符,**回滚**:`$MY -e "DELETE FROM dan_knowledge_base WHERE source_type='article';"` 后修正脚本重跑。 - [ ] **Step 4: 提交迁移脚本** ```bash mkdir -p /app/cfc/docs/migrations && cp /tmp/migrate_articles.sql /app/cfc/docs/migrations/20261020_articles_to_dan_knowledge.sql cd /app/cfc && git add docs/migrations && git commit -m "docs(knowledge): 809 篇菌群文章迁移至 dan_knowledge_base 脚本" ``` --- ### Task 4: 部署 langgraph 新镜像 + 全量重建索引 **Files:** 无源码改动(仅部署 + 触发) **Interfaces:** - Consumes: Task 2 已构建镜像 + Task 3 已迁移数据 - Produces: Chroma `cfc_knowledge` collection 三源重建完成,metadata 键 id/source/post_id/title/category/tags/source_url/pub_date/chunk_index/chunk_total **Steps:** - [ ] **Step 1: 替换容器** ```bash cd /app/cfc/cfc-langgraph && docker compose up -d --force-recreate langgraph-svc docker ps --filter name=langgraph -f status=healthy ``` Expected: 容器 healthy 且使用 Task 2 的新镜像 digest。 - [ ] **Step 2: 触发全量重建** ```bash curl -s -m 60 -X POST http://localhost:9000/api/v1/knowledge-base/sync ``` Expected: 同步任务启动(首次延迟 120s 后执行)。 - [ ] **Step 3: 监控重建进度** ```bash docker logs --tail -50 cfc-langgraph-langgraph-svc-1 2>&1 | grep -E "知识库同步|同步完成|ERROR" ``` Expected: 依次看到 article / microbiome / dan_knowledge 三源同步日志,microbiome 为 809 篇,无 ERROR。重建约 20–30 分钟(SiliconFlow 429 退避)。 - [ ] **Step 4: 验证三源统计** ```bash curl -s -X POST http://localhost:9000/api/v1/knowledge-base/stats | python3 -m json.tool ``` Expected: `by_source` 三源均有数据,microbiome 块数 ≈ 原 25369 量级,dan_knowledge ≈ 1885,**无 source='microbiome' 出现在 article 或反之**(窗口期双重索引检查)。 **验收(进 Task 5 前):** 三源重建完成 + 计数符合预期 + 无 ERROR 日志。 --- ### Task 5: 前端知识库管理页增强 + 删除健康知识库页 **Files:** - Modify: `cfc-web/src/components/KnowledgeTable/index.vue:10-25`(加来源方式列) - Modify: `cfc-web/src/views/admin/knowledge/index.vue:40-50`(加 sourceType 筛选下拉)+ fetch 参数 - Modify: `cfc-web/src/components/KnowledgeDialog/index.vue`(查看长文详情对话框,如现有 content 列已支持展开则跳过) - Delete: `cfc-web/src/views/admin/health-knowledge.vue` - Modify: `cfc-web/src/router/index.js:772-776`(删 health-knowledge 路由) - Modify: `cfc-web/src/Layout.vue:235`(删菜单项) **Interfaces:** - Consumes: Task 1 的 `sourceType` 参数 - Produces: 知识库管理页统一展示 848 条(source_type 可筛选/可识别),健康知识库页入口消失 **Steps:** - [ ] **Step 1: KnowledgeTable 加来源列** 在 sort 列前插入: ```html ``` - [ ] **Step 2: index.vue 加 sourceType 筛选** 筛选区加下拉: ```html ``` fetch 时传入 `sourceType`(不传则返回全部)。 - [ ] **Step 3: 删除健康知识库页** ```bash cd /app/cfc/cfc-web rm src/views/admin/health-knowledge.vue # 删 router/index.js 772-776 的 health-knowledge 路由对象 # 删 Layout.vue:235 的菜单项 grep -rn "health-knowledge\|健康知识库" src/ # 期望无残留(api 文件若有引用一并清理) ``` - [ ] **Step 4: 构建 + 部署 + 验证** ```bash cd /app/cfc/cfc-web && npm run build 2>&1 | tail -3 rsync -a --delete dist/ /app/pro/cfc/frontend/ curl -s -k https://cfc.etotem.com.cn/admin/ -o /dev/null -w "%{http_code}\n" # 期望 200 ``` Expected: Build complete;前端 200;产物 js 中 grep 无 `health/knowledge` 残留。 - [ ] **Step 5: Commit** ```bash cd /app/cfc && git add -A cfc-web/src cfc-web/.last_build_commit && git commit -m "feat(knowledge): 知识库管理页统一展示案例报告与菌群文章(sourceType 筛选/标识),删除健康知识库页与路由" ``` --- ### Task 6: 全链路验证 + 清理源表文章 + 推送 **Files:** 无源码改动 **Steps:** - [ ] **Step 1: 全链路验证** ```bash # 后端 systemctl is-active cfc-backend curl -s -o /dev/null -w "%{http_code}" -X POST http://localhost:9082/api/cart/list # 期望 200 # 前端 curl -s -o /dev/null -w "%{http_code}" -k https://cfc.etotem.com.cn/admin/ # 期望 200 # 后端日志无 ERROR grep -c " ERROR " /app/pro/cfc/backend/cfc-backend.log # langgraph 三源统计 curl -s -X POST http://localhost:9000/api/v1/knowledge-base/stats | python3 -m json.tool # 磁盘 du -sh /var/lib/docker/volumes/cfc-langgraph_langgraph-data/_data/ ``` - [ ] **Step 2: 清理源表文章数据(表保留)** ```bash MY="mysql -h mysql-internet-cn-north-1-23feae22680e4dfa.rds.jdcloud.com -P 3306 -u cfc -pcfc@1314 zxyj" $MY -e "SELECT item_type, COUNT(*) FROM health_knowledge_base GROUP BY item_type;" # 确认仅 article $MY -e "DELETE FROM health_knowledge_base WHERE item_type='article';" # 期望删 809 $MY -e "SHOW TABLES LIKE 'health_knowledge_base';" # 期望表仍存在 $MY -e "SELECT COUNT(*) FROM health_knowledge_base;" # 期望 0 ``` Expected: 表存在且 `COUNT(*)=0`,其他 item_type 行未被删除。 - [ ] **Step 3: 推送 + 工作区干净** ```bash cd /app/cfc && git push origin cfclub && git status --short # 期望空输出 ``` **最终验收(全部通过):** 管理端「知识库管理」页 848 条统一展示且可筛选;「健康知识库」菜单/路由消失;向量库三源 by_source 计数符合设计(microbiome ≈ 25369、dan_knowledge ≈ 1885、article 不变);无 ERROR 日志;磁盘不膨胀;`health_knowledge_base` 表保留空置。