如需我在本地使用你的 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 触发器;我可以协助把轮询替换为实时订阅的示例实现。 --- ## 故障排查记录(已执行) - 我们在仓库中运行了两个辅助脚本以排查 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.` 返回 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. 在服务器启动前注入环境变量并启动: ```powershell $env:SUPA_URL='http://192.168.1.62:18000' $env:SUPA_KEY='(粘贴你的 service_role key)' node server/push-server.js ``` 或后台运行并记录日志: ```powershell $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` 调试输出),并用以下命令验证: ```powershell Invoke-RestMethod -Uri 'http://localhost:7301/api/v1/push/devices?user_id=' -Method GET | ConvertTo-Json -Depth 5 ``` ### B)绕过 Supabase(仅用于本地快速验证) 如果你只是想立即验证“从服务端发起 -> 调用云函数”的链路,可把已知的 device(cid + user_id)写入本地 `server/data/push_devices.json`,并确保已配置 `CLOUD_FUNC_URL`,然后调用 `/api/v1/push/send`(会直接 POST 到云函数): ```powershell # 查看本地 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`) 快速使用: ```bash 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): ```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 中快速验证或手动创建: ```sql 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_role`(`SUPA_KEY`),不要将其暴露给客户端。后端应负责向 Supabase 写入/更新设备记录。 - 如果你需要通过客户端直接调用 Supabase REST(不推荐),请配置 RLS 策略并只允许安全的最小操作。 - 推荐做法:后端使用 `service_role` 写入;客户端只调用后端的 `/api/v1/push/register`。 示例:使用 `service_role` 通过 PostgREST 插入设备(等同于本服务中执行的请求): ```bash 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":"","platform":"android","appid":"default","is_active":true}]' ``` 注意事项: - 如果你使用的是 Supabase 的 `uuid_generate_v4()` 功能,可能需要在数据库安装 `uuid-ossp` 或使用 `gen_random_uuid()`(pgcrypto)。迁移脚本中已包含兼容性处理。 - 本服务对 Supabase 的写入为异步非阻塞:写入失败不会阻止本地响应,但会在日志中记录错误。可根据需要修改为同步错误返回策略。 如需我把本地 JSON 回退移除并只保留 Supabase 持久化(需你确认并提供测试凭证),我可以继续实现并更新代码与文档。 ## 服务说明(快速参考) **服务简介** - **目的**:为开发/集成阶段提供一个轻量的推送后端,用于接收客户端上报的推送客户端 ID(CID)、管理设备状态,并在开发中触发/代理推送以验证链路。 - **主要职责**:接收设备注册/注销、列出设备、触发推送(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_URL` 和 `SUPA_KEY` 时,服务会通过 Supabase REST(PostgREST)向 `push_devices` 表 upsert/patch 同步设备记录(异步、非阻塞)。 **重要环境变量** - `PORT`:监听端口(默认 7301) - `SUPA_URL` / `SUPA_KEY` / `SUPA_SCHEMA`:启用 Supabase 同步(`SUPA_KEY` 应为服务端 `service_role`,仅服务器端使用) - `SUPA_USE_BEARER`:(可选)仅当为 `true` 时才发送 `Authorization: Bearer `;默认只发送 `apikey`。 - `CLOUD_FUNC_URL` / `PUSH_TOKEN`:云函数调用地址 / (可选)鉴权 token **安全与部署注意** - 切勿将 `SUPA_KEY`(service_role)暴露给客户端;只能在后端使用。 - 生产场景应:后端持有凭证并负责写入/验证;对客户端使用后端接口,不直接对 Supabase 写入。 - 如直接允许客户端写 Supabase,必须开启 RLS 并最小化权限(一般不推荐)。 **表结构与迁移** - 已提供迁移脚本:`pages/mall/delivery/doc/需求文档/20260224_add_push_devices_and_notifications.sql`(包含 `push_devices` 与 `express_notifications` 表及触发器、索引)。 **快速测试示例** PowerShell: ```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,需替换变量): ```bash 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":"","platform":"android","appid":"default","is_active":true}]' ``` 如需我在本地使用你的 Supabase 凭证演示一次完整的注册→查询→发送流程,或将持久化切换为仅 Supabase(移除本地 JSON 回退),请直接告诉我你的选择并提供测试凭证或确认权限范围。