Files
eightbf-analysis-go/docs/服务端开发设计要求.md
T
gozerosandCursor 0c45b86d67 chore: 初始化 eightbf-analysis-go(对齐 Java 会商作业服务)
Go 实现 /api/v1/analysis 契约、Demo/Live Worker、DeerFlow SSE;含设计文档与冒烟脚本。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-03 00:13:46 +08:00

380 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` | 气泡流式展示 | 增量追加;终帧勿无脑整段覆盖丢掉 `<think>` |
| `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<T>` → `{ 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` 与正文一并折成 `<think>…</think>` + 正文,保证前端两边都能流。
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 逐字打日志
- 只写控制台、无滚动、无采集说明就当「生产可运维」
---
*文档版本与仓库代码同步维护;重大行为变更请改本节并在提交说明中引用。*