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