Files
medical-mall/server/PUSH_SERVER_README.md
not-like-juvenile 427010f7db 云服务推送
2026-02-27 16:02:44 +08:00

301 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
**Push Server - 使用与变更说明**
简要说明
- 该文档记录对 `server/push-server.js` 的修改、运行所需的环境变量、表结构依赖、以及如何把 Supabase 的 cid 与通知通过 dCloud UNIPUSH 下发的端到端操作步骤。
变更要点(代码修改摘要)
- supaFetch: 默认仅发送 `apikey`;仅当 `SUPA_USE_BEARER=true``SUPA_KEY` 看起来像 JWT包含两处 ".")时,才发送 `Authorization: Bearer`。避免把明文 service key 当作 JWT 发出导致 PostgREST 拒绝。
- 新增 endpoint `/api/v1/notifications`:将通知写入 `express_notifications`,根据 `aud``recipient_id` 查询 `push_devices`再发送推送proxy 或 mock并写回通知状态。
- 新增 uni-push adapter `sendToUniPush(targets, notification, payload)`:当设置了 `UNI_PUSH_URL` 时,`/api/v1/push/send``/api/v1/notifications` 会调用该适配器优先发送到 UNIPUSH否则若设置了 `PUSH_PROXY_URL` 则转发到该 URL。
新增/修改的接口(简要)
- GET `/health` — 健康检查。
- POST `/api/v1/push/register` — 注册/更新设备;会写本地 `server/data/push_devices.json`,并尝试 upsert 到 Supabase `push_devices` 表(如果配置了 SUPA_URL + SERVICE_ROLE_KEY
- POST `/api/v1/push/unregister` — 注销设备(本地并尝试同步 Supabase
- GET `/api/v1/push/devices` — 列出设备(优先从 Supabase 获取)。
- POST `/api/v1/push/send` — 直接按 `cids``user_id` 发送推送;若 `UNI_PUSH_URL` 存在使用 adapter否则若 `PUSH_PROXY_URL` 存在转发,默认 mock 返回。
- POST `/api/v1/notifications` — 将通知写入 `express_notifications` 并基于 `aud`/`recipient_id` 拉取 `push_devices` 发推送,成功/失败状态写回 `express_notifications.status_code`
依赖的数据库表(必须存在)
- `public.push_devices`:用于存储设备 cid、user_id/merchant_id、is_active 等(见仓库迁移脚本 `20260224_add_push_devices_and_notifications.sql`)。
- `public.express_notifications`:用于保存通知记录与状态(见迁移脚本)。
关键环境变量(示例与说明)
- SUPA_URL — Supabase RESTPostgREST地址内部建议 `http://rest:3000`)。
- SERVICE_ROLE_KEY 或 SUPA_KEY — 用作 `apikey` 向 PostgREST 请求(不要把明文放到 Authorization 除非该值确为 JWT
- SUPA_USE_BEARER — (可选) 若为 `true` 则强制发送 Authorization: Bearer <SUPA_KEY>。
- UNI_PUSH_URL — (可选) 若设置则使用内置 `sendToUniPush` adapter 直接调用 dCloud uni-push 接口。
- UNI_PUSH_APPID / UNI_PUSH_SECRET — adapter 用于构造或鉴权(按你现有 uni-push 接口调整)。
- PUSH_PROXY_URL / PUSH_PROXY_TOKEN — 若不使用 adapter可把此设置为你现有的推送代理 URL 与 token后端会将 {targets, notification, payload} 转发过去。
- PUSH_PROXY_URL 优先级低于 UNI_PUSH_URL若 UNI_PUSH_URL 存在,优先使用本地 adapter。
运行与测试(本地示例)
1) 安装依赖并启动:
```bash
cd server
npm install express body-parser cors node-fetch
SUPA_URL='http://rest:3000' SERVICE_ROLE_KEY='PASTE_SERVICE_ROLE_KEY' node push-server.js
```
2) 健康检查:
```bash
curl http://localhost:7301/health
# 返回 {"ok":true}
```
3) 注册设备(后端会写本地并尝试写 Supabase
```bash
curl -X POST http://localhost:7301/api/v1/push/register \
-H 'Content-Type: application/json' \
-d '{"cid":"test-cid-1","user_id":"<USER_UUID>","platform":"android"}'
```
4) 按 user 发通知(写入 express_notifications 并触发推送):
```bash
curl -X POST http://localhost:7301/api/v1/notifications \
-H 'Content-Type: application/json' \
-d '{"aud":"user","recipient_id":"<USER_UUID>","notification":{"title":"测试","body":"uni-push 测试"}}'
```
5) 直接按 cid 发(跳过 DB
```bash
curl -X POST http://localhost:7301/api/v1/push/send \
-H 'Content-Type: application/json' \
-d '{"cids":["test-cid-1"],"notification":{"title":"hi","body":"msg"}}'
```
UNIPUSH 集成注意事项
- adapter 当前构造 body 使用 `cidList``message:{title,content,payload}`。请根据你已经验证成功的 uni-push curl 请求体调整字段名与鉴权 header可使用 `UNI_PUSH_APPID``UNI_PUSH_SECRET`、或 `PUSH_PROXY_TOKEN`)。
- 建议:把你成功的 uni-push curl 发给我,我可以把 adapter 的 body/header 精确改成一致格式。
故障与排查要点
- 如果 Supabase 报 401 或 PGRST301不要把明文 service key 作为 Bearer使用 `apikey` header或生成并使用与 `PGRST_JWT_SECRET` 匹配的 JWT。可通过 `docker inspect` 检查容器 env 中的 `PGRST_JWT_SECRET`
- 如果 `/rest/v1/push_devices` 返回 404确认表名在 `public` schema 中并加载,或调整请求前缀。
- 查看 push-server 控制台输出中的 `supaFetch` warn 和 proxy 响应体以获取具体错误信息。
后续建议(可选实现)
-`express_notifications` 增加 `attempts``error``sent_at` 字段以支持重试与错误记录;可实现后台 worker 或 pg_notify+listener 做可靠投递与重试。
-`/api/v1/push/send``/api/v1/notifications` 添加管理员鉴权(例如 `PUSH_ADMIN_KEY`)以限制谁能发送通知。
文件位置
- 文档:[server/PUSH_SERVER_README.md](server/PUSH_SERVER_README.md)
- 代码:[server/push-server.js](server/push-server.js)
自动化部署(可选)
- 本仓库新增了一个打包并可选上传云函数的脚本:`server/tools/deploy-cloudfunc.js`,会把
`uniCloud-alipay/cloudfunctions/testUnipush2` 打包到 `server/dist/testUnipush2.zip`
- 使用前请在 `server` 目录安装依赖:
```bash
cd server
npm install archiver node-fetch form-data
```
- 本地打包(只生成 zip
```bash
node server/tools/deploy-cloudfunc.js
```
- 如果你有云平台的上传 URL可直接上传脚本会读取 `CLOUD_UPLOAD_URL` 与 `CLOUD_UPLOAD_TOKEN` 环境变量,或使用 `--upload <URL>` 参数):
```bash
export CLOUD_UPLOAD_URL='https://your-cloud-upload-api'
export CLOUD_UPLOAD_TOKEN='xxxxx'
node server/tools/deploy-cloudfunc.js --upload
```
- 我也添加了一个 GitHub Actions 模板(.github/workflows/deploy-cloudfunc.yml可在仓库的 `main` 分支 push 时自动打包并上传(需要在 Actions Secrets 中配置 `CLOUD_UPLOAD_URL` 与 `CLOUD_UPLOAD_TOKEN`)。
CI / 自动化 说明
- 已提供 GitHub Actions workflow: [.github/workflows/deploy-cloudfunc.yml](.github/workflows/deploy-cloudfunc.yml)。支持可选步骤:上传 -> 触发部署 API -> 调用云函数做 smoke test。
- 推荐在仓库 Secrets 中添加(根据你云厂商调整):
- `CLOUD_UPLOAD_URL` : 上传 zip 的 HTTP endpointupload 接口)
- `CLOUD_UPLOAD_TOKEN` : 上传接口的 Bearer token
- `CLOUD_DEPLOY_API` : (可选)触发云函数部署/发布的 API
- `CLOUD_DEPLOY_TOKEN` : (可选)部署 API 的 token
- `CLOUD_FUNC_URL` : (可选)已部署云函数的外网 URLCI 将对其执行一次 smoke 测试
- `PUSH_TOKEN` : (可选)云函数鉴权 token用于 smoke 测试
- `TEST_DEVICE_CID` : (可选)用于 CI smoke 测试的设备 CID
- 我也添加了本地 helper 脚本:`server/tools/ci-deploy.ps1`,可在本地执行相同流程(打包 -> 上传 -> 触发 -> 调用)。
如果需要,我可以:
- 把 adapter 的请求体精确匹配你现有成功的 uni-push curl请把 curl 发来);或
- 为通知添加重试/记录字段并实现简单重试机制。
自动部署成功示例
-----------------
下面是一个你实际执行并确认成功的示例,便于复制到文档或在团队内复现:
- 启用自动部署并设置 tokenPowerShell 示例):
```powershell
$env:AUTO_DEPLOY_ON_START='true'
$env:DEPLOY_BEARER='your-secret' # 请使用真实随机 token 暂无
$env:CLOUD_UPLOAD_URL='https://env-00jy5x5oy9zd.dev-hz.cloudbasefunction.cn/test'
$env:CLOUD_UPLOAD_TOKEN='upload-token' # 如果上传需要鉴权
$env:AUTO_DEPLOY_ARGS='--upload' # 可选:将 --upload 传给 deploy 脚本
node push-server.js
```
- 预期控制台输出(已在你的环境中观察到):
```
Push server listening on http://0.0.0.0:7301
ENV: UNI_PUSH_URL= https://restapi.getui.com/v2/...
Auto-deploy: spawning node D:\...\server\tools\deploy-cloudfunc.js
[auto-deploy stdout] 打包目录: D:\...\uniCloud-alipay\cloudfunctions\testUnipush2
[auto-deploy stdout] 打包完成 -> D:\...\server\dist\testUnipush2.zip (1324 bytes)
[auto-deploy stdout] 上传到: https://env-00jy5x5oy9zd.dev-hz.cloudbasefunction.cn/test
[auto-deploy stdout] 上传响应: 200 {"data":{...},"errCode":0,"errMsg":"success"}
Auto-deploy process exited with code=0 signal=null
```
- 说明:
- `errCode:0` / `errMsg: "success"` 表示云平台已接收并下发(上线)该云函数包。具体字段会随云厂商略有不同,请以返回的 `errCode` / `errMsg` 或 `status` 为准。
- 若你需要进一步验证云函数可用性,请用 `curl` 或 `Invoke-RestMethod` 调用返回的云函数 URL 做一次 smoke test见上文的“直接调用云函数smoke test”示例
安全提醒:自动部署具有上传并触发发布的能力,请仅在受控环境或 CI 中启用,并把 `DEPLOY_BEARER` / `CLOUD_UPLOAD_TOKEN` 等密钥放入 CI Secrets 或受限的环境变量中。
快速上手(最小环境变量)
- 如果你只是想快速上传并通过云函数下发一次推送,最小只需设置两个环境变量并运行打包脚本:
PowerShell 示例:
```powershell
$env:CLOUD_UPLOAD_URL='https://env-00jy5x5oy9zd.dev-hz.cloudbasefunction.cn/test'
$env:UNI_PUSH_APPID='__UNI__9462CA7'
node tools\deploy-cloudfunc.js --upload
```
说明:设置 `CLOUD_UPLOAD_URL` 与 `UNI_PUSH_APPID` 后,运行 `deploy-cloudfunc.js` 会把 `uniCloud-alipay/cloudfunctions/testUnipush2` 打包并上传到该地址,上传成功后云平台会返回已下发/上线的结果,从而完成推送通路的测试。
**用户整合指南(一步到位)**
下面把之前讨论过的流程与命令整合成一个可复用的清单,便于在本地或 CI 中自动化执行:
- 先决条件:在 `server` 目录下有 `tools/deploy-cloudfunc.js`,并且已安装依赖 `archiver node-fetch form-data`。
1) 本地打包并上传(最小)
PowerShell
```powershell
cd D:\叶\桌面文件\hfkj_mall\mall\server
npm install archiver node-fetch form-data
$env:CLOUD_UPLOAD_URL='https://env-00jy5x5oy9zd.dev-hz.cloudbasefunction.cn/test'
$env:UNI_PUSH_APPID='__UNI__9462CA7'
node tools\deploy-cloudfunc.js --upload
```
说明:这两条环境变量(`CLOUD_UPLOAD_URL` + `UNI_PUSH_APPID`)在多数场景下已足够完成上传并让云平台下发推送;上传成功时脚本会打印上传后的 URL 与平台响应(检查返回的 `errCode` / `data` 字段以确认)。
2) 直接调用云函数smoke test
PowerShell使用 `curl.exe` 保持与 Linux curl 行为一致):
```powershell
curl.exe -X POST "https://env-.../test" `
-H "Content-Type: application/json" `
-d "{\"token\":\"<PUSH_TOKEN>\",\"push_clientid\":\"<DEVICE_CID>\",\"title\":\"测试\",\"content\":\"hi\",\"payload\":{}}"
```
或用 PowerShell 原生:
```powershell
$body = @{ token = '<PUSH_TOKEN>'; push_clientid = '<DEVICE_CID>'; title='测试'; content='hi'; payload=@{} } | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri 'https://env-.../test' -ContentType 'application/json' -Body $body
```
3) 本地辅助脚本(整合流程)
- 已添加 `server/tools/ci-deploy.ps1`,支持:打包、上传、触发部署 API、调用云函数做 smoke test。用法示例
```powershell
# 只打包
.\server\tools\ci-deploy.ps1 -Pack
# 打包并上传并触发部署
.\server\tools\ci-deploy.ps1 -Pack -UploadUrl 'https://your-upload' -UploadToken 'token' -DeployApi 'https://your-deploy-api' -DeployToken 'token'
# 打包并上传然后调用云函数做 smoke test
.\server\tools\ci-deploy.ps1 -Pack -UploadUrl 'https://your-upload' -UploadToken 'token' -FuncInvokeUrl 'https://env-.../test' -PushToken 'xxx' -TestCid 'device-cid'
```
4) CIGitHub Actions
- workflow: `.github/workflows/deploy-cloudfunc.yml` 已包含:打包 -> 上传 ->(可选)触发部署 API ->(可选)调用云函数做 smoke test。
- 建议在 Actions Secrets 中添加:
- `CLOUD_UPLOAD_URL`(必需用于上传)
- `CLOUD_UPLOAD_TOKEN`(上传 token
- `CLOUD_DEPLOY_API` / `CLOUD_DEPLOY_TOKEN`(可选,用于触发厂商部署)
- `CLOUD_FUNC_URL` / `PUSH_TOKEN` / `TEST_DEVICE_CID`(可选,用于 smoke test
5) 后端直接单推(如果你愿意绕过云函数)
- 启动 `push-server`(示例):
```powershell
cd D:\叶\桌面文件\hfkj_mall\mall\server
npm install
SUPA_URL='http://rest:3000' SERVICE_ROLE_KEY='PASTE_SERVICE_ROLE_KEY' node push-server.js
```
- 直接按 CID 发送(跳过 DB
```powershell
curl.exe -X POST "http://localhost:7301/api/v1/push/send" -H "Content-Type: application/json" -d "{\"cids\":[\"<DEVICE_CID>\"],\"notification\":{\"title\":\"hi\",\"body\":\"msg\"}}"
```
注意与安全建议
- `uniCloud.uploadFile` 可以把 zip 存到云端存储(返回 `fileID`),但不会自动部署为云函数;若要自动部署需要调用云厂商的部署/发布 API或使用厂商控制台/CLI
- 保管好 `CLOUD_UPLOAD_TOKEN`、`CLOUD_DEPLOY_TOKEN`、`SERVICE_ROLE_KEY` 等机密,放到 CI Secrets 或安全环境变量中,不要硬编码在仓库。
- 上传后若要确认运行状态,请查看云平台函数控制台或调用厂商的状态/日志接口CI 的 smoke test 能快速验证下发路径是否通顺。
如果你希望,我可以把上面示例中的 `curl` 调用替换成你厂商的部署 API 或把 `ci-deploy.ps1` 改为更严格的错误处理与日志输出。
部署服务文件说明
----------------
以下两个文件用于让后端通过一个 HTTP 接口自动完成云函数的打包、上传、触发部署与可选的 smoke-test
- `server/tools/deploy-cloudfunc-service.js`
- 目的封装打包zip、上传、触发部署 API、调用云函数smoke-test的通用函数便于从后端代码调用或在 CI 中复用。
- 主要导出:`deployCloudFunction(options)`,接收的 `options` 支持字段:
- `uploadUrl`(必需或由环境变量提供):上传 zip 的 HTTP endpoint。
- `uploadToken`:上传接口的 Bearer token可选
- `uniAppId`:可附加到上传请求的 appId可选
- `deployApi` / `deployToken`:如果需要调用厂商的部署/发布 API可指定可选
- `funcInvokeUrl` / `pushToken` / `testCid`:若提供,将在上传/部署后调用该云函数进行 smoke-test可选
- 返回值:一个对象,包含 `packed`(打包信息)、`uploaded`(上传响应)、`deployed`(触发部署 API 的响应)和 `invoked`smoke-test 调用响应)四部分,便于在后端记录和判断各个阶段的结果。
- `server/routes/deploy.js`
- 目的:提供一个 Express 路由 `/api/v1/deploy-cloudfunc`,让后端或 CI 通过一次 HTTP POST 调用完成上面封装的流程。
- 请求体示例JSON
```json
{
"uploadUrl":"https://your-upload-endpoint",
"uploadToken":"<UPLOAD_TOKEN>",
"uniAppId":"__UNI__9462CA7",
"deployApi":"https://your-deploy-api",
"deployToken":"<DEPLOY_TOKEN>",
"funcInvokeUrl":"https://env-.../test",
"pushToken":"<PUSH_TOKEN>",
"testCid":"<DEVICE_CID>"
}
```
- 行为:路由优先使用请求体中的字段;若缺失则回落使用环境变量(例如 `CLOUD_UPLOAD_URL`、`CLOUD_UPLOAD_TOKEN`、`UNI_PUSH_APPID`、`CLOUD_DEPLOY_API`、`CLOUD_DEPLOY_TOKEN`、`CLOUD_FUNC_URL`、`PUSH_TOKEN`、`TEST_DEVICE_CID`)。
- 返回JSON 格式,包含 `result` 对象(见 `deployCloudFunction` 的返回结构)。
安全与接入建议
- 强烈建议只在内网或受保护的管理接口上暴露该路由,或在请求中加入鉴权头(例如 `Authorization: Bearer <ADMIN_TOKEN>`)并在服务端验证。此接口具有上传并触发部署的能力,若未保护将导致安全风险。
- 如果你需要,我可以:
- 把路由自动挂载到 `server/push-server.js`(并添加简单的 token 验证);或
- 增强 `deploy-cloudfunc-service.js` 的错误日志与重试策略,或把上传/部署调用改写成你云厂商的精确 API 参数格式。
异步 Consumer已实现说明
---------------------------------
- 目的:从数据库 `express_notifications` 拉取待发送的消息pending解耦写入与下发流程保证可重试、可审计与可运维。
- 实现位置:`server/push-server.js`(已新增 consumer 逻辑)。
- 启用:设置环境变量 `ENABLE_CONSUMER=true`(或 `CONSUMER_ENABLED=true`),可选配置轮询间隔 `CONSUMER_POLL_MS`(默认 2000 ms
- 关键环境变量:`SUPA_URL`、`SUPA_KEY`Supabase REST、`CLOUD_FUNC_URL`(云函数 invoke URL、`PUSH_TOKEN`(云函数鉴权)。
- 行为摘要:
- 轮询 `express_notifications`status_code IS NULL并选取记录
- 通过带过滤的 PATCH 抢占(将 `status_code` 设为 `processing`)以避免并发重复处理;
- 查询目标设备(`push_devices`),对每个 `cid` 构造 event 并 POST 到 `CLOUD_FUNC_URL`(若未配置则回退到 `UNI_PUSH_URL` 适配器);
- 根据调用结果回写 `express_notifications.status_code` 为 `success` / `failed` / `no-targets`。
- 限制与扩展点:当前 consumer 依赖 Supabase REST尚未在 DB 中新增 `retry_count`/`last_error` 字段(建议在迁移中加入以支持指数退避与重试);为保证高可用建议配合 `FOR UPDATE SKIP LOCKED` 或 Supabase Realtime 优化并发策略。
文档实践
---------------------------------
- 变更策略:今后每次由自动化工具或我生成/修改后端代码时,将同步把该变更的作用、配置项与启用方法记录到本文件(`server/PUSH_SERVER_README.md`)或对应 workflow 文档中,确保运行/运维人员能直接查阅到最新说明。