Files
medical-mall/server
not-like-juvenile e67016a6f4 消息推送
2026-03-10 16:39:50 +08:00
..
2026-03-10 16:39:50 +08:00
2026-02-26 09:54:21 +08:00
2026-02-27 16:02:44 +08:00
2026-03-10 16:39:50 +08:00
2026-02-27 16:02:44 +08:00
2026-03-10 16:39:50 +08:00
2026-03-09 10:39:29 +08:00
2026-02-27 16:02:44 +08:00
2026-02-26 09:54:21 +08:00
2026-03-09 10:39:29 +08:00
2026-03-10 16:39:50 +08:00
2026-03-10 16:39:50 +08:00
2026-02-27 16:02:44 +08:00
2026-03-10 16:39:50 +08:00
2026-02-26 09:54:21 +08:00
2026-03-10 16:39:50 +08:00
2026-02-27 16:02:44 +08:00
2026-03-10 16:39:50 +08:00
2026-03-10 16:39:50 +08:00
2026-02-26 09:54:21 +08:00

如需我在本地使用你的 Supabase 凭证演示一次完整的注册→查询→发送流程,或将持久化切换为仅 Supabase移除本地 JSON 回退),请直接告诉我你的选择并提供测试凭证或确认权限范围。

Cloud 函数 & 消费者(自动调用)

  • 说明:server/push-server.js 并非由 Supabase 在插入时直接触发云函数;当启用消费者(轮询)时,服务会定期读取 Supabase 表 express_notifications 的待处理记录,然后对配置的 CLOUD_FUNC_URL 发起 POST 请求。

  • 关键代码位置(仓库):

  • 所需/可选环境变量:

    • 必要:SUPA_URLSUPA_KEY(或 SERVICE_ROLE_KEY,用于读取/写入 Supabase
    • 启用消费者:ENABLE_CONSUMER=trueCONSUMER_ENABLED=true
    • 云函数地址:CLOUD_FUNC_URL(每个目标 cid 会对该 URL 发 POST
    • 可选鉴权透传:PUSH_TOKEN(会在 POST body 的 token 字段中传递)
    • 轮询与重试配置:CONSUMER_POLL_MSMAX_RETRIESRETRY_INITIAL_MSRETRY_FACTORRETRY_MAX_MS
  • POST 请求体(发送到 CLOUD_FUNC_URL

{
	"token": "(来自 env:PUSH_TOKEN 或 null)",
	"push_clientid": "目标 cid",
	"title": "通知标题",
	"content": "通知内容",
	"payload": { }
}
  • 成功判定:

    • HTTP 层面:云函数应返回 HTTP 2xx服务会把非 2xx 或网络错误视为失败并按重试策略重试或标记失败)。
    • 业务层面(推荐约定):若云函数返回 JSON 且包含 ok:falseerrCode!=0push-server 会视为失败并写入 last_error
  • 快速启用示例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
  • 本地测试:直接调用云函数验证可达性:
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

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
# 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 查询最近记录的 send_status / last_error

说明:status_code 是物流/业务状态(由 notify-worker 写入consumer 的投递状态使用 send_status。 若你的数据库还没有该字段,请先执行迁移:pages/mall/delivery/doc/需求文档/20260309_add_express_notifications_send_status.sql

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;
  • 常见现象解释:
    • push-server 日志里 supaFetch response preview: []:表示当前没有 pending/retrying 且到期的记录可处理(队列为空)。

常见问题:手机没通知/显示 ????

1) /api/v1/push/send 返回 errCode: 400, errMsg: "push_clientid required"

含义:云函数没有拿到 push_clientid,因此没有真正调用 uni-push 下发。

常见原因:CLOUD_FUNC_URL 指向的是“HTTP 触发”云函数(例如 cloudbasefunction.cn。这类云函数往往把 JSON 请求体放在 event.body(字符串)里,而不是直接放在 event.push_clientid

云函数侧的最小兼容写法(示例):

let input = event || {}
if (input && typeof input.body === 'string') {
	try { input = JSON.parse(input.body) } catch (e) { input = {} }
} else if (input && input.body && typeof input.body === 'object') {
	input = input.body
}
const { token, push_clientid, title, content, payload } = input

2) 手机通知标题/内容变成 ????

含义:中文在“发出请求前”已被错误编码成问号(常见于 Windows PowerShell 5.1 发送 JSON

推荐做法:用 UTF-8 字节发送请求体(并带上 charset

$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(更接近 Linux curl 行为)来避免 PowerShell 的编码坑。

3) 云函数“回显 recv”后反而收不到消息

如果你为了排查乱码在云函数里加了 return { recv: ... } 之类的提前返回,云函数会在执行 uniPush.sendMessage() 之前就退出,自然不会真的推送。排查完成后请移除/注释该提前 return。

  • 注意:如果你需要“插入时立即触发云函数”的实时行为,可考虑将轮询改为 Supabase Realtime 订阅或使用 Supabase 的 Edge Function / webhook 触发器;我可以协助把轮询替换为实时订阅的示例实现。

故障排查记录(已执行)

  • 我们在仓库中运行了两个辅助脚本以排查 Supabase 凭证问题:

    • server/probe_supa.js:对单个 key 发起 REST 请求并打印响应(用于快速验证)。
    • server/try_supa_keys.js:扫描仓库中形似 JWT 的字符串并逐一尝试对 Supabase REST 发起请求,打印每个 candidate 的 HTTP 状态与响应预览。
  • 排查结果摘要:

    • 在仓库中发现 6 个看起来像 JWT 的候选 key均对 GET /rest/v1/push_devices?user_id=eq.<user> 返回 HTTP 401包括 PGRST301 / Invalid authentication credentials)。
    • 说明这些仓库内的字符串不是当前 Supabase 项目可用的 service_role key或 Supabase 项目/Key 已被替换/撤销,或请求的 Supabase URL 与当时不同。

推荐操作(二选一)

A使用正确的 Supabase service_role key推荐

  1. 在 Supabase 控制台Project → Settings → API → Service role key复制该 key仅在后端使用切勿公开
  2. 在服务器启动前注入环境变量并启动:
$env:SUPA_URL='http://192.168.1.62:18000'
$env:SUPA_KEY='(粘贴你的 service_role key'
node server/push-server.js

或后台运行并记录日志:

$env:SUPA_URL='http://192.168.1.62:18000'
$env:SUPA_KEY='(你的 key'
Start-Process node -ArgumentList 'server/push-server.js' -RedirectStandardOutput '.\\server.log' -RedirectStandardError '.\\server.err' -PassThru

运行后可检查 server.log(包含 supaFetch 调试输出),并用以下命令验证:

Invoke-RestMethod -Uri 'http://localhost:7301/api/v1/push/devices?user_id=<user-uuid>' -Method GET | ConvertTo-Json -Depth 5

B绕过 Supabase仅用于本地快速验证

如果你只是想立即验证“从服务端发起 -> 调用云函数”的链路,可把已知的 devicecid + user_id写入本地 server/data/push_devices.json,并确保已配置 CLOUD_FUNC_URL,然后调用 /api/v1/push/send(会直接 POST 到云函数):

# 查看本地 devices 文件
Get-Content .\server\data\push_devices.json -Raw | ConvertFrom-Json

# 手动追加(示例,替换 cid 与 user_id
$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

# 然后测试发送(会直接调用 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)

已添加的辅助脚本

  • server/probe_supa.js — 对单个 key 发起请求并打印响应(用于快速验证)。
  • server/try_supa_keys.js — 在仓库中查找 JWT 样式字符串并逐一尝试调用 Supabase REST打印每个 candidate 的状态与响应预览。

如果你把正确的 service_role key 提供给我(仅用于本次会话),我可以代为注入环境、重启服务并把 supaFetch 的调试输出和最终结果贴回给你;或我可以现在把截图中已有的 cid/user_id 写入本地 JSON 并立即做一次 mock 下发测试。

Push Server (开发用)

这是一个用于本地开发与调试的轻量 Node.js 推送后端mock

功能:

  • 注册/更新设备: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 到 CLOUD_FUNC_URL

快速使用:

cd server
npm install
npm start

默认监听端口:7301,可通过 PORT 环境变量修改。

设备存储在 server/data/push_devices.json,用于本地持久化。

可选:直接写入 Supabase

服务器可以可选地将 push_devices 记录直接写入 Supabase/Postgres适用于生产或集成测试。启用方法在启动前设置下列环境变量

  • SUPA_URL — Supabase 项目 URL例如 https://xyzcompany.supabase.co
  • SUPA_KEY — Supabase service_role key仅在服务器端使用切勿暴露给客户端

当这两个变量存在时,服务会通过 PostgREST REST API 向 /rest/v1/push_devices 发起 upsert使用 on_conflict=cid),并在注销时将设备的 is_active 标记为 false。同时本地 JSON 存储仍会保留为回退。

示例PowerShell:

$env:SUPA_URL = 'https://xyz.supabase.co'
$env:SUPA_KEY = 'eyJhbGci...'
npm start

请确保你的 Supabase 项目已经包含与仓库迁移脚本匹配的 push_devices 表: pages/mall/delivery/doc/需求文档/20260224_add_push_devices_and_notifications.sql

Supabase 表结构与权限(快速参考)

下面是 push_devices 表的最小字段集合,供你在 Supabase 中快速验证或手动创建:

CREATE TABLE public.push_devices (
	id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
	user_id uuid NULL,
	cid varchar(255) NOT NULL,
	platform varchar(32) NOT NULL DEFAULT 'android',
	appid varchar(128) NOT NULL DEFAULT 'default',
	is_active boolean NOT NULL DEFAULT true,
	last_seen_at timestamptz NULL,
	created_at timestamptz NOT NULL DEFAULT now(),
	updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX IF NOT EXISTS ux_push_devices_appid_cid ON public.push_devices(appid, cid);

权限与安全建议:

  • 生产环境请务必只在后端使用 service_roleSUPA_KEY),不要将其暴露给客户端。后端应负责向 Supabase 写入/更新设备记录。
  • 如果你需要通过客户端直接调用 Supabase REST不推荐请配置 RLS 策略并只允许安全的最小操作。
  • 推荐做法:后端使用 service_role 写入;客户端只调用后端的 /api/v1/push/register

示例:使用 service_role 通过 PostgREST 插入设备(等同于本服务中执行的请求):

curl -X POST "${SUPA_URL}/rest/v1/push_devices?on_conflict=cid" \
	-H "apikey: ${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}]'

注意事项:

  • 如果你使用的是 Supabase 的 uuid_generate_v4() 功能,可能需要在数据库安装 uuid-ossp 或使用 gen_random_uuid()pgcrypto。迁移脚本中已包含兼容性处理。
  • 本服务对 Supabase 的写入为异步非阻塞:写入失败不会阻止本地响应,但会在日志中记录错误。可根据需要修改为同步错误返回策略。

如需我把本地 JSON 回退移除并只保留 Supabase 持久化(需你确认并提供测试凭证),我可以继续实现并更新代码与文档。

服务说明(快速参考)

服务简介

  • 目的:为开发/集成阶段提供一个轻量的推送后端,用于接收客户端上报的推送客户端 IDCID、管理设备状态并在开发中触发/代理推送以验证链路。
  • 主要职责:接收设备注册/注销、列出设备、触发推送mock 或代理到真实推送服务)、可选将设备写入 Supabase。

暴露的接口HTTP

  • GET /health:健康检查
  • 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:发送推送(仅云函数:直接调用 CLOUD_FUNC_URL;未配置则返回错误)

数据与持久化

  • 开发默认将设备保存在本地文件:server/data/push_devices.json(回退/离线使用)。
  • 可选:当设置 SUPA_URLSUPA_KEY 时,服务会通过 Supabase RESTPostgRESTpush_devices 表 upsert/patch 同步设备记录(异步、非阻塞)。

重要环境变量

  • PORT:监听端口(默认 7301
  • 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_KEYservice_role暴露给客户端只能在后端使用。
  • 生产场景应:后端持有凭证并负责写入/验证;对客户端使用后端接口,不直接对 Supabase 写入。
  • 如直接允许客户端写 Supabase必须开启 RLS 并最小化权限(一般不推荐)。

表结构与迁移

  • 已提供迁移脚本:pages/mall/delivery/doc/需求文档/20260224_add_push_devices_and_notifications.sql(包含 push_devicesexpress_notifications 表及触发器、索引)。

快速测试示例 PowerShell:

Invoke-RestMethod -Uri 'http://localhost:7301/api/v1/push/register' -Method POST -ContentType 'application/json' -Body '{"cid":"CID_TEST_001","user_id":"user-1","platform":"android"}'

curl示例 upsert 到 Supabase需替换变量:

curl -X POST "${SUPA_URL}/rest/v1/push_devices?on_conflict=cid" \
	-H "apikey: ${SUPA_KEY}" \
	-H "Authorization: Bearer ${SUPA_KEY}" \
	-H "Content-Type: application/json" \
	-d '[{"cid":"CID_TEST_001","user_id":"<user-uuid>","platform":"android","appid":"default","is_active":true}]'

如需我在本地使用你的 Supabase 凭证演示一次完整的注册→查询→发送流程,或将持久化切换为仅 Supabase移除本地 JSON 回退),请直接告诉我你的选择并提供测试凭证或确认权限范围。