消息推送后台打通
This commit is contained in:
117
server/README.md
117
server/README.md
@@ -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(仅用于本地快速验证)
|
||||
|
||||
如果你只是想立即验证下发链路,可把已知的 device(cid + user_id)写入本地 `server/data/push_devices.json`,然后执行 mock 推送:
|
||||
如果你只是想立即验证“从服务端发起 -> 调用云函数”的链路,可把已知的 device(cid + 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)暴露给客户端;只能在后端使用。
|
||||
|
||||
Reference in New Issue
Block a user