138 lines
4.5 KiB
Markdown
138 lines
4.5 KiB
Markdown
# 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 整合
|