# 普通业务推送:原生通道部署与回退 **状态(2026-09-29)**:服务端具备 FCM/APNs 路由能力,默认 `uni`,**尚未启用原生推送**。本功能不处理音视频通话,也不包含 App 端 token 获取、上报和展示改造;未完成真机验证前不要切换生产环境。 ## 配置与凭据 三端真实凭据已按用户明确要求提交在 `ruoyi-admin/src/main/resources/business-push/`,并随 JAR 打包;每端只保留一份普通 APNs `.p12`,开发/正式只切换网关。任何能读取 Git 历史、JAR 或包含资源的镜像的人都能取得服务账号私钥与证书密码。须限制仓库及构建产物访问,不把密钥内容写入日志或公开分发;如发生泄露,应轮换 Firebase 服务账号与 APNs 证书,并重新构建发布。 | 接收端 | FCM 服务账号 | APNs 普通证书 | APNs topic/密码配置 | | --- | --- | --- | --- | | 用户 | `user-fcm-service-account.json` | `user-apns.p12` | `user-apns-config.json` | | 商家(含夜市及分管账号) | `merchant-fcm-service-account.json` | `merchant-apns.p12` | `merchant-apns-config.json` | | 骑手 | `rider-fcm-service-account.json` | `rider-apns.p12` | `rider-apns-config.json` | APNs 配置 JSON 使用原文件的 `packetName` 作为 topic、`pwd` 解锁同端 `.p12`;三端 Firebase 项目分别对应各自 App,不能共享 `server.json`。不使用 VoIP 证书。`application.yml` 默认指向上述 classpath 文件,仍保留显式文件路径及 APNs 密码/topic 覆盖能力,以兼容原部署方式。 | 设置 | 环境变量 | 默认/要求 | | --- | --- | --- | | 通道 | `BUSINESS_PUSH_MODE` | `uni`;可选 `native`,重启后生效 | | APNs 环境 | `BUSINESS_PUSH_APNS_ENVIRONMENT` | `production`;测试包用 `development` | | 三端 FCM 路径覆盖 | `BUSINESS_PUSH_{USER|MERCHANT|RIDER}_FCM_CREDENTIALS_PATH` | 可选,默认各自的 classpath JSON | | 三端 APNs 证书路径覆盖 | `BUSINESS_PUSH_{USER|MERCHANT|RIDER}_APNS_CERTIFICATE_PATH` | 可选,默认各自的 classpath `.p12` | | 三端 APNs 配置路径覆盖 | `BUSINESS_PUSH_{USER|MERCHANT|RIDER}_APNS_CONFIG_PATH` | 可选,默认各自的 classpath 配置 JSON | 如需覆盖 APNs 配置 JSON,也可分别设置原有的 `BUSINESS_PUSH_{USER|MERCHANT|RIDER}_APNS_CERTIFICATE_PASSWORD` 与 `BUSINESS_PUSH_{USER|MERCHANT|RIDER}_APNS_TOPIC`;两项都显式提供时不读取配置 JSON。默认 `uni` 模式不读取原生凭据;`native` 模式启动时校验三端配置并加载文件,缺失、不可读或证书无私钥/已过期时启动失败。 `BUSINESS_PUSH_APNS_ENVIRONMENT=development` 使用 APNs sandbox 网关,`production` 使用正式网关;两种环境读取同端同一份证书。已核对三端 `packetName` 与证书主题相符,但证书能否在两个网关实际投递仍需真机验证。三端证书到期日为 2027-07-30;更新任一资源后必须重新构建并部署 JAR,再做各端测试。 ## 启用前条件 1. App 端分别上报真实的 Android FCM token 或 iOS APNs device token 至 `info_user.deviceToken`,并把 `cidType` 设置为 `android` 或 `ios`;`cid` 只用于 uniPush,不能复制到 `deviceToken`。本次交付不实现 App 端上报。 2. 确认新手机登录后旧手机会话失效,数据库中账号只保留当前设备 token;原生发送时服务端重新读取该账号的当前 `cidType/deviceToken`。 3. 在非生产环境确认私有 JAR 包含九个按端命名的资源,设置对应 APNs 环境与 `BUSINESS_PUSH_MODE=native` 并重启。先检查服务启动,再用三端 Android/iOS 各一台真机触发订单、闪送和支付通知;核对通知栏、点击 payload、消息中心、语言与接收人。iOS 骑手使用 `pushsound.caf`;Android 骑手 FCM 带 `qsRing` 通知渠道标识,须由 App 创建并配置该渠道才能保证自定义铃声,本次不修改 App。门店订单有可用分管账号时只通知分管账号,没有时才回退主账号。 4. 重点覆盖 CID 为空而原生 token 有效、token 缺失/失效、账号换机、预约闪送进入可抢单窗口,以及发送失败但业务操作成功。某条原生通知失败时不会自动回退 uniPush;原路径已有的消息中心记录应保持一次。 ## 切换与回退 生产切换须先完成 App 端配套和真机验收。使用包含上述资源的受控构建,将 `BUSINESS_PUSH_MODE` 设为 `native`,按实例逐一重启并检查启动及推送错误日志;一条通知只会选择当前实例的一个通道,滚动重启期间不同实例可能短暂处于不同模式。若原生投递异常,改回 `BUSINESS_PUSH_MODE=uni` 并逐一重启;之后的新通知重新使用 `cid`,已失败的通知不会由服务端自动补发。回退时确认 App 仍维护有效的 uniPush CID。 自动化测试验证三端 classpath 凭据可加载、路由、请求构造和错误隔离;真实设备 token、FCM/APNs 外部投递、通知展示与回退操作仍需部署环境验证。 ## ApiPost 原生推送测试(业务模式无需切换) 常驻接口 `POST /system/push/testNative` 允许匿名访问,`Content-Type: application/json`,无需账号 ID 或后台 token。即使 `BUSINESS_PUSH_MODE` 保持默认 `uni`,该接口仍直接调用原生通道;Android 使用所选端的 FCM 项目,iOS 固定走 APNs 正式网关。请求示例: ```json { "appType": "rider", "platform": "android", "deviceToken": "手机取得的原生 FCM 或 APNs Token", "title": "原生推送测试", "content": "请检查手机通知栏", "payload": "{}" } ``` `appType` 可填 `user`、`merchant`、`rider`;`platform` 可填 `android`、`ios`。测试 iOS 时改为 `ios` 并传正式包取得的 64 位十六进制 APNs token;开发包的 sandbox token 不能用于这个正式网关。`payload` 可省略。响应 `code=200` 只表示 FCM/APNs 接受发送请求,**不证明手机通知栏已显示**;失败时 `code=500` 和 `msg` 返回不含 token 的错误类别。需在真机核对通知权限、前台展示与通知栏,不调用业务订单流程,也不写入 `push_message`。 该接口按需求长期匿名开放,公网部署后任何知道地址的人都可以使用服务端凭据向其掌握的设备 Token 发送消息,可能造成滥用和推送配额消耗;如不接受此风险,应在网关限制可访问来源或后续改为鉴权接口。 外卖和闪送新单通知附近最多 20 名骑手时,`native` 模式会按骑手当前设备类型分流:Android 骑手按相同语言文案和订单 payload 使用骑手端 FCM multicast,逐 token 处理返回结果;iOS 骑手继续逐设备走 APNs。每位骑手的消息中心事件保持独立,批量失败不回退 uniPush。默认 `uni` 模式及其逐骑手新单通知完全不变,旧的 uni 批量方法未接入此流程。