# eightbf-analysis 服务端开发设计要求 > 适用范围:本仓库(8BF「多岗位会商」后台作业服务,Spring Boot 2.0.9 + JDK8)。 > 目标:关浏览器后会商仍能跑完;状态以服务端为准;产品前端只经 `/analysis-api` 访问本服务。 > 配套:根 [README.md](../README.md)、[内网迁移.md](./内网迁移.md)、源码包注释(`package-info` / 各层类注释)。 本文是**开发约束与设计验收标准**,不是空泛愿景。改功能前先对照;评审以本文为准。 --- ## 1. 定位与边界 ### 1.1 本服务负责 | 职责 | 说明 | |------|------| | 会商作业生命周期 | 创建 / 查询 / 取消 / 续跑 / 热更新 / 注入 / 结果 | | 服务端执行 | Demo 假进度或 Live 真模型链条;关页不断 | | 状态与流式内容落库 | `status`、`plan`、`dialogues`、`result` 等以 MySQL 为准 | | 对接 DeerFlow | 登录、建线程、消费 SSE;失败策略由本服务决定 | | 岗位可见性投影 | 按账号裁剪对话后再返回前端 | ### 1.2 本服务不负责 - 产品登录 / SSO / 账号体系本体(只认请求头里的账号与角色) - 岗位会商「DeerFlow joinStream」那条链路(岗位侧保持原样,不经本服务挂起) - 浏览器直连 DeerFlow `roundtable-jobs` 跑 8BF(**禁止**作为正式路径) - Fusion 其它 API(`/zl_consumer`、`/deerflow-api` 等) ### 1.3 硬原则(不可破) 1. **状态权威在服务端**:前端轮询展示;刷新必须带草稿里的 `jobId`,禁止用「task 下最新一场」冒充重连。 2. **一开一场**:每次「开始研讨」新建作业;同一 `taskId` 可并存多场;不自动取消旧场。 3. **浏览器可走,作业不能丢**:取消只靠 `cancel_flag` + Worker 协作停;不要假设 SSE 连着用户标签页。 4. **失败不当成功**:模型不可用、席位空输出、流无终帧、自动换模 → 整场 `error`;禁止刷完链条再标 `done` 解锁前端下一阶段。 5. **热更新以库为准**:人工干预 / 模型 / inject 写在库里;Runner 每席与 flush 前从库合并,禁止内存态盖掉用户 PATCH。 --- ## 2. 总体架构 ```text 浏览器 → Nginx /analysis-api → eightbf-analysis (:18097) ├─ web(HTTP + DTO) ├─ service(编排 / 鉴权投影 / 落库 / DemoWorker) ├─ worker(LiveWorker 调度 + LiveChainRunner 执行) ├─ deerflow(Auth / Client / SSE) └─ MySQL eba_analysis_job ↑ DeerFlow Gateway(仅服务端访问) ``` ### 2.1 分层要求 | 包 | 允许 | 禁止 | |----|------|------| | `web` | 参数校验、HTTP 状态码、组 Caller、调 service | 直接 JDBC;直接调 DeerFlow;写业务状态机细节 | | `service` | 作业编排、权限、仓储、Demo 推进 | 拼 DeerFlow SSE;复制一套状态机与 Runner 打架 | | `worker` | 真模型调度与链条;读写作业检查点 | 暴露新 HTTP;绕过 Repository 改库 | | `deerflow` | HTTP/SSE、token、模型校验、流式折算 | 知道「作业业务规则」以外的前端约定;改作业 status | | `domain` | 实体、状态常量 | Spring 注解、IO | 依赖方向只能向内:`web → service → domain`;`worker → service/domain + deerflow`。禁止 `deerflow` 依赖 `web`。 ### 2.2 运行模式 | 配置 | 行为 | |------|------| | `analysis.mode=live` + `eba.worker.demo-enabled=false` | **正式/联调默认**:LiveWorker 领 `queued`,LiveChainRunner 调 DeerFlow | | `analysis.mode=demo` + `eba.worker.demo-enabled=true` | 仅演示假发言;不得当内网验收真模型 | 两模式互斥清晰:禁止「一边假进度一边又去打 DeerFlow」。 --- ## 3. 领域与状态机 ### 3.1 作业(AnalysisJob) 一场作业 = 服务端上的一场研讨录像带。关键袋子: | 字段 | 用途 | 开发约束 | |------|------|----------| | `id` | 主键;草稿必须存 | 新建生成;禁止按 task 覆盖 | | `status` / `phase` | 生命周期 / 细阶段 | 状态字符串只用 `JobStatus` 常量 | | `plan` | 检查点与开关 | 至少:`seatIndex`、`manualIntervention`、`pendingInject`、`awaitReason`、`seedMessage` | | `dialogues` | 气泡流式展示 | 增量追加;终帧勿无脑整段覆盖丢掉 `` | | `threadIds` | DeerFlow 线程 | 有则续跑跳过 init | | `access` | 岗位账号限制 | 结构与前端约定一致 | | `progress` / `result` | 进度文案 / 终态汇总 | `error` 时清掉误导性 summary / 共识 | ### 3.2 状态流转 ```text queued → running → awaiting_input ⇄ queued(resume) → done → error → cancelled ``` | 状态 | 含义 | 允许的写操作 | |------|------|----------------| | `queued` | 等 Worker 领取 | cancel;inject;patch plan | | `running` | 执行中 | cancel;inject;patch plan | | `awaiting_input` | 澄清或人工卡点 | resume;cancel;inject;patch plan(关干预可自动放行) | | `done` / `error` / `cancelled` | 终态 | **禁止** resume / inject / 改计划 | 实现要求: - 取消:先 `cancel_flag=1`,Runner/Demo 每拍检查;再落 `cancelled`。 - 续跑:仅 `awaiting_input → queued`;检查点用 `plan.seatIndex`,勿重新从 0 开场除非产品明确要求。 - 人工干预默认安全:`manualIntervention` 缺省视为 **true**(宁可多卡一次)。 ### 3.3 真模型链条语义 顺序固定: 1. 校验席位 ≥ 2、模型在 DeerFlow 列表中(未知模型 **拒绝**,禁止静默换模)。 2. 无 `threadIds` 则 init;有则续跑。 3. 对每个席位:总控派活 → 该席输出 →(若开人工干预且非末席)进入 `awaiting_input`。 4. 全席成功后总控收口;交付不全或对话已有 error 气泡 → 不得 `done`。 一席失败立刻 `error` 停场,禁止「继续跑完再汇总错误」。 --- ## 4. API 设计要求 ### 4.1 约定 - 前缀:`/api/v1/analysis` - 包体:`ApiResponse` → `{ code, message, data }`;成功 `code=0` - HTTP:`201` 创建;`404` 找不到;`403` 无权限;`400` 状态/参数不允许 - 身份头:`X-Account-Id`、`X-Analysis-Role`(`admin` / `task_owner` / `position_owner` / `viewer`) - 探活:`GET /health` **不鉴权** ### 4.2 接口清单(保持稳定) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/jobs` | 新建;返回投影后快照 | | GET | `/jobs/{id}` | 轮询 / 重连主路径 | | GET | `/jobs?taskId=&latest=` | 兼容查询;**正式重连勿依赖此接口替代 jobId** | | POST | `/jobs/{id}/cancel` | 仅责任人/创建人/管理员 | | POST | `/jobs/{id}/resume` | 澄清回答或人工「继续」 | | POST | `/jobs/{id}/plan` | 热更新人工干预 / 模型 | | POST | `/jobs/{id}/inject` | 运行中补充;空文本 400 | | GET | `/jobs/{id}/result` | 裁剪字段的结果视图 | | GET | `/health` | 探活 | ### 4.3 兼容与演进 - 字段名尽量与前端 `mapJob` 一致,少做二次映射。 - 新增字段:只增不改语义;废弃字段先双写/文档标明,再删。 - 破坏性变更(状态字符串、必填头、错误码语义)必须同步改 code-zk 客户端与本文,并在 PR 说明迁移步骤。 --- ## 5. 鉴权与投影 配置:`analysis.auth.enabled`(本机可 false;内网建议 true)。 | 角色 | 读 | resume/inject/改 plan | cancel | |------|----|------------------------|--------| | admin / task_owner / 创建人 / access 任务责任人 | 全量 | 是 | 是 | | position_owner(绑定了席位) | 本席 + system/user/leader | 是 | **否**(除非兼责任人) | | 无账号且强制鉴权 | 否 | 否 | 否 | 投影规则(即使本机关强制鉴权,声明了 `position_owner` 也要裁剪,便于验收): - 岗位账号:只见 `system` / `user` / `leader` + 自己 `agentId` 的 special 气泡。 - `result.seatDeliveries` 同步裁剪。 新接口若返回作业快照,必须走 `JobAccessService.projectForCaller`,禁止裸库对象出站。 --- ## 6. 持久化要求 - 表:`eba_analysis_job`(见 `schema.sql`);大字段 JSON 文本列。 - 正式库:**MySQL utf8mb4**;H2 仅应急联调。 - 写库路径统一经 `JobRepository`;流式 flush 可节流(约 ≥150ms),但取消旗、状态跃迁必须及时。 - 索引:`task_id` / `status` / `owner_account_id` 保持;新增查询条件先补索引再上线。 - 启动执行 `schema.sql` 允许 `continue-on-error`(重复索引);**禁止**靠静默失败掩盖真实 DDL 错误而不验收。 --- ## 7. DeerFlow 集成要求 1. **仅服务端**持有 Gateway 账号;口令走环境变量,不写死进正式配置提交物。 2. Token 复用并提前刷新;并发登录要有同步,避免打爆登录接口。 3. `assertModelAvailable`:指定模型不在列表 → 开跑前失败;禁止依赖 Gateway「自动换模」。 4. SSE:识别 `event:error`、`model_switch`、无终帧;一律映射为 `invalidDelivery` / 业务失败。 5. 流式展示:`reasoning_content` 与正文一并折成 `…` + 正文,保证前端两边都能流。 6. 超时:`stream-idle-timeout-ms` 可配;取消检查嵌在读循环里。 7. 本服务不得把 DeerFlow 内部错误原文无差别甩给用户;要带「当前模型」提示,便于换模重开。 --- ## 8. Worker 与并发 - LiveWorker:FIFO 领 `queued`;先占坑改 `running` 再异步执行;默认并发上限 **2**(可配置化时须有文档)。 - 单机进程内线程池即可;多实例部署前必须先设计「领取互斥」(乐观锁 / `FOR UPDATE` / 外部队列),否则禁止双活抢同一作业。 - DemoWorker 与 LiveWorker 开关清晰;生产关闭 demo。 --- ## 9. 配置与环境 | 项 | 要求 | |----|------| | 端口 | 默认 `18097`;经 Nginx `/analysis-api` 反代 | | 数据源 | `EBA_DB_*` 覆盖;正式禁用默认弱口令 | | DeerFlow | `EBA_DF_*` 覆盖 | | 鉴权 | 内网打开 `analysis.auth.enabled=true` | | 密钥 | 不进 Git;参考 `docs/env.sample` | 变更配置项必须更新:`application.yml` 注释、`env.sample`、内网迁移文档。 --- ## 10. 编码与注释规范 - 语言:JDK **8**;禁止使用仅高版本语法(除非整体升级并改构建)。 - 注释:新代码须**详细中文注释**(为什么 / 约定 / 边界),禁止只复述变量名;有用旧注释不得顺手删。 - 异常:业务用明确文案;`SecurityException` → 403;`IllegalStateException` / `IllegalArgumentException` → 400。 - 日志:见 **§11 日志与收集**;关键作业必须带 `jobId=`;禁止打 token、密码、完整用户隐私对话到 INFO(必要时 DEBUG 且脱敏)。 - 测试:至少覆盖状态不允许的操作、权限拒绝、模型校验失败、取消中断;无单测的改动须在 PR 写清手工验收步骤。 --- ## 11. 日志与收集 本节是运维可落地的约束:先保证「能查、能关联、不泄密」,再谈集中平台。 ### 11.1 目标 | 目标 | 说明 | |------|------| | 排障 | 任意一场会商能用 `jobId`(必要时再加 `requestId` / `taskId`)串起创建→领取→DeerFlow→卡点→结束 | | 审计 | 谁 cancel / resume / inject / 改 plan 可追溯(带账号头,不带口令) | | 采集 | 日志形态稳定,便于本机落盘或内网 Filebeat/Vector/rsyslog 采集 | | 安全 | 密钥与大段会商正文默认不上日志;完整内容以库表为准 | ### 11.2 输出与落盘(应用侧) 1. **默认打 stdout/stderr**(systemd / Docker / 进程托管都能直接收)。 2. 正式环境建议同时挂 **滚动文件**(便于采集 agent 扫目录),例如: - 路径:`/var/log/eightbf-analysis/app.log`(可用环境变量覆盖) - 滚动:按天或按大小(如 100MB × 14 份);禁止无限单文件撑爆磁盘 3. 使用标准 Logback(Spring Boot 默认即可);自定义 `logback-spring.xml` 时须文档化路径与滚动策略。 4. 日志级别: - 生产默认:`com.eightbf.analysis=INFO`,框架 `WARN` - 排障临时:单 logger 升 `DEBUG`,用完改回;禁止长期全局 DEBUG ### 11.3 格式(方便采集与检索) 每条业务日志尽量同构,文本键值即可(先不强制 JSON,内网解析成本更低): ```text 2026-11-02 23:40:01.123 INFO [...] c.e.a.worker.LiveWorker - 开始真模型会商 jobId=job_xxx requestId=req_yyy ``` **推荐固定键(有则必带):** | 键 | 何时 | |----|------| | `jobId=` | 一切与作业相关的日志 | | `requestId=` | 创建、与前端对账 | | `taskId=` | 仅当本次操作明确带了 task(勿刷屏) | | `accountId=` | cancel / resume / inject / patchPlan(来自请求头) | | `phase=` / `status=` | 状态跃迁、暂停等待 | | `model=` | 模型校验失败、开跑、换模拒绝 | 禁止:无键的散文导致无法 grep;同一事件重复刷屏(流式 delta **不要**每字打日志)。 ### 11.4 必打事件(最低集合) | 级别 | 事件 | |------|------| | INFO | 作业创建;LiveWorker 领取并开始;进入 `awaiting_input`(澄清/人工);resume 重新入队;会商 `done`;`cancelled` | | INFO | DeerFlow 登录成功(只打过期秒数,**不打 token**);init 完成(席位数) | | WARN | Worker/Runner 未捕获异常;DeerFlow HTTP≥400;模型不可用;流无终帧 / `model_switch` 拒绝;取消后仍异常退出 | | ERROR | 进程级故障(数据源不可用、反复登录失败等) | HTTP 访问明细优先靠 **Nginx access log**(`/analysis-api`);应用内不必再镜像每一条轮询 GET(会淹没真正故障)。 ### 11.5 脱敏与禁止项 **禁止写入日志:** - DeerFlow / DB 密码、Bearer token、Cookie - 完整 `dialogues` / 模型长回复 / inject 全文(库里已有;日志最多截断摘要 ≤200 字且仅 WARN/ERROR) - 身份证等敏感 PII(本服务一般无;若 payload 带入勿原样打印) **允许:** 错误摘要、模型名、agentId、席位序号、HTTP 状态码、耗时毫秒。 ### 11.6 采集与留存(运维侧) 按内网条件选一种,**应用不绑定某一家商业 APM**: ```text eightbf-analysis → stdout 和/或 /var/log/eightbf-analysis/*.log → Filebeat / Vector / rsyslog → 内网 Loki / ELK / 现有日志平台 Nginx /analysis-api access/error → 同上(务必带上游 upstream 耗时,便于区分网关与本服务) ``` 要求: 1. 采集配置与本服务**同主机交付说明**写进内网迁移或运维手册(路径、用户、权限)。 2. 应用日志与 Nginx 日志**时间对齐**(统一时区 `Asia/Shanghai`)。 3. 留存:建议热数据 ≥14 天,冷存按等保/单位规定;磁盘告警纳入主机巡检。 4. 检索演练:上线后用一场真实 `jobId` 能在平台搜到「创建→开始→结束/失败」完整链,算采集验收通过。 ### 11.7 与业务库的分工 | 数据 | 放哪 | |------|------| | 会商正文、气泡、检查点 | **MySQL**(权威) | | 何时开始/停/失败、对接 DeerFlow 是否成功 | **日志**(排障) | | 进度百分比给用户看 | `progress` 字段,不是日志 | 不要把日志当第二份 dialogues 备份。 ### 11.8 可观测(日志之外,可排期) 最低仍须保留:`GET /api/v1/analysis/health`。 建议后续:作业耗时、DeerFlow 错误码计数、queued 堆积告警(指标/告警通道另定,不替代日志)。 内网功能验收仍见 [内网迁移.md](./内网迁移.md)。 --- ## 12. 变更流程(本仓) 1. **先方案**:非琐碎改动先说清影响面(状态机 / API / 前端约定 / DeerFlow),用户批准后再写代码。 2. **范围克制**:只改任务所需;不顺手重构无关层。 3. **联调**:本服务 + code-zk `/analysis-api`;`sim:build` 后走一遍开研讨 / 干预 / 刷新。 4. **提交**:仅用户要求时 commit/push;勿夹带密钥与 `target/`、`data/`。 5. **文档同步**:行为变了必须改本文相关条款 + README/内网迁移(若涉及部署)。 --- ## 13. 验收检查表(开发自测) - [ ] 新建两场同 `taskId`,互不覆盖;刷新只跟各自 `jobId` - [ ] live 模式关浏览器后作业仍推进或停在明确卡点 - [ ] 取消后 SSE/推进停止,状态 `cancelled` - [ ] 人工干预开:每席(末席前)进入 `awaiting_input`;关且在卡点时自动续跑 - [ ] inject 后下一席派活带上补充;气泡可见用户原文 - [ ] 未知模型开跑失败,不进入 done - [ ] `position_owner` 看不到其它席 special 气泡;不能 cancel - [ ] 失败场无「可解锁下一阶段」的假 summary / 100% 共识 - [ ] 一场作业可用 `jobId` 在日志中串起创建→开始→结束/失败;无 token/密码泄露 - [ ] 正式环境日志有滚动策略;采集路径写进运维说明并能检索 - [ ] 注释与本文冲突时:以代码行为为准并回修文档 --- ## 14. 反例(明确禁止) - 浏览器直连 DeerFlow 跑 8BF 正式会商 - 用 `GET ?taskId=` 代替草稿 `jobId` 做刷新重连主路径 - 信任 DeerFlow 自动换模并当成功 - flush 时用旧内存 `manualIntervention`/`model` 盖掉库里用户热改 - 终态仍允许 inject/resume 而不报错 - 多实例无互斥双活抢作业 - 把演示 DemoWorker 开进生产当真模型 - 把 token / 密码 / 完整 dialogues 打进 INFO;流式 delta 逐字打日志 - 只写控制台、无滚动、无采集说明就当「生产可运维」 --- *文档版本与仓库代码同步维护;重大行为变更请改本节并在提交说明中引用。*