推送消息到手机,在需求文档里通常只是一句话:用户下单后 App 要能收到通知。真正落地后你很快会遇到 APNs、FCM、厂商通道、通知栏权限、token 失效、离线消息、省电策略这些彼此牵连的问题。如果只调通一个 Demo,就以为推送做完了,生产环境很容易出现“测试机收得到,用户手机收不到”的现象。
这篇文章围绕一条主线展开:一条业务消息从服务端发出之后,是如何经过推送网关到达手机并展示到通知栏的,以及接入方需要关心哪些关键配置。内容面向已经写过服务端接口、但还没有完整接通过移动推送的开发者。读完之后,你既能在脑子里建立完整的推送链路,也能拿到一份最小可复现的接入案例和一套可复用的排错清单。
1. 先理解“推送消息到手机”这条链路为什么不能只看通知栏
许多刚接推送的开发者会犯同一个错误:以为只要服务端调用某个接口,手机通知栏就会弹消息。其实通知栏只是链路最后一公里,真正决定成败的是设备注册、token 上报、网关鉴权、系统进程分发、通知渠道设置以及隐私权限。
1.1 推送和拉取的本质差别
如果没有任何推送通道,最原始的实现就是让 App 每隔几秒向服务端问一次“有没有新消息”。这个模型可以用一段伪代码表达:
while True: latest = server.query_new_message(user_id) if latest: show_notification(latest) time.sleep(10)这段代码的代价很明显:App 要保持活跃,网络请求会持续消耗流量,屏幕亮灭和网络切换都可能打断轮询。即使把间隔调大到 30 秒,实时性也差一个数量级。更关键的是,手机上大多数 App 在退到后台一段时间后会被系统挂起,轮询线程根本没有机会稳定执行。
推送方案把“拉”变成了“收”。系统层面有一个可靠的进程替 App 接收消息,App 被挂起、甚至被用户从后台列表划掉之后,系统进程依然有机会收到消息并展示到通知栏。这个系统进程不属于业务 App,而是由 Google、苹果或手机厂商维护,所以它在省电策略、进程优先级和设备唤醒上都有特殊调度权限。
理解这一点后再看推送,它的本质不是“App 自己收到了消息”,而是“手机系统收到消息后,决定是否展示通知并唤醒动作”。
1.2 一条推送从服务端到手机要经过哪些角色
一条标准推送链路通常包含四个参与方:
| 参与方 | 典型代表 | 作用 |
|---|---|---|
| 业务服务端 | 自己的订单系统、IM 服务 | 决定给谁发什么消息,调用推送网关 |
| 推送网关 | APNs、FCM、华为推送、小米推送等 | 校验调用方身份,把消息投递给对应设备 |
| 系统推送服务 | iOS 系统推送服务、手机厂商推送服务 | 接收网关上已经寻址到本机的消息,触发通知展示 |
| 业务 App | App 内的推送 SDK 回调 | 注册设备、接收数据消息、处理点击跳转 |
完整流程可以拆成四步:
- 用户在手机上安装并打开 App。
- App 内的推送 SDK 向系统推送服务申请设备令牌,通常称为 token、deviceToken 或 regid。
- App 把这个 token 连同用户 ID、平台类型上报给业务服务端,服务端保存到设备表。
- 服务端需要发消息时,把 token 作为目标,调用推送网关接口;网关把消息投递给手机系统,系统再展示通知或触发 App 回调。
这里最容易忽略的一个概念是:token 不是永久的。用户重装 App、清除应用数据、切换系统语言或厂商升级系统,都可能导致 token 变化。服务端如果保存了过期 token,推送网关会返回类似InvalidToken的错误。生产环境必须把“token 失效清理”当成推送模块的一部分来实现。
1.3 先分清两种消息模型:通知消息和数据消息
推送网关投递给 App 的消息,按用途可以分成两类。
通知消息(notification message)带有标题、正文、声音、角标等展示字段。系统收到后会自动展示到通知栏,App 是否在前台、是否被杀,都不会影响系统展示这一动作。它适合订单状态、物流提醒、社交互动这类用户能直接看到的内容。
数据消息(data message)则是一段自定义键值对,系统不负责展示,只是把 payload 传给业务 App。App 自己的推送回调收到数据后,可以决定是弹通知、刷新页面、还是触发一次静默请求。它适合需要业务逻辑参与的场景,比如新消息内容要更新到会话列表。
一句话总结:通知消息让系统帮你展示,数据消息让 App 自己处理。后台传递数据消息的效果并不稳定,因为系统为了省电可能推迟 App 进程启动;所以不要把“杀掉 App 后还能精确执行数据刷新”作为数据消息的设计前提。
2. iOS、Android 和厂商通道的差异,决定了选型方案
推送通道不是简单的“选一个 HTTP 接口”,而是和平台生态强绑定。iOS、海外 Android、国内 Android 在推送方案上的差异非常大,选错通道,后面的稳定性和可达性都会很难补。
2.1 iOS:一切推送都走 APNs
iOS 设备上的推送统一由 Apple Push Notification service,也就是 APNs 负责。开发者不能直接在 App 内建立自己的长连接去接收系统通知,也不能绕过 APNs 直接唤出通知栏。苹果在推送机制上采取封闭策略,所有消息都必须经过它的网关。
接入 APNs 前需要在开发者后台开启 Push Notifications 能力,并生成服务端使用的 APNs 证书或 .p8 密钥。发送时服务端通过 APNs 的 HTTP/2 接口投递消息,网关会根据设备 token 把消息发送到用户手机。
APNs 有两个常见环境:开发和发布。测试环境通常连接 sandbox 网关,正式环境连接生产网关。很多开发者遇到“测试正常、线上收不到”的问题,都是因为打包时签发的 profile 环境和服务端使用的 APNs 环境不一致。
2.2 Android 的碎片化与厂商推送通道
Android 系统虽然开放,但 Google 的 FCM(Firebase Cloud Messaging)在国内公开分发场景下并不默认可用。海外通过 Google Play 分发的应用,FCM 是标准路径;面向中国大陆用户的应用,通常要接华为、小米、OPPO、vivo 这些手机厂商自己的系统推送服务。
厂商推送服务的特点,是每个厂商在系统内部维护一条高优先级的长连接。应用接入对应厂商的 SDK 后,服务端调用厂商的 REST 接口,消息经过厂商服务器下发到本机系统服务,再由系统展示通知。由于是系统级通道,用户即使杀掉 App,通知也有较大概率成功展示。
但这也带来一个工程问题:不能只接一家厂商。华为手机走华为推送,小米手机走小米推送,OPPO、vivo、荣耀又各有各自的后台,服务端要分别适配鉴权方式、接口地址和错误码。如果不打算自己逐家适配,可以考虑第三方聚合推送。
2.3 第三方聚合推送是中间层,不是银弹
市场上常见的极光推送、友盟 U-Push、个推等第三方服务,本质上是在你与各厂商通道之间加了一层统一封装。你接入它们的 SDK 和服务端 SDK,由它们帮你把消息分发给华为、小米、OPPO、vivo、APNs 等通道。
第三方聚合推送的价值,在于客户端 SDK 可以自动判断设备属于哪一个厂商,并在厂商后台无法直接下发时降级到自己的长连接通道方案。这个“云推通道”能在一定程度上弥补厂商通道缺失的问题。
但要注意:当应用长期在后台、系统省电策略激进时,第三方自建长连接同样会被系统限制,它只能作为第二通道,不能替代真正的厂商系统通道。选型时不要只信宣传中的“到达率 99%”,要结合目标用户的机型分布、系统版本比例和推送业务类型来评估。
2.4 自建 WebSocket 或 TCP 长连接适合哪些业务
自建长连接的最大优点是消息内容完全自定义、实时性完全可控,可以像聊天 App 一样真正做到毫秒级收发。为了维持连接,客户端需要处理断线重连、心跳保活、数据加密、消息去重和序列号同步,工程量并不小。
从手机系统角度看,App 进入后台后,自建长连接很难长期存活。手机厂商从省电和隐私角度会限制 App 的 CPU 唤醒和网络活动。除非你的 App 本身就是聊天、会议、协同工具,用户对后台保活有明确预期,并且你做了一整套系统白名单引导,否则不建议把核心消息完全押在自建通道上。
综合方案是:业务实时性要求高的消息走自有长连接,通用通知走厂商通道或第三方聚合通道,App 冷启动或回到前台后,再通过普通 HTTP 接口同步离线消息。
3. 动手前先准备环境和前置条件
推送 Demo 跑起来很快,但前置条件如果不清楚,往往会在“控制台发消息”这一步卡很久。下面分两块说:一是通用的账号和证书准备,二是最小学习 Demo 的技术栈选择。
3.1 账号、证书和测试机的准备清单
| 准备对象 | 必要配置 | 用途 |
|---|---|---|
| Apple Developer 后台 | App ID 开启 Push Notifications,生成 .p8 密钥或 APNs 证书 | iOS 令牌和消息推送采用 APNs 鉴权 |
| Firebase 控制台 | 创建 Android 项目,填写应用包名,上传 google-services.json | FCM 客户端注册和服务器推送 |
| 手机厂商开放平台 | 分别创建 App,填写包名、应用签名 SHA-256 | 厂商推送的注册校验 |
| 业务服务端 | 申请推送服务 API 账号或保存签发的密钥文件 | 调用网关接口前需要鉴权 |
| 测试真机 | Android 手机和 iPhone 各一台 | 模拟器对推送支持不稳定,不推荐做最终验证 |
在准备阶段最容易忽略的是应用签名。很多厂商推送平台要求你在后台填写 Android App 的签名 SHA-256。调试环境和正式环境签名不同,如果你在开发阶段上传的是 debug 签名,等发布包换成 release 签名后,同样包名可能注册不到 token。因此项目一开始就要想清楚签名环境。
3.2 最小 Demo 选什么通道更合适
这里以 FCM 作为标准协议示例,原因是它更像一份可以对照学习的“推送参考实现”。它提供了清楚的令牌注册流程、统一的消息结构,并且控制台可以直接测试向指定 token 发消息。
如果你的业务明确面向中国大陆用户,直接依照示例思路去接厂商推送即可,厂商接口的字段大同小异。不要把网络可达性作为示例讨论范围,选型时根据产品面向的市场决定即可。
4. 从零到一跑通:客户端注册 token 到服务端发送
现在按一条完整链路,从 Android 客户端开始,到服务端发送一条真实通知,最后在手机上验证结果。
4.1 在 Android 应用里接入 Firebase Messaging
新建一个最小 Android 工程,包名例如com.example.pushdemo,然后在 Firebase 控制台添加 Android App,填写该包名并下载google-services.json,放到 app 模块的src目录下。
根目录build.gradle.kts中启用 Google Services 插件:
plugins { id("com.android.application") version "8.2.0" apply false id("com.google.gms.google-services") version "4.4.0" apply false }app 模块的build.gradle.kts里加上插件和依赖,依赖版本以 Firebase BOM 官方发布版本为准:
plugins { id("com.android.application") id("com.google.gms.google-services") } android { namespace = "com.example.pushdemo" compileSdk = 34 defaultConfig { applicationId = "com.example.pushdemo" minSdk = 23 targetSdk = 34 versionCode = 1 versionName = "1.0" } } dependencies { implementation(platform("com.google.firebase:firebase-bom:32.7.0")) implementation("com.google.firebase:firebase-messaging") }Android 13 及以上系统要求应用申请通知权限,否则即使消息下发成功,通知栏也不会展示。需要在AndroidManifest.xml中声明权限:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />同时在应用启动后,主动申请通知权限,理想时机是用户登录或有明确业务场景后,不要在首屏立即弹窗。
4.2 编写接收推送的服务组件
自定义一个FirebaseMessagingService子类,接收 token 更新和消息到达事件:
package com.example.pushdemo import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage class PushMessageService : FirebaseMessagingService() { override fun onNewToken(token: String) { super.onNewToken(token) // token 变更时上报到服务端,旧 token 在服务端应标记失效 reportDeviceToken(token) } override fun onMessageReceived(message: RemoteMessage) { // notification 类型的消息在前台可能不会自动展示,需要 App 自行处理 // data 类型消息不会自动展示,必须在这里解析并决定是否弹通知 if (message.notification != null) { // 记录到达事件,准备跳转路径 } if (message.data.isNotEmpty()) { // 根据 data 的内容决定是否调用 NotificationManager 展示 } } private fun reportDeviceToken(token: String) { // 这里应该把当前登录用户和 token 一起 POST 到业务服务端 // POST /api/v1/device-token // { "userId": "10086", "platform": "android", "token": token } } }有些教程会直接告诉你“token 变化就在 onNewToken 里打印日志”,这在实际项目里不够。一份上报逻辑要把用户维度一起带上,做一对一绑定,否则同一台设备换账号后,新用户可能收到属于旧用户的消息。
在AndroidManifest.xml中声明这个服务:
<service android:name=".PushMessageService" android:exported="false"> <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" /> </intent-filter> </service>安装 App 首次启动后,从日志中看到类似FirebaseMessaging: Firebase message service is created的信息,说明 SDK 已经注册成功。接下来获取 token。
4.3 在 Firebase 控制台验证发送一条通知
如果只是验证客户端链路,可以先不写服务端代码。打开 Firebase 控制台的 Cloud Messaging 菜单,点击“发送测试消息”,在目标字段粘贴刚才手机上拿到的 token。如果手机通知栏出现了消息,说明客户端接入成功。
这一步是用来定位问题的:客户端接对了,测试通知就一定能收到;如果控制台测试都收不到,后面服务端怎么写都是白搭。所以建议把它作为第一个检查点。
4.4 服务端调用推送网关接口发送通知
客户端接入成功后,服务端才进入链路。FCM HTTP v1 接口的地址形式是:
POST https://fcm.googleapis.com/v1/projects/{project_id}/messages:send调用前需要使用 Firebase 服务账号的 JSON 文件生成 OAuth 2.0 访问令牌。这里不展开 JWT 和签名细节,生产环境由服务端自行维护令牌缓存,因为令牌有效期通常是一小时左右。
请求体示例如下:
{ "message": { "token": "DEVICE_TOKEN", "notification": { "title": "订单状态", "body": "您的订单已经发货" }, "android": { "priority": "HIGH", "ttl": "86400s" } } }对应的 curl 命令是:
curl -X POST \ 'https://fcm.googleapis.com/v1/projects/your-project-id/messages:send' \ -H 'Authorization: Bearer ACCESS_TOKEN' \ -H 'Content-Type: application/json' \ -d @push-message.json关键参数的含义:
| 参数 | 说明 | 注意点 |
|---|---|---|
token | 目标设备令牌 | 服务端必须保存最新的 token 与用户映射 |
notification | 通知栏展示的标题和正文 | 系统会直接用于 UI 展示 |
android.priority | HIGH表示高优先级 | 需要实时送达的业务消息建议设为 HIGH |
android.ttl | 消息在服务端等待设备上线的有效期 | 过短则离线消息直接丢弃,过长则造成延迟展示 |
同一份请求体里真正跟业务强相关的是token。服务端接收到网络返回的message id,就代表网关已接受这条消息,但这不等于用户已经看到通知。还要在网关后台查看消息状态和送达统计。
4.5 厂商推送路径的对应关系
如果是华为、小米、OPPO、vivo 这类厂商推送,协议细节虽然不同,但都遵循同一套逻辑:
- 客户端集成对应厂商的 Push SDK,注册后获取厂商 token。
- 服务端调用厂商 REST API,在请求体里指定 token 和目标 App。
- 请求体通常包含
title、body、data、过期时间等字段。
理论上可以考虑用聚合 SDK 抹平差异。但自研推送模块时,各家的错误码和限流策略差异很大,建议在服务端抽象一层PushClient接口,每个平台一个实现,避免业务代码里堆满if (huawei) ... else if (xiaomi)。
5. 消息参数和通知渠道设计对到达率的影响
这个阶段比较容易踩的坑,是“服务端明明发送成功,手机却不显示”。根因通常不在网络,而在通知渠道、消息优先级和离线策略这几个地方。
5.1 Android 8.0 之后必须先有通知渠道
Android 8.0 起,所有通知必须归属到某个NotificationChannel。如果 App 没有创建有效渠道,系统可能直接丢弃通知,或者只在设置里显示一条警告。
客户端发送自定义通知前要确保渠道存在:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( "default", "默认通知", NotificationManager.IMPORTANCE_HIGH ).apply { description = "业务提醒通知" } val manager = getSystemService(NotificationManager::class.java) manager.createNotificationChannel(channel) }渠道的IMPORTANCE_HIGH决定通知是否弹出铃声、震动或横幅。如果你把渠道设为低重要度,即使服务端传入priority: HIGH,展示形式上仍会受限。
用户还可以在系统设置里为 App 单独关闭某些渠道。因此线上排查时,除了看服务端是否发送成功,还要检查该用户手机上的系统设置里,这个渠道是否被手动关闭。
5.2 前台通知行为与通知消息展示差异
有一类现象非常典型:App 在后台时能收到通知,切到前台后同样的消息突然不弹了。原因可能不是推送链路坏了,而是平台对前台通知有额外约定。
以 FCM 为例,当 App 处于前台时,通知消息会通过onMessageReceived交给业务层处理,系统不会直接弹出通知栏。如果客户端没有在自己的onMessageReceived中重新构建通知,用户就会认为“消息丢了”。
解决方式有两种:业务侧判断 App 前台显示时不必要弹系统通知,只在界面内展示;或者仍然调用 NotificationManager 主动弹一次消息。这里要根据消息场景定,不要一刀切。
5.3 TTL 和离线消息的取舍
服务端发送消息时,网关不一定能立刻找到在线设备。此时消息会在网关暂存一段时间,等设备重新上线后再下发。这个等待时间就是 TTL。
TTL 过长的问题很突出:一条“取件码即将过期”的消息如果延迟一小时才展示,对用户已经不构成价值,反而会造成困扰。TTL 过短则离线期间完全丢掉,用户下次打开 App 时要通过业务接口拉取离线数据来补偿。
实际项目中建议区分业务消息类型:
- 实时性要求高,丢失不可接受的聊天消息,TTL 可以较短,App 回到前台后用“拉取未读列表”兜底。
- 时效性不敏感的营销通知,可以使用较长 TTL,但要控制发送总条数。
不要把推送当作唯一数据通道。推送的职责是通知用户“有新内容”,真正的内容同步由业务接口完成,这样离线消息即使丢了几条,也不会导致用户数据缺失。
6. 收不到推送、时收时不收时,按这条链路定位问题
推送问题排查最忌讳一上来就问“为什么 Android 推送不可靠”。要先确定问题发生在哪一段:客户端注册阶段、服务端发送阶段、网关投递阶段,还是手机本地展示阶段。
6.1 按顺序排查这五个节点
先确认 token 是否有效。在客户端日志中找到 token,把它粘贴到控制台发一条测试推送。如果能收到,说明客户端链路正常,问题大概率在服务端或网关 API;如果连测试推送都收不到,则先检查 notification 权限、应用包名、签名和系统省电设置。
再确认服务端返回结果。网关返回success或 message ID,只代表网关接受了消息,不代表手机已展示。需要看服务端业务日志中,调用后是否出现 timeout、InvalidToken、MessageTooBig、QuotaExceeded 等字段。
然后看网关后台统计。FCM 控制台、厂商推送控制台通常都有每日发送量、送达量、展示量等指标。如果发送量有、展示量为零,很大概率是消息到达手机后,被本地渠道或权限挡住了。
再检查手机本地。Android 上依次检查:系统设置中的通知权限、App 对应的通知渠道是否关闭、电池优化白名单、自启动权限。这些设置因品牌和系统版本不同有很大差异,要结合实际机型逐步确认。
最后用一台“最干净”的真机复测。把目标型号手机恢复出厂设置或关闭所有省电策略,如果此时能收到,说明不是代码问题,而是系统策略问题。
6.2 高频问题的现象与处理建议
| 问题现象 | 可能导致原因 | 优先处理动作 |
|---|---|---|
| 控制台测试也收不到 | 包名、签名、证书不一致,或通知权限被关 | 重新核对包名与 SHA-256,清理应用数据后重新启动检查 token |
| 服务端发送返回成功但手机没展示 | Android 8.0 后无通知渠道,或渠道被用户关闭 | 创建正确的 NotificationChannel,检查系统设置中的渠道状态 |
| iOS 正式环境收不到 | 服务端连接了 APNs sandbox 或证书过期 | 核对 .p8/证书类型,分别检查 Debug 和 Release 的推送环境 |
| 消息时收时不收 | 应用被系统挂起、省电策略限制、TTL 过短 | 接入对应厂商系统推送,并检查网关后台数据 |
| 用户杀掉 App 后收不到 | App 进程被强制结束后,自建长连接无法工作 | 切换到系统级厂商通道,并在用户打开 App 后用业务接口补齐离线消息 |
| Android 13 手机首次启动收不到 | 未授予 POST_NOTIFICATIONS 运行权限 | 在设置中开启通知权限,并在代码中把申请时机放在合适的业务节点 |
其中“服务端发送成功但手机没展示”是最常见的盲区。服务端和网关之间叫投递,网关到手机叫下发,手机到通知栏叫展示,这三段不能混在一起看。记录日志的字段也要分开:服务端发送日志、网关 message ID、客户端到达时间和用户点击时间,每一段都独立埋点,排查时才有足够信息判断断点在哪。
6.3 举例:厂商反馈“送达成功”但仍没展示
生产环境经常遇到厂商后台显示送达,用户却说没收到。送达一般表示设备系统服务接收成功了,但展示动作仍可能被系统打断。可能原因包括:通知渠道重要度过低、相同 tag 的消息被折叠、系统判断当前非打扰时间、用户手动关闭了该 App 的横幅展示。
处理这类问题时,不要只依赖厂商统计。客户端可以在收到推送回调后附加一次本地日志,把“是否触发 onMessageReceived”“是否创建 Notification”“用户是否点击”分别上报。数据积累起来后,再结合厂商送达数据对比,往往很快能找到问题。
7. 生产环境下的推送模块,还需要补上这些东西
一条推送从“能收到”到“稳定可靠”,至少要补齐 token 管理、消息持久化、发送策略、失败重试和监控列表。下面给出一份可以直接扩展的工程骨架。
7.1 token 与用户绑定关系的数据模型
服务端至少要维护两份核心表:设备 token 表和消息发送记录表。设备表的常见设计如下:
CREATE TABLE push_device ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id VARCHAR(64) NOT NULL COMMENT '业务用户 ID', platform TINYINT NOT NULL COMMENT '1:iOS 2:Android', channel VARCHAR(32) NOT NULL COMMENT 'apns/fcm/huawei/xiaomi/oppo/vivo', device_token VARCHAR(512) NOT NULL COMMENT '推送令牌', status TINYINT NOT NULL DEFAULT 1 COMMENT '1:有效 0:失效', last_active_at DATETIME NULL COMMENT '最近活跃时间', update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_user_channel (user_id, platform, channel) );设备表的作用有两个:一是根据 user_id 找到所有可用的 token;二是当用户退出登录时,可以把该用户和这台设备解绑,避免下个登录用户收到旧用户消息。
消息发送记录表建议保存请求体快照和发送状态:
CREATE TABLE push_message_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, message_no VARCHAR(64) NOT NULL COMMENT '业务幂等号', user_id VARCHAR(64) NOT NULL, token VARCHAR(512) NOT NULL, title VARCHAR(128) NULL, body VARCHAR(1024) NULL, data_payload JSON NULL COMMENT '自定义数据', channel VARCHAR(32) NOT NULL, push_status TINYINT NOT NULL DEFAULT 0 COMMENT '0:待发送 1:成功 2:失败', gateway_message_id VARCHAR(128) NULL, error_code VARCHAR(64) NULL, retry_count INT NOT NULL DEFAULT 0, create_time DATETIME NOT NULL, send_time DATETIME NULL );把消息请求快照存下来,方便重试和审计。特别是网关返回 token 失效时,要能反查具体是哪一条消息发给了哪个 token。
7.2 发送策略和失败重试的工程建议
发送接口应当是幂等的。业务服务端构造message_no,推送服务先查日志表,如果已经存在相同 message_no,则直接返回上一次结果,避免网络抖动导致重复发送。
对网关返回的错误要区分处理:
| 返回错误 | 含义 | 处理策略 |
|---|---|---|
| 未授权 | AccessToken 或鉴权参数无效 | 刷新网关凭证后重试 |
| InvalidToken | token 无效 | 立即标记 token 失效,不要重试 |
| MessageTooBig | 请求体超出大小限制 | 截断 payload 后重新发送 |
| 限流 |