5.2 KiB
幂等请求与 Transactional Outbox
状态:已确认的基础设施设计。对应 DDL 位于
sql/001_foundation.sql。首期只接入关键写操作和异步链路,不要求所有接口一次完成。
1. 分别解决什么问题
idempotency_requests 防止同一个用户请求被重复执行;典型原因是重复点击、浏览器超时重试或网络重放。它的结果是:同一个写请求只产生一份门店、发布任务或 AI 任务。
outbox_events 防止业务提交与队列投递之间出现空档。它的结果是:即使 API 在数据库提交后、BullMQ 入队前崩溃,Dispatcher 仍能从 PostgreSQL 找到待投递事件并补投。
两者互补但不互相替代:幂等解决“重复发起”,Outbox 解决“任务漏投递”。operations、发布任务、素材转存任务等业务表仍是用户可见的事实来源;Outbox 只是可靠投递机制。
2. 幂等请求的统一规则
仅对已认证的业务写接口使用本机制;注册、登录、刷新令牌等认证端点采用各自的限流和安全规则,不保存密码相关请求体。
2.1 调用与保存
客户端为一次逻辑操作生成 UUID 并放入请求头:
Idempotency-Key: 8f421c70-…
服务端在事务中以“工作空间、用户、HTTP 方法、规范化实际路径、键”查找或创建 idempotency_requests。request_fingerprint 是由服务端密钥计算的 HMAC,输入为规范化请求体和影响语义的请求头;不保存明文请求体,也不用普通哈希,以避免低熵敏感输入被离线猜测。
- 同一键、相同指纹、记录已完成:返回已保存的 HTTP 状态和安全响应体,不重复执行。
- 同一键、不同指纹:返回
409 IDEMPOTENCY_KEY_REUSED。 - 同一键仍在执行:返回
409 IDEMPOTENCY_REQUEST_IN_PROGRESS,客户端稍后以同一键重试。 - 业务事务回滚:幂等记录一并回滚,客户端可安全重试。
成功业务事务在写入资源、操作记录和 Outbox 事件后,将幂等记录标为 completed 并保存响应摘要。记录至少保留 24 小时;首期 DDL 默认 48 小时,由清理任务在过期后删除。
3. Outbox 的事务与投递
任何会启动异步工作的业务请求,必须在同一 PostgreSQL 事务内完成:
写入业务资源 / operation 或领域任务
+
写入 outbox_events(待投递)
+
完成 idempotency_requests(如该接口要求幂等)
↓ commit
Dispatcher 投递到 BullMQ
↓
Worker 执行业务工作并更新领域任务/operation
event_payload 仅保存事件处理所需的 ID、版本或不可变业务快照。Worker 再在受 RLS 约束的数据库事务内读取完整数据;不得把 API Key、Cookie、密码、对象存储永久密钥或预签名访问 URL 放入载荷或错误信息。
4. Dispatcher 与 Worker 规则
- Dispatcher 按工作空间处理待投递事件,并在每笔数据库事务中设置
app.workspace_id;应用角色不使用BYPASSRLS。 - 领取事件时使用
FOR UPDATE SKIP LOCKED,将状态置为dispatching,记录locked_by和有限的locked_until。 - 成功调用 BullMQ 后标记
dispatched;以outbox_events.id作为 BullMQjobId。若“已入队但尚未来得及标记”时进程崩溃,锁到期后会再次投递,因此投递语义是至少一次。 - 投递失败时增加
attempt_count,使用指数退避更新next_attempt_at并恢复pending;超过规定次数时标记dead_letter,保留脱敏错误供运维处理。 - Worker 必须可重入:以领域任务状态、平台侧幂等能力或自己的去重键阻止重复创建平台商品。不得把 BullMQ 的“只执行一次”当作正确性保证。
- 已投递事件不立即删除;保留一段时间用于排障,再由保留期清理任务删除。
idempotency_requests过期后也由独立清理任务删除。
5. 首批接入范围
| 场景 | 幂等 | Outbox | 说明 |
|---|---|---|---|
| 创建/替换门店授权、凭据验证 | 是 | 是 | 避免重复门店和漏掉验证任务。 |
| 上传完成后的媒体校验/处理 | 是 | 是 | 防止同一对象重复派生。 |
| 采集素材转存 | 是 | 是 | HTTP 接收成功后可恢复执行转存。 |
| 首次上架发布 | 是 | 是 | 避免重复发布请求与发布任务丢失。 |
| 在线商品全量同步 | 是 | 是 | 同一门店同时只允许一个活跃同步任务。 |
| AI 文本、类目或图片任务 | 是 | 是 | 避免重复调用和重复计费。 |
| 普通商品草稿保存 | 否(首期) | 否 | 使用 revision / If-Match 处理并发即可。 |
6. 实施验收
- 重放同一
Idempotency-Key不产生第二份资源或第二个业务任务,并返回原响应。 - 同一键但请求不同返回
409;两个并发相同请求仅一个进入业务执行。 - 在“业务事务提交后、BullMQ 投递前”模拟进程崩溃,Dispatcher 能补投。
- 模拟 BullMQ 已接受任务但 Dispatcher 未标记成功,重复投递不重复产生平台副作用。
- 未设置工作空间 RLS 上下文时,应用角色无法读取或投递任一工作空间的 Outbox 事件。