推送真发送:webhook/slack 内置真实发送端 + worker 终态自动通知

此前订阅与分发框架都在,但分发端点传 sender=None,
webhook/slack 永远报无 sender、从未真发过。本轮:
- push:send_webhook/send_slack(标准库 urllib POST,
  单次 10 秒超时,不重试,失败记 errors);sender=None
  时用内置端,显式 sender 优先;webpush/apns/fcm 仍报
  缺凭证(原因写明:webpush 需浏览器端三字段订阅与标准库
  没有的 P-256 ECDH + AES-GCM;apns 需 HTTP/2;fcm 需
  Firebase 服务账号,三者均无现成接收方,不造假发送)
- worker:run_one 终态(completed/failed,缺 key 与仓库
  失败两条早退路径也在内)后 best-effort 调分发,通知
  失败只记日志、绝不影响任务状态;取消(interrupted)
  不在事件集合里,不通知
- 测试:真实发送端 6 个(本地回环服务器)、worker 终态
  通知 2 个,旧无 sender 断言按新语义改写
This commit is contained in:
host committed 2026-10-26 17:31:30 +08:00
1 parent b7dace55b9
commit fb3a311da8
7 files changed
+429 -29

No files matched your search

+19 -7
View File
@@ -222,12 +222,24 @@ webhook 实测)。
### 推送凭证清单
- webhook / slack:需要可注入 sender(worker 侧),HTTP 层没有
sender 时明确报错,不静默成功。
- webpush:需要 VAPID 密钥对(私钥绝不进仓库)。
- apns:需要 APNs 证书。
- fcm:需要 FCM key。
以上三者目前只有接口 + 缺凭证报错,没有真实发送端。
- webhook / slack:已有内置真实发送端(标准库 urllib POST,
单次 10 秒超时,不重试,失败记入分发结果的 errors):
webhook 投 JSON(event_type、project_id、task_id、payload),
slack 投 Slack incoming webhook 格式的一行文本(任务 id +
事件 + 项目,失败附 error 摘要)。任务终态(completed /
failed)由 worker 在 run_one 的终态写回后自动通知订阅者;
手动分发走 `POST /admin/push/dispatch`,同样经内置发送端。
显式注入的 sender 优先于内置端。
- webpush:需要 VAPID 密钥对(私钥绝不进仓库),且需要
浏览器端订阅(endpoint + p256dh + auth,现有订阅模型只有
一个 target 文本字段,装不下)与标准库没有的 P-256 ECDH +
AES-GCM——只有接口 + 缺凭证报错,没有真实发送端。
- apns:需要 APNs 证书与标准库没有的 HTTP/2——只有接口 +
缺凭证报错。
- fcm:需要 FCM key 与用户另建的 Firebase 服务账号——只有
接口 + 缺凭证报错。
以上三者目前只有接口 + 缺凭证报错,没有真实发送端,也
不编造推送。
### SQLite 并发上限
@@ -374,7 +386,7 @@ psycopg,写明“需安装 psycopg”),绝不静默回落到 SQLite,
| `POST /projects/{id}/push/subscriptions` | 该项目 `create_task` | 记订阅 body `{channel, target, preference?}`,channel 为 webhook/slack/webpush/apns/fcm,preference 为 instant/digest/silent,返回 `201` |
| `GET /projects/{id}/push/subscriptions` | 该项目 `read` | 只列调用者自己的订阅(不泄露他人 target) |
| `DELETE /projects/{id}/push/subscriptions/{sid}` | 该项目 `create_task` | 只能删自己的订阅,项目 owner 可删任意;不存在 `404`,他人订阅 `403` |
| `POST /admin/push/dispatch` | 该项目 `read` | body `{event_type, project_id, payload?}`;只发状态变化事件(completed/failed/goal-unmet/merge-failed/review-requested);HTTP 层无注入 sender:webhook/slack 报“无 sender”,webpush/apns/fcm 报缺凭证,都不静默成功;真实发送端由 worker 注入 sender 后调 `push.dispatch_event` |
| `POST /admin/push/dispatch` | 该项目 `read` | body `{event_type, project_id, payload?}`;只发状态变化事件(completed/failed/goal-unmet/merge-failed/review-requested);sender=None 时用内置真实发送端(webhook/slack 真 POST,webpush/apns/fcm 报缺凭证),都不静默成功;worker 在任务终态(completed/failed)时自动调分发,不依赖手动调本端点 |
| `POST /webhooks/github` | GitHub 签名 | 见“webhook 端点” |
| `POST /webhooks/slack` | Slack 签名 | 见“webhook 端点” |