推送接入

本页说明推送相关能力的接入步骤。webhook 部分是当前可用的全部能力;token、通道、点击跳转与角标为规划中,接口与命名尚未冻结,请勿在代码中依赖。

当前能力边界(必须如实告知)

本项目没有 APNs、FCM 或国内厂商的发送实现,没有通知点击跳转与角标。

已实现:设备 token 台账(客户端 REST 注册/查询/注销,幂等 + 每用户 20 个上限)、6 个厂商通道的配置存储与校验、投递尝试记录(no_token / not_configured,sent 恒 0)与手动重试。

另有服务端“离线消息 webhook”与 push.send:接收者无在线连接时 POST 应用配置的 URL,3 秒超时、无签名/重试。

完整状态机与新接口见 推送通道与投递;本页保留为链路背景。

步骤 0:配置应用推送 webhook(已实现)​

通过 Admin API 设置应用级 webhook。示例使用本地地址,仅用于联调;生产环境请使用 https,并在接收端做鉴权与幂等。

Shell
curl -sS -X POST http://127.0.0.1:8080/api/admin/apps/demo/push \
  -H 'X-Admin-Secret: dev-admin' \
  -H 'content-type: application/json' \
  -d '{"webhookUrl":"http://127.0.0.1:9000/hook","enabled":true}'

# 查询当前配置
curl -sS http://127.0.0.1:8080/api/admin/apps/demo/push \
  -H 'X-Admin-Secret: dev-admin'
字段类型必填说明
webhookUrlstring是http(s) 地址;服务端 POST JSON,3 秒超时
enabledboolean是false 关闭派发但保留 URL

步骤 1:实现 webhook 接收端(已实现)​

接收端只需要一个能处理 JSON POST 的 HTTP 服务。注意:服务端不签名、不重试,建议按 message.messageId 去重并落库后再做业务处理。

JavaScript
import { createServer } from 'node:http';

const seen = new Set();

createServer((req, res) => {
  if (req.method !== 'POST' || req.url !== '/hook') {
    res.writeHead(404).end();
    return;
  }
  let raw = '';
  req.on('data', (chunk) => { raw += chunk; });
  req.on('end', () => {
    const payload = JSON.parse(raw);
    const message = payload.message;
    // 幂等:同一条消息只处理一次
    if (!seen.has(message.messageId)) {
      seen.add(message.messageId);
      console.log('[offline]', payload.userId, message.content);
      // 在这里调用 APNs/FCM/厂商通道(本项目尚未实现)
    }
    res.writeHead(200, { 'content-type': 'application/json' });
    res.end('{"ok":true}');
  });
}).listen(9000, '127.0.0.1', () => console.log('webhook listening on :9000'));

步骤 2:制造离线消息并验证(已实现)​

  1. 让接收者离线

    接收者断开 WS 连接或退出应用;只要该用户没有在线会话,消息就会进入离线队列并成为推送候选。

  2. 发送消息

    用 发送单聊消息 或客户端 SDK 向离线用户发送任意已实现类型(text/image/file/voice/location/custom)。

  3. 观察 webhook

    接收端应收到 { appkey, userId, reason: "offline", message };reason 固定为 offline。

  4. 核对日志

    用 push.log.list 或控制台推送页核对 pushId 与投递结果。

不会触发 webhook 的情况

接收者仍在线(包括 Web/PC 在线)时不会触发;聊天室消息不落离线、不触发;系统通知不经过该 webhook;撤回不新增消息、不触发。

步骤 3:发送不落地通知(已实现,但仅 webhook)​

push.send 按范围投递标题/内容/extras,不写入任何会话。下面的 Node 示例使用 Open API 签名;scope=user 时 target 传 userId。

JavaScript
import { createHash, randomBytes } from 'node:crypto';

const BASE = 'https://im.example.com';
const APP_KEY = 'demo';
const APP_SECRET = 'YOUR_APP_SECRET';
const nonce = randomBytes(16).toString('hex');
const timestamp = String(Date.now());
const signature = createHash('sha1').update(APP_SECRET + nonce + timestamp).digest('hex');

const res = await fetch(BASE + '/im/v2/push/send', {
  method: 'POST',
  headers: {
    'content-type': 'application/json',
    'App-Key': APP_KEY,
    Nonce: nonce,
    Timestamp: timestamp,
    Signature: signature,
  },
  body: JSON.stringify({
    scope: 'user',
    target: 'user2',
    title: '系统通知',
    content: '你有一条新的业务提醒',
    extras: { route: '/orders/9f2a' },
  }),
});
console.log(await res.json());

响应中的 pushId 可用于 push.stats.by-id 查询投递统计。未配置 webhook 时 delivered=0,但推送日志仍会记录。

步骤 4:设备 token 注册(已实现)​

客户端用 Bearer 用户 token 调 POST /api/apps/{appkey}/push/tokens 注册厂商 token;服务端按 (userId, platform, token) 幂等,每用户最多 20 个活跃 token,同一 token 被其他用户注册时旧记录自动停用。

端需要获取的 token注册方式状态
Android各厂商推送 SDK 的 registrationId / deviceTokenREST 注册,platform=android 或具体厂商✅ 台账可用
iOSAPNs deviceTokenREST 注册,platform=ios✅ 台账可用
Web / Electron浏览器 PushSubscription(如使用 Web Push)REST 注册,platform=web✅ 台账可用
Flutter / Native依赖宿主平台的厂商通道 token由宿主 App 注册✅ 台账可用
SDK 与台账的边界

Web / Electron SDK 已提供 registerPushToken/unregisterPushToken/getPushTokens;Android / Flutter / Native / iOS 尚未实现,请直接调用 REST。

注册 token 只代表“台账里有设备”,厂商发送仍未实现:投递记录只会得到 not_configured。

步骤 5:通道与证书配置(仅存储)​

控制台「推送通道」/ Admin API 可以保存 APNs、FCM、华为、小米、OPPO、vivo 六个通道的必填字段与启用位;敏感字段回读脱敏,通道测试返回 implemented=false。这一步不会让通知真正发出。

通道必填字段本项目
APNskeyId / teamId / bundleId / privateKey(production 可选)🟡 配置可存;发送未实现
FCMprojectId / serviceAccountJson🟡 配置可存;发送未实现
华为 / 小米 / OPPO / vivoappId/appKey/appSecret/packageName 等(见通道页)🟡 配置可存;发送未实现
RongPush融云自建通道⛔ 不在范围

步骤 6:点击跳转(规划中)​

融云在点击远程通知时发出隐式 Intent(会话 / 会话列表 / 不落地通知),也支持 PushEventListener 自定义。本项目没有通知,因此没有点击事件可处理。

  • 现有 webhook 载荷已包含 message.conversationType、targetId、messageId,应用可据此自行构造跳转。
  • 规划中的客户端 SDK 需要提供通知点击回调与默认会话跳转;命名未冻结,暂不在文档中给出。

步骤 7:角标(规划中)​

本项目只有会话 unreadCount,没有系统角标同步、没有免打扰会话的角标抑制规则,也没有厂商角标接口。规划中。

步骤 8:测试方法​

测试项方法当前能否验证
webhook 配置生效Admin API 设置后查询返回 webhookUrl/enabled✅ 可以
离线消息触发接收者离线 → 发消息 → 本地接收器收到 POST✅ 可以
在线不触发接收者保持 WS 连接 → 发消息 → 接收器无请求✅ 可以
关闭派发enabled=false 后重复离线消息 → 无请求✅ 可以
不落地通知push.send 后接收器收到标题/内容/extras✅ 可以(仅 webhook)
设备 token 注册/注销REST 注册 → 管理端台账可查;重复注册 tokenId 不变✅ 可以
投递状态诚实性Admin 创建投递 → no_token/not_configured,sent=0✅ 可以
厂商通道到达模拟器/真机收到系统通知并点击进会话⛔ 无法验证(通道未实现)
角标/多语言系统角标与推送语言切换⛔ 无法验证(未实现)
证据口径

当前只能提供 webhook 的本地接收日志与推送日志;不能声称“模拟器收到推送”,因为不存在通道实现。

常见问题​

现象排查
接收器无请求确认接收者确实全部离线;确认 enabled=true、URL 为 http(s);查看服务端日志 [push] webhook error
收到重复请求服务端无重试,但多端/多次触发可能重复;按 messageId 去重
3 秒超时接收端同步处理过慢;先落库快速 200,再异步处理
想直接接 APNs当前需在 webhook 接收端自行实现;本项目不提供证书与通道管理层

相关页面​

厂商通道发送尚未提供;在具备可验证的发送证据之前,本页“规划中”结论不得改写为已实现。