投递独立于生成
把投递写进生成脚本会带来两个后果:收件人变化要改脚本,投递失败要整批重跑。正确结构是生成一次成品,订阅层按「订阅人、报表、周期、渠道、格式、数据范围」决定发给谁。投递本身要幂等去重、要求回执,失败可对单个对象补发,并且内容必须按订阅人权限裁剪——多租户场景把全量报表群发出去就是一次数据泄露。
基本信息
| 订阅要素 | 订阅人、报表、周期、渠道、格式、数据范围 |
|---|---|
| 常见渠道 | 邮件、对象存储链接、消息通知、接口回推 |
| 关键控制 | 幂等去重、回执确认、重试上限、退订管理 |
| 权限裁剪 | 按订阅人可见范围生成切片,不群发全量 |
| 留痕要求 | 投递记录可查,支持按对象补发与追责 |
典型使用场景
- 周期报表分发:月报按订阅关系在固定时点发出,收件人可自助增减
- 多租户切片投递:每个租户只收到自身范围的数据切片
- 大文件分享:成品上传对象存储后发鉴权链接,避免邮件附件超限
- 失败补发:个别渠道失败时只补发失败对象,不打扰已成功收件人
- 审计归档:归档投递记录,能回答「某周期谁收到了什么」
投递环节要求与失败表现
| 环节 | 要求 | 失败表现 |
|---|---|---|
| 订阅解析 | 按数据范围生成个人化版本 | 群发全量数据,形成越权泄露 |
| 成品生成 | 同幂等键覆盖,不产生第二版 | 收件人收到两个口径的同一报表 |
| 投递执行 | 带回执,失败有限次重试 | 重复投递打扰收件人或静默不达 |
| 失败补发 | 按对象粒度补发 | 整批重发,已成功对象被重复打扰 |
| 归档审计 | 成品与投递记录留存 | 争议时无法证明发过什么内容 |
订阅关系定义
订阅关系定义
subscriptions:
- report_id: res-usage-monthly
period: monthly
channel: email # 渠道可替换,报表本身不感知渠道
format: pdf
scope: tenant_self # 只投递订阅人所属租户切片,禁止全量
send_at: 每月 3 日 09:00 # 生成完成后再投递,避免发出空报表
idempotency_key: report_id + period + recipient # 防止重复投递
on_failure: retry_3_then_alert # 重试有上限,超限转人工
link_policy: 大文件用鉴权链接,有效期与可下载次数受限 # 链接外泄风险可控投递时间要晚于生成完成时间,并留出余量。把投递挂在固定时刻而不检查成品是否就绪,就会周期性发出空报表或半成品。
收件人范围等同于数据授权范围。多租户平台上按「全部订阅人收到同一份全量报表」实现最省事,但这是最典型的越权泄露路径。