# Bucket D — Webhook 重试持久化（bullmq 接入）

**优先级**：P1
**估时**：2 天
**架构级 PR**：必须独立 PR（CLAUDE.md "高风险路径必须单独 PR"）

## 背景

`webhook.service.ts:M9 重试链` 当前是**进程内 setTimeout**：
```ts
首轮失败 → setTimeout 2s → 重试 → setTimeout 4s → 重试 → ...
```
进程崩溃 / 重启 / 部署 → 剩余尝试全丢失。生产不可接受。

`bullmq` 已经在 `package.json` deps 里但**全后端没接过**——这是引入新基础设施的入口。

## 范围

### 1. bullmq 标准化接入
- [ ] `backend/src/core/messaging/queue/bullmq.module.ts`：通用 BullMQ Module，配置 Redis 连接
- [ ] 命名规范：队列名前缀 `ffoa:` + worktree 隔离（如 `ffoa:wt:approval-form-polish:webhook-delivery`）
- [ ] Graceful shutdown 钩子
- [ ] 可观测：Bull Board UI（dev only）

### 2. form-webhook-delivery 队列
- [ ] Producer：`webhook.service.ts:create/update/delete/triggerEvent` 入队
- [ ] Consumer：独立 worker 处理投递 + 指数退避重试
- [ ] 任务 schema：`{ webhookId, eventType, payload, signature, retryCount, maxRetries }`
- [ ] DLQ（死信队列）：达到 maxRetries 后归档

### 3. 现有 setTimeout 重试链替换
- [ ] 删除 `webhook.service.ts:scheduleRetry` 进程内 setTimeout 逻辑
- [ ] 改为入队 BullMQ + delay 参数（指数退避 2/4/8/16/32/60s 封顶）
- [ ] webhookLog.retryCount 由 worker 写入

### 4. 测试
- [ ] L1：投递成功路径
- [ ] L1：投递失败 → 重试 → 重试 → 成功路径
- [ ] L1：达到 maxRetries → 进 DLQ
- [ ] L1：模拟 worker 崩溃 → 重启后任务恢复（bullmq 持久化保证）

### 5. 文档
- [ ] `docs/standards/messaging/queue-bullmq.md`：BullMQ 接入规范
- [ ] `docs/modules/form-management/07-api.md`：Webhook 重试章节更新（去掉"进程内 setTimeout"语义）
- [ ] env：`REDIS_URL` / `QUEUE_PREFIX` 注释

## 风险点

- bullmq 是新依赖，CI 资源 / 部署 env / Redis 连接配置都得跟上
- worktree 多副本下 Redis 共用 → 队列名必须 worktree 前缀避免互相消费
- worker 进程是否独立部署？还是嵌入 backend 主进程？（决策点）

## 交付物

- bullmq.module + form-webhook-delivery 队列实现
- L1 测试套件
- BullMQ 接入文档
- env 模板更新

## 完成条件

- L1 测试全过
- UAT 跑一周无队列任务积压 / 内存泄露
- 删除本文件
