Files
medical-mall/server/消息推送文档/PUSH_SERVER_README.md
not-like-juvenile 4acbb8ced5 补充方案
2026-03-12 10:36:51 +08:00

23 KiB
Raw Blame History

Push Server - 使用与变更说明

简要说明

  • 该文档记录对 server/push-server.js 的修改、运行所需的环境变量、表结构依赖、以及如何把 Supabase 的 cid 与通知通过云函数(CLOUD_FUNC_URL)下发的端到端操作步骤。

组件补充(新增)

  • server/notify-worker.js:常驻 workernotify_queue 生成/写入 express_notifications(消息入队)。
  • 说明文档:server/NOTIFY_WORKER_README.md(包含技术栈/依赖、配置项、监控与管理者摘要)。

变更要点(代码修改摘要)

  • 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

新增/修改的接口(简要)

  • GET /health — 健康检查。
  • POST /api/v1/push/register — 注册/更新设备;会写本地 server/data/push_devices.json,并尝试 upsert 到 Supabase push_devices 表(如果配置了 SUPA_URL + SERVICE_ROLE_KEY
    • 支持字段:cid(必填)、user_id(可选)、merchant_id(可选)、platform(可选)、appid(可选,默认 default)。
    • 注意:数据库侧 push_devices 的唯一约束是 (appid, cid);服务端 upsert 使用 on_conflict=appid,cid
  • POST /api/v1/push/unregister — 注销设备(本地并尝试同步 Supabase
  • GET /api/v1/push/devices — 列出设备(优先从 Supabase 获取)。
  • POST /api/v1/push/send — 直接按 cidsuser_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:用于保存通知记录与状态(见迁移脚本)。

字段语义(从 2026-03-09 起,重要变更)

  • status_code:物流/业务状态(如 OUT_FOR_DELIVERY/DELIVERED/...),由 notify-worker 写入。
  • send_status:投递处理状态(null=待发送,processing/retrying/success/failed/no-targets),由 push-server consumer 读写。

为什么需要这个变更:引入 notify-worker 后,status_code 不再适合作为“投递状态”,否则 consumer 会捞不到记录(或把物流状态误当投递状态)。

迁移脚本(必需执行一次):

  • pages/mall/delivery/doc/需求文档/20260309_add_express_notifications_send_status.sql

消息生成(可选但推荐)

  • public.notify_queue:轨迹事件入队表(由 DB trigger 写入)。
  • 迁移脚本:pages/mall/delivery/doc/需求文档/20260309_add_notify_queue_and_trigger.sql(创建 notify_queue + AFTER INSERT trigger
  • 常驻消费者:server/notify-worker.js(从 notify_queue 生成 express_notificationspush-server consumer 再负责下发)。

关键环境变量(示例与说明)

  • 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 字段中透传)。
  • PUSH_SERVER_PORT — (可选) push-server 监听端口,默认 7301推荐用这个便于与 webhook-receiver 共享同一份 server/config.json)。

运行与测试(本地示例)

  1. 安装依赖并启动:
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
  1. 健康检查:
curl http://localhost:7301/health
# 返回 {"ok":true}
  1. 注册设备(后端会写本地并尝试写 Supabase
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","appid":"default"}'

商家端注册(写 merchant_id 维度,避免 send_status=no-targets

curl -X POST http://localhost:7301/api/v1/push/register \
  -H 'Content-Type: application/json' \
  -d '{"cid":"test-cid-2","merchant_id":"<MERCHANT_UUID>","platform":"android","appid":"default"}'
  1. 按 user 发通知(写入 express_notifications 并触发推送):
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

# 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 中查询最近记录:
select id, message_id, send_status, status_code, retry_count, last_error, updated_at
from public.express_notifications
order by created_at desc
limit 10;
  1. 直接按 cid 发(跳过 DB
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 集成注意事项

故障与排查要点

  • 如果 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)。示例:
$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 增加 attemptserrorsent_at 字段以支持重试与错误记录;可实现后台 worker 或 pg_notify+listener 做可靠投递与重试。
  • /api/v1/push/send/api/v1/notifications 添加管理员鉴权(例如 PUSH_ADMIN_KEY)以限制谁能发送通知。

文件位置

自动化部署(可选)

  • 本仓库新增了一个打包并可选上传云函数的脚本:server/tools/deploy-cloudfunc.js,会把 uniCloud-alipay/cloudfunctions/testUnipush2 打包到 server/dist/testUnipush2.zip
  • 使用前请在 server 目录安装依赖:
    cd server
    npm install archiver node-fetch form-data
    
  • 本地打包(只生成 zip
    node server/tools/deploy-cloudfunc.js
    
  • 如果你有云平台的上传 URL可直接上传脚本会读取 CLOUD_UPLOAD_URLCLOUD_UPLOAD_TOKEN 环境变量,或使用 --upload <URL> 参数):
    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_URLCLOUD_UPLOAD_TOKEN)。

CI / 自动化 说明

  • 已提供 GitHub Actions workflow: .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 示例):
$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 / errMsgstatus 为准。
    • 若你需要进一步验证云函数可用性,请用 curlInvoke-RestMethod 调用返回的云函数 URL 做一次 smoke test见上文的“直接调用云函数smoke test”示例

安全提醒:自动部署具有上传并触发发布的能力,请仅在受控环境或 CI 中启用,并把 DEPLOY_BEARER / CLOUD_UPLOAD_TOKEN 等密钥放入 CI Secrets 或受限的环境变量中。

快速上手(最小环境变量)

  • 如果你只是想快速上传并通过云函数下发一次推送,最小只需设置两个环境变量并运行打包脚本:

    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_URLUNI_PUSH_APPID 后,运行 deploy-cloudfunc.js 会把 uniCloud-alipay/cloudfunctions/testUnipush2 打包并上传到该地址,上传成功后云平台会返回已下发/上线的结果,从而完成推送通路的测试。

用户整合指南(一步到位)

下面把之前讨论过的流程与命令整合成一个可复用的清单,便于在本地或 CI 中自动化执行:

  • 先决条件:在 server 目录下有 tools/deploy-cloudfunc.js,并且已安装依赖 archiver node-fetch form-data
  1. 本地打包并上传(最小)

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 字段以确认)。

  1. 直接调用云函数smoke test

PowerShell使用 curl.exe 保持与 Linux curl 行为一致):

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 原生:

$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
  1. 本地辅助脚本(整合流程)
  • 已添加 server/tools/ci-deploy.ps1,支持:打包、上传、触发部署 API、调用云函数做 smoke test。用法示例
    # 只打包
    .\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'
    
  1. 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
  1. 后端直接单推(如果你愿意绕过云函数)
  • 启动 push-server(示例):
    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
    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_TOKENCLOUD_DEPLOY_TOKENSERVICE_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 的响应)和 invokedsmoke-test 调用响应)四部分,便于在后端记录和判断各个阶段的结果。
  • server/routes/deploy.js

    • 目的:提供一个 Express 路由 /api/v1/deploy-cloudfunc,让后端或 CI 通过一次 HTTP POST 调用完成上面封装的流程。
    • 请求体示例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_URLCLOUD_UPLOAD_TOKENUNI_PUSH_APPIDCLOUD_DEPLOY_APICLOUD_DEPLOY_TOKENCLOUD_FUNC_URLPUSH_TOKENTEST_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_MS2000(即每 2000 毫秒2 秒轮询一次)。若需调整频率,请设置环境变量 CONSUMER_POLL_MS 为毫秒数。例如将间隔改为 5 秒:

      $env:ENABLE_CONSUMER="true"
      $env:CONSUMER_POLL_MS="5000"
      node push-server.js
      
  • 关键环境变量:SUPA_URLSUPA_KEYSupabase RESTCLOUD_FUNC_URL(云函数 invoke URLPUSH_TOKEN(云函数鉴权)。

  • 行为摘要:

    • 轮询 express_notificationssend_status IS NULLsend_status='retrying' 且到期)并选取记录;
    • 通过带过滤的 PATCH 抢占(将 send_status 设为 processing)以避免并发重复处理;
    • 查询目标设备(push_devices),对每个 cid 构造 event 并 POST 到 CLOUD_FUNC_URL;若未配置 CLOUD_FUNC_URL 则本次处理将失败并写入错误原因;
    • 根据调用结果回写 express_notifications.send_statussuccess / failed / no-targets(失败时会写入 retry_count/last_error/next_attempt_at 用于退避重试)。
  • 限制与扩展点:当前 consumer 依赖 Supabase REST并发控制基于“先查后 claim”的方式实现适合 MVP。如需更强一致性/高并发,可进一步改造为 DB 端锁(FOR UPDATE SKIP LOCKED)或引入队列系统。

文档实践

  • 变更策略:今后每次由自动化工具或我生成/修改后端代码时,将同步把该变更的作用、配置项与启用方法记录到本文件(server/PUSH_SERVER_README.md)或对应 workflow 文档中,确保运行/运维人员能直接查阅到最新说明。