feat(queue): async delivery via Cloudflare Queues with retry backoff and DLQ

This commit is contained in:
RhenCloud 2026-08-15 14:58:48 +08:00
parent 737dd7af98
commit 486f38365f
No known key found for this signature in database
GPG key ID: A574A617378C4E0B
16 changed files with 702 additions and 22 deletions

View file

@ -85,7 +85,18 @@ bunx wrangler d1 execute webhooker --remote --file ./migrations/0005_audit_logs.
:::
### 4. Deploy
### 4. Create Queues (Optional)
The `QUEUE` binding routes webhook delivery through Cloudflare Queues (async dispatch with retry backoff and a dead-letter queue). Skip this step to keep dispatch inline (synchronous).
```bash
bunx wrangler queues create webhooker-delivery
bunx wrangler queues create webhooker-delivery-dlq
```
The queues are already declared in `wrangler.jsonc` (`queues.producers` / `queues.consumers`), so no binding change is needed. The `webhooker-delivery` consumer retries retryable failures with exponential backoff (5s/30s/2m/10m) up to `max_retries`, after which the message is moved to `webhooker-delivery-dlq` and marked dead.
### 5. Deploy
```bash
bunx wrangler deploy
@ -93,13 +104,13 @@ bunx wrangler deploy
Your worker is now live at `https://webhooker.<your-subdomain>.workers.dev`.
### 5. Configure GitHub Webhook
### 6. Configure GitHub Webhook
1. Go to your GitHub App settings
2. Set **Webhook URL** to `https://webhooker.<your-subdomain>.workers.dev/webhook`
3. Set **Webhook secret** to match `GITHUB_WEBHOOK_SECRET`
### 6. (Optional) Configure Gitea Webhook
### 7. (Optional) Configure Gitea Webhook
1. In your Gitea repo, go to **Settings → Webhooks → Add Webhook → Gitea**
2. Set **Target URL** to `https://webhooker.<your-subdomain>.workers.dev/webhook`
@ -181,4 +192,4 @@ To use a custom domain instead of `*.workers.dev`:
3. Update `BASE_URL` to match
> [!NOTE]
> This project is a Cloudflare Worker. It requires the KV and D1 bindings declared in `wrangler.jsonc`, so it cannot run as a standalone Node/container process.
> This project is a Cloudflare Worker. It requires the KV and D1 bindings declared in `wrangler.jsonc`, so it cannot run as a standalone Node/container process. The Queues binding (`QUEUE`) is optional — without it, webhook dispatch stays inline.

View file

@ -12,9 +12,11 @@
| `state:{hex}` | `{ redirectTo, expiresAt, discordUserId?, telegramUserId?, telegramChatId? }` | 600 seconds |
| `invite:{token}` | `{ groupId, role, expiresAt, createdBy, note? }` | 7 days |
| `invite:group:{id}` | Token index per group (keeps invite listing consistent) | Permanent |
| `delivery:{id}` | Webhook delivery id (dedup marker) | 300 seconds |
| `delivery:{groupId}:{id}` | Tenant-scoped delivery dedup for the per-group webhook ingress | 300 seconds |
| `tenant:{groupId}` | Per-group webhook secret (64-char hex, generated from the console) | Permanent |
| `delivery:{provider}:{groupId}:{id}` | Webhook delivery dedup (provider- and tenant-scoped) | 300 seconds |
| `delivery-state:{provider}:{groupId}:{id}` | Queue delivery state (`pending`/`processing`/`delivered`/`retrying`/`failed`/`dead`) | 1 day |
| `queue:payload:{provider}:{groupId}:{id}` | Oversized webhook payload parked for the queue consumer | 1 day |
| `nonce:{nonce}` | Custom-webhook replay protection nonce (single use) | 600 seconds |
| `tenant:{groupId}` | Per-group webhook secret (64-char hex, generated from the console) | Permanent |
| `msg:{routeId}:{key}:{target}` | Message id tracking for in-place updates (e.g. `workflow_run` / `check_run`) | 7 days |
| `cmd:guild:{id}` | Guild id whose commands were registered (dedup) | Permanent |
| `cmd:registered:global` | Global command registration marker (dedup) | 1 day |

View file

@ -85,7 +85,18 @@ bunx wrangler d1 execute webhooker --remote --file ./migrations/0005_audit_logs.
:::
### 4. 部署
### 4. 创建队列(可选)
`QUEUE` 绑定会通过 Cloudflare Queues 投递 webhook异步分发带重试退避与死信队列。跳过此步则保持同步内联分发。
```bash
bunx wrangler queues create webhooker-delivery
bunx wrangler queues create webhooker-delivery-dlq
```
队列已在 `wrangler.jsonc` 中声明(`queues.producers` / `queues.consumers`),无需修改绑定。`webhooker-delivery` 消费者对可重试失败做指数退避重试5s/30s/2m/10m达到 `max_retries` 后消息进入 `webhooker-delivery-dlq` 并标记为 dead。
### 5. 部署
```bash
bunx wrangler deploy
@ -93,13 +104,13 @@ bunx wrangler deploy
Worker 现在可通过 `https://webhooker.<your-subdomain>.workers.dev` 访问。
### 5. 配置 GitHub Webhook
### 6. 配置 GitHub Webhook
1. 进入 GitHub App 设置页面
2. 设置 **Webhook URL**`https://webhooker.<your-subdomain>.workers.dev/webhook`
3. 设置 **Webhook secret**`GITHUB_WEBHOOK_SECRET` 一致
### 6.(可选)配置 Gitea Webhook
### 7.(可选)配置 Gitea Webhook
1. 在 Gitea 仓库中进入 **设置 → Web 钩子 → 添加 Web 钩子 → Gitea**
2. 设置 **目标 URL**`https://webhooker.<your-subdomain>.workers.dev/webhook`
@ -181,4 +192,4 @@ Worker 现在可通过 `https://webhooker.<your-subdomain>.workers.dev` 访问
3. 更新 `BASE_URL` 以匹配
> [!NOTE]
> 本项目是一个 Cloudflare Worker依赖 `wrangler.jsonc` 中声明的 KV 与 D1 绑定,无法作为独立的 Node/容器进程运行。
> 本项目是一个 Cloudflare Worker依赖 `wrangler.jsonc` 中声明的 KV 与 D1 绑定,无法作为独立的 Node/容器进程运行。Queues 绑定(`QUEUE`)可选——未绑定时 webhook 分发保持内联同步。

View file

@ -12,9 +12,11 @@
| `state:{hex}` | `{ redirectTo, expiresAt, discordUserId?, telegramUserId?, telegramChatId? }` | 600 秒 |
| `invite:{token}` | `{ groupId, role, expiresAt, createdBy, note? }` | 7 天 |
| `invite:group:{id}` | 每组的 Token 索引(保证邀请列表一致性) | 永久 |
| `delivery:{id}` | Webhook 投递 id去重标记 | 300 秒 |
| `delivery:{groupId}:{id}` | 分组级 webhook 入口的租户级投递去重 | 300 秒 |
| `tenant:{groupId}` | 分组 webhook secret64 位 hex控制台生成 | 永久 |
| `delivery:{provider}:{groupId}:{id}` | Webhook 投递去重(按 provider 与租户隔离) | 300 秒 |
| `delivery-state:{provider}:{groupId}:{id}` | 队列投递状态(`pending`/`processing`/`delivered`/`retrying`/`failed`/`dead` | 1 天 |
| `queue:payload:{provider}:{groupId}:{id}` | 暂存供队列消费者读取的超大 webhook 负载 | 1 天 |
| `nonce:{nonce}` | 自定义 webhook 重放防护 nonce一次性 | 600 秒 |
| `tenant:{groupId}` | 分组 webhook secret64 位 hex控制台生成 | 永久 |
| `msg:{routeId}:{key}:{target}` | 原地更新用消息 id 追踪(如 `workflow_run` / `check_run` | 7 天 |
| `cmd:guild:{id}` | 已注册命令的服务器 id去重 | 永久 |
| `cmd:registered:global` | 全局命令注册标记(去重) | 1 天 |