Files
medical-mall/server/PUSH_SERVER_README.md
2026-03-09 17:27:56 +08:00

390 lines
21 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 与通知通过云函数(`CLOUD_FUNC_URL`)下发的端到端操作步骤。
变更要点(代码修改摘要)
- supaFetch: 默认仅发送 `apikey`;仅当 `SUPA_USE_BEARER=true` 时才发送 `Authorization: Bearer`。用于避免自托管 Supabase/Kong 场景下因 JWT_SECRET 不一致触发 `PGRST301`
- 新增 endpoint `/api/v1/notifications`:将通知写入 `express_notifications`(排队);由消费者(轮询)读取待处理记录并 POST 到 `CLOUD_FUNC_URL`
- 仅云函数模式:`server/push-server.js` 已禁用 UNIPUSH/代理分支,发送只走 `CLOUD_FUNC_URL`
<!--
历史能力(已注释/不再使用):
- uni-push adapter sendToUniPush(targets, notification, payload)
- PUSH_PROXY_URL / PUSH_PROXY_TOKEN 转发
-->
新增/修改的接口(简要)
- 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` 发送推送(仅云函数:对每个 cid POST 到 `CLOUD_FUNC_URL`)。
- POST `/api/v1/notifications` — 将通知写入 `express_notifications`(排队);实际下发由消费者完成。
依赖的数据库表(必须存在)
- `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>。
- ENABLE_CONSUMER / CONSUMER_ENABLED — 启用消费者轮询(从 express_notifications 读取待处理记录)。
- CONSUMER_POLL_MS — 轮询间隔(毫秒)。
- CLOUD_FUNC_URL — 云函数外网调用 URL每个目标 cid 会对该 URL 发 POST
- PUSH_TOKEN — (可选) 云函数鉴权 token会在 POST body 的 token 字段中透传)。
<!--
UNI_PUSH_URL / UNI_PUSH_APPID / UNI_PUSH_SECRET / PUSH_PROXY_URL / PUSH_PROXY_TOKEN仅在非云函数模式下使用当前已不再使用。
-->
运行与测试(本地示例)
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 测试"}}'
```
> 说明(仅云函数模式):`/api/v1/notifications` 仅写入 `express_notifications`(排队)。实际下发由消费者轮询后对 `CLOUD_FUNC_URL` 执行 POST。
端到端验证(推荐)
-----------------
前置条件
- `CLOUD_FUNC_URL` 可访问且返回 2xx否则会被标记为 failed/retrying
- 目标用户在 `push_devices` 中存在至少一个 `is_active=true` 的设备(否则会被标记为 no-targets
PowerShell 示例Windows
```powershell
# 1) 注册设备(写入 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) 写入通知(排队)
$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 中查询最近记录:
```sql
select id, message_id, status_code, retry_count, last_error, updated_at
from public.express_notifications
order by created_at desc
limit 10;
```
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 集成注意事项
<!--
仅云函数模式:此服务不再直接对接 UNIPUSH请在云函数内部完成 UNIPUSH2 调用与鉴权。
-->
故障与排查要点
- 如果 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 响应体以获取具体错误信息。
- 如果 `/api/v1/push/send` 返回 HTTP 200 但 body 为 `{ errCode: 400, errMsg: "push_clientid required" }`
- 含义:云函数没有拿到 `push_clientid`,因此没有真正执行 uni-push 下发。
- 常见原因:`CLOUD_FUNC_URL` 指向 HTTP 触发云函数cloudbasefunction.cn 等),请求体通常在 `event.body`(字符串)中。
- 云函数侧最小兼容写法:先把 `event.body` 解析为 JSON再从解析结果解构 `push_clientid/title/content`
- 如果手机通知标题/内容变成 `????`
- 含义中文在发出请求前已被错误编码Windows PowerShell 5.1 常见)。
- 解决:用 UTF-8 字节发送 JSON并设置 `application/json; charset=utf-8`)。示例:
```powershell
$bodyObj = @{
cids = @('YOUR_DEVICE_CID')
notification = @{ title = '测试标题'; body = '测试内容' }
payload = @{ order_id = '123' }
}
$json = $bodyObj | ConvertTo-Json -Depth 10
$bytes = [System.Text.Encoding]::UTF8.GetBytes($json)
Invoke-RestMethod -Method Post -Uri 'http://127.0.0.1:7301/api/v1/push/send' `
-ContentType 'application/json; charset=utf-8' `
-Body $bytes | ConvertTo-Json -Depth 20
```
- 备注:也可用 `curl.exe` 发送 JSON避免 PowerShell 的编码差异。
- 如果你在云函数里加了“回显 recv 并提前 return”用于排查乱码
- 排查完成后务必移除该提前 return否则云函数不会执行 `uniPush.sendMessage()`,手机将收不到通知。
后续建议(可选实现)
-`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: CLOUD_FUNC_URL configured? true
ENV: ENABLE_CONSUMER= true
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
- 轮询频率说明:默认 `CONSUMER_POLL_MS` 为 `2000`(即每 2000 毫秒2 秒轮询一次)。若需调整频率,请设置环境变量 `CONSUMER_POLL_MS` 为毫秒数。例如将间隔改为 5 秒:
```powershell
$env:ENABLE_CONSUMER="true"
$env:CONSUMER_POLL_MS="5000"
node push-server.js
```
- 关键环境变量:`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`;若未配置 `CLOUD_FUNC_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 文档中,确保运行/运维人员能直接查阅到最新说明。