chore: 初始化 eightbf-analysis-go(对齐 Java 会商作业服务)

Go 实现 /api/v1/analysis 契约、Demo/Live Worker、DeerFlow SSE;含设计文档与冒烟脚本。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
gozerosandCursor committed 2026-11-03 00:13:46 +08:00
commit 0c45b86d67
29 files changed
+3789

No files matched your search

+379
View File
@@ -0,0 +1,379 @@
# 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 逐字打日志
- 只写控制台、无滚动、无采集说明就当「生产可运维」
---
*文档版本与仓库代码同步维护;重大行为变更请改本节并在提交说明中引用。*