Files
gaokao-volunteer-system/docs/DELIVERY_SERVICE_DESIGN.md
Hermes Agent 94200960b2
Some checks failed
CI / pytest (Python 3.10) (push) Has been cancelled
CI / pytest (Python 3.11) (push) Has been cancelled
CI / pytest (Python 3.12) (push) Has been cancelled
docs(design): tighten X-02/X-03/X-05/X-08 design docs and add CROWD_DB_DATA_QUALITY
2026-06-15 15:14:27 +08:00

138 lines
4.5 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.
# DELIVERY_SERVICE_DESIGN
最后更新: 2026-06-14
## 1. 目标
将“报告生成完成”与“已交付给用户”明确区分,避免把 `delivered` 误当成只是一条后台状态修改。
## 2. 设计原则
1. 交付是独立业务对象,不等于报告文件存在
2. 用户可见状态必须基于真实交付物
3. 通知触发点应尽量靠近稳定主链,而不是只挂在单一路由
4. 至少一种 MVP 交付方式先闭环
## 3. MVP 交付方式
当前优先:
- 站内查看 HTML 报告
- 站内下载 PDF
后续可扩展:
- 邮件投递
- 微信/渠道通知
## 4. 建议对象
### delivery_job
- `delivery_id`
- `order_id`
- `channel` (`station/email/...`)
- `status` (`pending/ready/sent/failed`)
- `payload_json`
- `created_at`
- `updated_at`
### delivery_attempt
- `delivery_id`
- `attempt_no`
- `status`
- `failure_reason`
- `created_at`
## 5. 当前实现状态
已存在:
- `delivery_notifications` 事件表
- portal 状态页 / 报告页 / PDF 下载页
- `delivered` 但无交付物时不再误报 `report_ready`
- `data.notifications.dispatcher.DeliveryDispatcher`
- `scripts/gaokao-delivery-dispatch.py`
- `scripts/gaokao-delivery-watchdog.py`(失败返回 exit code `2`
- `ready -> sent` / `缺文件 -> failed` / `attempt_count` 递增
- `station` 通道 dispatch 时会真实生成 `station_notice`portal 状态页展示“通知已发送”消息卡片
- `email` 通道使用 `SMTPDeliverySender` 发送真实邮件通知,支持 `GAOKAO_SMTP_*` 配置,并已通过本地 stub SMTP 验证
- `docs/DELIVERY_RETENTION_OPS_RUNBOOK.md`
- `deploy/systemd/gaokao-delivery-{dispatch,watchdog}.{service,timer}`
- `deploy/cron/gaokao-jobs.crontab`
未完成:
- 独立 `delivery_job` / `delivery_attempt` 模型
- 邮件/渠道真实发送执行器
- 告警推送集成(当前只有 watchdog 非 0 退出码)
- 多通道统一投递状态页
## 6. 当前最短闭环
1. 订单进入 `delivered`
2. 真实 HTML/PDF 都存在
3. portal 显示 `report_ready`
4. 状态页提供查看/下载入口
5. `report_ready` 事件落库且幂等
6. `gaokao-delivery-dispatch.py --channel station` 可把 ready 事件推进到 sent
7. 缺失交付物时事件转 failed并记录 `failure_reason`
8. 可通过 systemd timer / cron 周期执行 dispatcher并用 watchdog 暴露失败退出码
## 7. 下一步实施建议
1. 增加失败重试阈值与告警推送
2. 增加面向用户的独立通知审计页
3. 把事件生命周期细化为 `ready` / `validated` / `delivered` / `failed`
4. 区分 `station`(本地渲染)与 `email`(真实下游推送)两类 channel 的 `delivered` 判定
5. 再决定是否扩到更多通道
## 8. 事件生命周期2026-06-14 修订)
P2-3 整改后, dispatcher 强制走如下状态机:
```
ready ── 初始化, 还没校验
validated ── payload 解析通过 + 交付物 (html / pdf) 校验通过
│ 注意: station channel 在这一步会落 payload, 但不再升级为 delivered
delivered ── 真实下游推送完成 (仅 email)
failed ── 任意环节失败
```
- `ready -> validated` 必须有 artifact (html / pdf) 真实存在于磁盘
- `validated -> delivered` 必须由真实 sender 回调成功 (`email.send_report_ready` 返回成功)
- `station` 通道因为没有真实下游, 永远停在 `validated`
- `sent` 字段是 legacy alias, 仍然存在但不应再被新代码依赖
## 9. 生产化接入口径
仓库内已提供最小 runbook 与调度样例:
- runbook`docs/DELIVERY_RETENTION_OPS_RUNBOOK.md`
- systemd`deploy/systemd/gaokao-delivery-dispatch.{service,timer}` / `deploy/systemd/gaokao-delivery-watchdog.{service,timer}`
- cron`deploy/cron/gaokao-jobs.crontab`
注意:当前 watchdog 仍是“带失败退出码的 dispatch 巡检”,并不等于独立通知告警系统。
## 10. 不可降级的不变量
- 事件状态机 `ready` / `validated` / `delivered` / `failed` 必须被 dispatcher 严格遵守
- `delivered` 永远只能由真实 sender 成功返回
- `failed` 事件必须有 `failure_reason` 字段
- 同一 `(order_id, event_type)` 必须幂等, 重复 dispatcher 不会产生额外副作用
- `delivered` 之后, payload 写入的 `delivered_at` 必须等于 `last_attempt_at`
## 11. 后续工作
- 把独立 `delivery_job` / `delivery_attempt` 模型实现为 SQLite 表
- 引入 `attempt_count``last_attempt_at` 之外的成功重试预算
- 多通道统一投递状态页 (类似 notification audit 页面)
- 告警推送 (Sentry / 飞书 / 钉钉) 与 watchdog 整合