消息推送后台打通

This commit is contained in:
not-like-juvenile
2026-03-09 10:39:29 +08:00
parent 427010f7db
commit 436b7b251f
11 changed files with 466 additions and 291 deletions

View File

@@ -1,5 +1,101 @@
如需我在本地使用你的 Supabase 凭证演示一次完整的注册→查询→发送流程,或将持久化切换为仅 Supabase移除本地 JSON 回退),请直接告诉我你的选择并提供测试凭证或确认权限范围。
## Cloud 函数 & 消费者(自动调用)
- 说明:`server/push-server.js` 并非由 Supabase 在插入时直接触发云函数;当启用消费者(轮询)时,服务会定期读取 Supabase 表 `express_notifications` 的待处理记录,然后对配置的 `CLOUD_FUNC_URL` 发起 POST 请求。
- 关键代码位置(仓库):
- 轮询待推送记录: [server/push-server.js](server/push-server.js#L271-L286) `fetchPendingNotifications`
- 调用云函数的实现: [server/push-server.js](server/push-server.js#L313-L323) `invokeCloudFuncForCid`
- 在处理记录時对每个 `cid` 调用云函数: [server/push-server.js](server/push-server.js#L360-L370)
- 启动消费者(定时轮询): [server/push-server.js](server/push-server.js#L417-L418) `setInterval(consumerOnce, CONSUMER_POLL_MS)`
- 写入通知的 HTTP 接口(会插入 `express_notifications` [server/push-server.js](server/push-server.js#L594-L610) `POST /api/v1/notifications`
- 所需/可选环境变量:
- 必要:`SUPA_URL``SUPA_KEY`(或 `SERVICE_ROLE_KEY`,用于读取/写入 Supabase
- 启用消费者:`ENABLE_CONSUMER=true``CONSUMER_ENABLED=true`
- 云函数地址:`CLOUD_FUNC_URL`(每个目标 `cid` 会对该 URL 发 POST
- 可选鉴权透传:`PUSH_TOKEN`(会在 POST body 的 `token` 字段中传递)
- 轮询与重试配置:`CONSUMER_POLL_MS``MAX_RETRIES``RETRY_INITIAL_MS``RETRY_FACTOR``RETRY_MAX_MS`
- POST 请求体(发送到 `CLOUD_FUNC_URL`
```json
{
"token": "(来自 env:PUSH_TOKEN 或 null)",
"push_clientid": "目标 cid",
"title": "通知标题",
"content": "通知内容",
"payload": { }
}
```
- 成功判定:云函数应返回 HTTP 2xx服务会把非 2xx 或网络错误视为失败并按重试策略重试或标记失败)。
- 快速启用示例PowerShell
```powershell
$env:SUPA_URL="https://your-supabase.example"
$env:SUPA_KEY="your-service-role-key"
$env:CLOUD_FUNC_URL="https://your-cloudfunc.example/handle"
$env:ENABLE_CONSUMER="true"
node server/push-server.js
```
- 本地测试:直接调用云函数验证可达性:
```bash
curl -X POST https://your-cloudfunc.example/handle -H "Content-Type: application/json" \
-d '{"token":"test","push_clientid":"CID123","title":"测试","content":"hello","payload":{}}'
```
- 通过完整链路测试(写入通知 -> 消费者轮询 -> 云函数 POST
> 注意:`POST /api/v1/notifications` 在“仅云函数模式”下只负责把通知写入 `express_notifications`(排队),不会在该请求内立即下发;实际下发由消费者轮询后对 `CLOUD_FUNC_URL` 执行 POST。
> 前置条件:目标用户/商户必须在 `push_devices` 表(或本地 `server/data/push_devices.json`)中存在至少一个 `is_active=true` 的设备,否则该条通知会被标记为 `no-targets`。
```bash
curl -X POST http://localhost:7301/api/v1/notifications \
-H "Content-Type: application/json" \
-d '{"aud":"user","recipient_id":123,"notification":{"title":"测试","body":"hello"},"payload":{}}'
```
- 端到端验证推荐步骤PowerShell
```powershell
# 1) 注册一个设备cid + user_id写入 push_devices
Invoke-RestMethod -Uri 'http://localhost:7301/api/v1/push/register' -Method POST -ContentType 'application/json' -Body (@{
cid='CID_TEST_001';
user_id='a8e3a568-fc1f-4237-bcc5-5722e2fca0a3';
platform='android'
} | ConvertTo-Json)
# 2) 写入一条通知到 express_notifications排队
$body = @{
aud='user'
recipient_id='a8e3a568-fc1f-4237-bcc5-5722e2fca0a3'
notification=@{ title='测试'; body='hello' }
payload=@{ order_id='123' }
} | ConvertTo-Json -Depth 6
Invoke-RestMethod -Uri 'http://localhost:7301/api/v1/notifications' -Method POST -ContentType 'application/json' -Body $body
# 3) 等待 2 秒CONSUMER_POLL_MS=2000观察 push-server 控制台:应出现对 CLOUD_FUNC_URL 的 POST
```
- 如何确认是否已处理:在 Supabase 查询最近记录的 `status_code` / `last_error`
```sql
select id, message_id, status_code, retry_count, last_error, updated_at
from public.express_notifications
order by created_at desc
limit 10;
```
- 常见现象解释:
- push-server 日志里 `supaFetch response preview: []`:表示当前没有 pending/retrying 且到期的记录可处理(队列为空)。
- 注意:如果你需要“插入时立即触发云函数”的实时行为,可考虑将轮询改为 Supabase Realtime 订阅或使用 Supabase 的 Edge Function / webhook 触发器;我可以协助把轮询替换为实时订阅的示例实现。
---
## 故障排查记录(已执行)
@@ -41,7 +137,7 @@ Invoke-RestMethod -Uri 'http://localhost:7301/api/v1/push/devices?user_id=<user-
### B绕过 Supabase仅用于本地快速验证
如果你只是想立即验证下发链路,可把已知的 devicecid + user_id写入本地 `server/data/push_devices.json`然后执行 mock 推送
如果你只是想立即验证“从服务端发起 -> 调用云函数”的链路,可把已知的 devicecid + user_id写入本地 `server/data/push_devices.json`并确保已配置 `CLOUD_FUNC_URL`,然后调用 `/api/v1/push/send`(会直接 POST 到云函数)
```powershell
# 查看本地 devices 文件
@@ -52,7 +148,7 @@ $devs = Get-Content .\server\data\push_devices.json -Raw | ConvertFrom-Json
$devs += @{ cid='d9aa69ec415...'; user_id='a8e3a568-fc1f-4237-bcc5-5722e2fca0a3'; platform='android'; created_at=(Get-Date).ToString('o'); updated_at=(Get-Date).ToString('o'); active=$true }
$devs | ConvertTo-Json -Depth 5 | Set-Content .\server\data\push_devices.json -Encoding utf8
# 然后测试发送(mock
# 然后测试发送(会直接调用 CLOUD_FUNC_URL
Invoke-RestMethod -Uri 'http://localhost:7301/api/v1/push/send' -Method POST -ContentType 'application/json' -Body (@{ user_id='a8e3a568-fc1f-4237-bcc5-5722e2fca0a3'; notification=@{ title='测试'; body='hello' } } | ConvertTo-Json)
```
@@ -70,9 +166,15 @@ Invoke-RestMethod -Uri 'http://localhost:7301/api/v1/push/send' -Method POST -Co
- 注册/更新设备:`POST /api/v1/push/register` { cid, user_id, platform }
- 注销设备:`POST /api/v1/push/unregister` { cid | user_id }
- 列出设备:`GET /api/v1/push/devices?user_id=...&active=true|false`
- 发送推送(模拟`POST /api/v1/push/send` { cids:[], user_id, notification, payload }
- 发送推送(云函数模式`POST /api/v1/push/send` { cids:[], user_id, notification, payload }(直接 POST 到 `CLOUD_FUNC_URL`
如果你有真实的推送服务端 API可以设置环境变量 `PUSH_PROXY_URL`(和可选的 `PUSH_PROXY_TOKEN`),服务器会将 `/api/v1/push/send` 请求代理到该 URL。
<!--
仅云函数模式:本仓库当前已将 push-server 改为只走 CLOUD_FUNC_URL。
历史能力(已注释/不再使用):
- PUSH_PROXY_URL / PUSH_PROXY_TOKEN将 /api/v1/push/send 代理到真实推送服务
- UNI_PUSH_URL / UNI_PUSH_*:直接调用 dCloud uni-push HTTP 接口
-->
快速使用:
@@ -135,7 +237,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS ux_push_devices_appid_cid ON public.push_devic
```bash
curl -X POST "${SUPA_URL}/rest/v1/push_devices?on_conflict=cid" \
-H "apikey: ${SUPA_KEY}" \
-H "Authorization: Bearer ${SUPA_KEY}" \
# 如需发送 Authorization: Bearer,请设置 SUPA_USE_BEARER=true默认只发送 apikey
-H "Content-Type: application/json" \
-d '[{"cid":"CID_TEST_001","user_id":"<user-uuid>","platform":"android","appid":"default","is_active":true}]'
```
@@ -158,7 +260,7 @@ curl -X POST "${SUPA_URL}/rest/v1/push_devices?on_conflict=cid" \
- `POST /api/v1/push/register`注册或更新设备body: `{ cid, user_id, platform, ... }`
- `POST /api/v1/push/unregister`注销设备body: `{ cid | user_id }`
- `GET /api/v1/push/devices`列出设备query: `user_id`, `active`
- `POST /api/v1/push/send`:发送推送(开发环境为 mock可代理到 `PUSH_PROXY_URL`
- `POST /api/v1/push/send`:发送推送(仅云函数:直接调用 `CLOUD_FUNC_URL`;未配置则返回错误
**数据与持久化**
- 开发默认将设备保存在本地文件:`server/data/push_devices.json`(回退/离线使用)。
@@ -166,8 +268,9 @@ curl -X POST "${SUPA_URL}/rest/v1/push_devices?on_conflict=cid" \
**重要环境变量**
- `PORT`:监听端口(默认 7301
- `PUSH_PROXY_URL` / `PUSH_PROXY_TOKEN`:将 `/api/v1/push/send` 代理到真实推送服务
- `SUPA_URL` / `SUPA_KEY` / `SUPA_SCHEMA`:启用 Supabase 同步(`SUPA_KEY` 应为服务端 `service_role`,仅服务器端使用)
- `SUPA_USE_BEARER`:(可选)仅当为 `true` 时才发送 `Authorization: Bearer <SUPA_KEY>`;默认只发送 `apikey`
- `CLOUD_FUNC_URL` / `PUSH_TOKEN`:云函数调用地址 / (可选)鉴权 token
**安全与部署注意**
- 切勿将 `SUPA_KEY`service_role暴露给客户端只能在后端使用。