spec.md 13 KB

Feature Specification: 可切换的原生业务推送通道

Feature Branch: 033-native-push-routing

Created: 2026-09-29

Status: 服务端路由与随包 classpath 凭据已实现;默认 uni,原生通道未启用且真机投递未验证

Input: 为现有普通业务通知增加 Android FCM 与 iOS APNs 原生发送能力,以服务端配置切换,重启后生效;交付时继续默认使用 uniPush,不启用原生通道。仅涉及服务端;音视频通话推送不在范围内。

背景与边界

当前业务推送通过 PayPush 调用用户、商家、骑手三套 uniCloud 云函数,部分路径通过 PushEvent 写入 push_message。info_user 已有 cid、cidType、deviceToken:cid 是 uniPush 标识,deviceToken 承载 Android FCM token 或 iOS APNs device token,两者不可互用。用户已确认同一账号只允许一台手机登录,新手机登录后旧手机登录状态失效,因此本功能沿用现有单设备字段,不新增推送设备表。

本功能覆盖当前仍在使用的普通业务通知,包括订单、闪送、支付结果及平台/商家通知等现有发送路径;只替换发送通道,不增加新的业务触发时机或文案。不修改已废弃的支付/订单入口、临时测试推送接口,也不改动音视频通话推送。App 端 token 获取、上报及通知展示不在本功能实现范围;服务端以已有上报字段为输入。

User Scenarios & Testing (mandatory)

User Story 1 - 默认继续使用 uniPush (Priority: P1)

运维部署新版本后,不做任何切换,普通业务通知仍按现有路径发送,不因原生凭据尚未部署而影响服务启动。

Why this priority: 先交付原生能力但暂不启用,必须保证当前线上推送行为不变。

Independent Test: 不设置通道配置,触发用户、商家、骑手各一条现有业务通知,验证只调用对应 uniCloud 通道。

Acceptance Scenarios:

  1. Given 未配置推送通道或配置为 uni,When 任一普通业务通知触发,Then 仅走现有 uniPush 通道,不调用 FCM/APNs。
  2. Given 未部署原生通道凭据,When 服务以默认配置启动,Then 启动成功且现有 uniPush 可用。
  3. Given 已部署原生发送代码,When 音视频通话推送触发,Then 继续按原有独立流程处理,不受普通业务通知开关影响。

User Story 2 - 配置切换后 Android 走 FCM (Priority: P1)

运维将普通业务推送配置切换为原生并重启服务后,Android 接收方通过对应 App 的 FCM 项目收到现有通知。

Why this priority: 这是 Android 原生通知的核心能力,同时必须避免误把 uniPush CID 当作 FCM token。

Independent Test: 在原生配置下,分别向用户、商家、骑手 Android 账号触发通知,验证使用各自 Firebase 项目和 deviceToken,且未调用 uniCloud。

Acceptance Scenarios:

  1. Given 通道为 native、接收方系统为 Android 且有有效 deviceToken,When 普通业务通知触发,Then 通过该接收端对应的 Firebase 项目向 deviceToken 发送一次;不得把 cid 传给 FCM。
  2. Given 用户、商家、骑手分别使用不同 Firebase 项目,When 三端各有通知触发,Then 每条通知使用对应项目凭据,不串用项目。
  3. Given 普通商家、夜市商家或商家子账号是接收方,When 原生通知触发,Then 均按商家端凭据路由。
  4. Given 门店存在符合当前通道发送条件的已登录分管账号,When 订单通知触发,Then 只通知这些分管账号,不同时通知主账号;如果没有符合条件的分管账号,才回退通知可用的主账号。切换原生通道不得改变这一收件人优先级。

User Story 3 - 配置切换后 iOS 走 APNs (Priority: P1)

同一原生配置下,iOS 接收方通过对应 App 的 APNs 普通通知证书收到现有通知。

Why this priority: 只有 Android FCM 不满足“三端全切原生”的目标。

Independent Test: 在原生配置下,分别向用户、商家、骑手 iOS 账号触发通知,验证使用各自普通推送证书、正确环境与 deviceToken,且未调用 uniCloud。

Acceptance Scenarios:

  1. Given 通道为 native、接收方系统为 iOS 且有有效 deviceToken,When 普通业务通知触发,Then 通过该接收端对应的 APNs 普通通知证书向 deviceToken 发送一次;不得使用 voIPToken。
  2. Given 运行环境为开发或生产,When 发送 iOS 通知,Then 选择相应 APNs 网关并使用该接收端对应的同一份普通推送证书,不混用三端凭据。
  3. Given 原有通知带有订单标识、业务类型或点击跳转所需 payload,When 改走 APNs,Then 这些业务字段仍可供接收端识别。

User Story 4 - 安全切换、失败隔离与回退 (Priority: P1)

运维能通过修改服务端配置并重启,在 uniPush 与原生通道间切换。单次通知只选择一个通道;发送失败不影响订单等业务操作。

Why this priority: 切换期间必须避免重复通知、业务流程被第三方推送故障阻断,以及随包凭据被写入日志或向非授权方分发。

Independent Test: 用缺失 token、原生服务异常、缺失凭据及配置回退场景验证发送结果、业务结果与消息中心记录。

Acceptance Scenarios:

  1. Given 通道为 native 且原生 token 缺失或发送失败,When 普通业务通知触发,Then 记录可定位的失败信息,不自动补发 uniPush,也不改变原业务操作结果。
  2. Given 原路径会通过 PushEvent 写入消息中心,When 任一通道发送成功、失败或 token 缺失,Then 保持原有入库语义,每次业务通知最多写入一条记录,不因切换重复入库。
  3. Given 通道从 native 改回 uni 并重启,When 后续通知触发,Then 重新使用 cid 和 uniPush,不再调用 FCM/APNs。
  4. Given 原生模式所需的三端 FCM 或 APNs 凭据配置缺失、无法读取,When 服务启动,Then 明确报错并拒绝以不完整的原生配置启动;默认 uni 模式不受影响。

Edge Cases

  • cidType 为空或不是 android/ios:原生模式下不猜测平台,不尝试使用错误通道;记录失败,保持业务和原有消息中心行为。
  • deviceToken 为空、失效或来自另一 App/项目:不把 cid 当作替代 token;记录失败结果,失效 token 的后续处理应避免误删新手机刚更新的 token。
  • 同一账号换手机或 token 刷新:后端始终以当前账号最新上报的 cidType 与 deviceToken 为发送目标;本功能不改变现有登录互斥机制。
  • 批量发送含不同接收端或系统:按接收端、系统及相应项目/证书分组,不把不同项目 token 混发,也不因部分失败重复发送成功项。
  • 业务代码直接调用底层 uniPush 方法:在范围内的普通业务通知必须纳入统一路由;已废弃代码、临时测试推送接口及音视频通话流程除外。
  • 在用路径存在“CID 为空则不选择收件人/不触发推送”的前置判断:默认 uni 模式维持原判断效果;原生模式应以当前账号的 deviceToken 判断可发送性,不得因 CID 为空漏掉有原生 token 的收件人。

Requirements (mandatory)

Functional Requirements

  • FR-001: 服务端 MUST 提供普通业务推送的全局通道配置,取值为 uni/native,未配置时默认为 uni;修改后重启生效,不新增管理后台实时开关。
  • FR-002: 默认 uni 模式 MUST 保持现有用户、商家、骑手 uniCloud 发送行为与消息中心入库行为;不得要求原生凭据存在。
  • FR-003: native 模式 MUST 根据接收端类型和 cidType 路由:Android 使用对应 App 的 FCM,iOS 使用对应 App 的 APNs;普通商家、夜市商家及商家子账号均属于商家端。
  • FR-003a: 门店订单通知 MUST 保持现有“符合条件的分管账号优先、没有时主账号兜底”规则,不得因切换通道而同时推送两类账号;分管账号的发送地址资格按当前通道判断。
  • FR-004: 原生发送 MUST 只使用 deviceToken,uniPush MUST 只使用 cid;不得相互复制或替代,voIPToken 不得用于普通通知。
  • FR-005: 用户、商家、骑手三套 Firebase 项目及 APNs 普通通知证书 MUST 独立配置并正确选择;iOS MUST 区分开发与生产环境。
  • FR-006: 三端 FCM 服务账号、普通 APNs 证书及其 packetName/pwd 配置 MUST 分别以 user、merchant、rider 明确命名并放在 ruoyi-admin/src/main/resources/business-push/,由 classpath 读取。用户已明确要求这些真实凭据提交 Git 并随 JAR 打包;不得把私钥或密码写入日志、规格正文或推送响应。仓库和构建产物必须按含密钥材料管理。
  • FR-006a: 每端 ios.p12 与 iosdev.p12 经核对文件完全相同,资源中每端只保留一份 APNs 普通推送 .p12。development/production 只切换 APNs 网关,不切换证书文件;三端证书、Firebase 项目和 topic 不得混用。
  • FR-007: 原生模式的必要凭据不完整时 MUST 在启动阶段明确失败;默认 uni 模式 MUST 可在无原生凭据时正常启动。
  • FR-008: 所有当前在用的普通业务推送路径 MUST 使用一致的通道选择规则,包括适用的单发、批量及直接调用现有底层方法的路径;不改变业务触发时机、文案和 payload 语义。临时测试推送接口不在范围内。
  • FR-009: 单次业务通知 MUST 只使用一个外部发送通道;原生发送失败或缺少 token 时 MUST NOT 自动回退到 uniPush,且 MUST NOT 使原业务操作失败。
  • FR-010: 对现有会入库的推送,通道切换 MUST NOT 增加或漏掉 push_message 记录;消息中心记录与外部发送结果保持解耦。
  • FR-011: 实现范围 MUST 排除音视频通话推送、已废弃订单/支付入口及 App 端代码;不新增推送业务场景或改变登录互斥规则。
  • FR-012: 本功能 MUST 沿用现有 info_user 单设备字段,不新增数据库字段或设备表。
  • FR-013: 外卖和闪送新单向多名骑手开放时,仅在 native 模式对 Android 骑手按相同标题、正文及 payload 分组,使用骑手端 FCM multicast 发送;iOS 骑手保留逐设备 APNs 请求,uni 模式保留原有逐骑手发送方式,不接入旧批量方法。各骑手的语言、消息中心记录和发送失败处理 MUST 保持独立,原生失败不得回退 uniPush。

Key Entities (include if feature involves data)

  • 接收账号:已有账号记录,包含接收端类型、cid、cidType、deviceToken;同一账号仅保留当前手机的一组推送标识。
  • 普通业务通知:已有业务事件生成的标题、正文、payload 与接收方;发送渠道是可替换属性,不改变业务含义。
  • 消息中心记录:现有 push_message,继续按原路径写入,不作为外部发送成功的证明。
  • 通道配置与凭据:服务端全局 uni/native 配置,以及已提交资源目录的三端 FCM/APNs 凭据;不属于业务数据库数据。

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: 未配置开关的部署中,受测在用普通业务通知 100% 仍选择原 uniPush 通道,且无原生凭据时服务可启动。
  • SC-002: 原生配置下,用户、商家、骑手 × Android、iOS 六种路由组合均选中正确凭据与 deviceToken,未把 cid 或 voIPToken 发送到原生通道。
  • SC-003: 受测通知在正常发送、token 缺失和发送异常场景下,单次均不出现双通道外发;现有会入库的路径每次最多生成一条消息记录。
  • SC-004: 模拟 FCM/APNs 不可用或 token 无效时,原业务操作结果与不发送推送时一致;失败可通过日志定位到接收端、平台与错误类别,日志不包含完整 token 或凭据。
  • SC-005: 测试配置切回 uni 并重启后,后续受测通知恢复原 uniPush 发送路径;音视频通话路径保持原行为。

Assumptions

  • 用户已确认同一账号只允许一台手机登录,且新手机登录后旧手机登录状态失效;本功能依赖现有机制,不重新实现登录互斥。
  • App 端会上报真实 FCM/APNs deviceToken 与正确的 cidType;App 端变更和真机展示不包含在此次服务端交付范围。
  • 原生能力交付后仍保持 uni 为默认值;凭据虽随包提供,只有运维确认 App 端 token、将服务端配置切为 native 并重启,才会启用原生通道。
  • 音视频通话使用其他推送流程,不受本次普通业务通知通道开关控制;voIPToken 及 VoIP 证书不用于本功能。
  • 证书与 Firebase 服务账号由用户提供并提交到本仓库;仓库访问、构建产物分发和凭据轮换由部署方按含密钥材料管理。默认 uni 下不加载这些凭据。