05 · 消息载荷与平台定制¶
目标读者:要构造跨 Android/iOS/Web 多端推送的开发者 核心问题:同一个 message 怎么在 Android/iOS/Web 上都有最佳体验?哪些字段是必填的? 前置: 01-FCM核心概念 / 02-HTTP-v1-API与认证 相关: 04-批量发送与广播模式 最后更新:2026-04-11
一、消息三层结构¶
FCM HTTP v1 的 message 对象有三层:
┌────────────────────────────────────────────────┐
│ Layer 1:公共字段(所有平台都解释) │
│ · notification { title, body, image } │
│ · data { k: v }(value 必须是 string) │
├────────────────────────────────────────────────┤
│ Layer 2:平台覆盖块(对应平台读取,会覆盖公共) │
│ · android { priority, ttl, notification } │
│ · apns { headers, payload } │
│ · webpush { headers, notification } │
├────────────────────────────────────────────────┤
│ Layer 3:元字段 │
│ · fcm_options.analytics_label │
│ · token / topic / condition (三选一) │
└────────────────────────────────────────────────┘
覆盖规则:如果 Layer 1 和 Layer 2 同名字段都存在,Layer 2 的值会覆盖 Layer 1。
二、Notification 消息 vs Data 消息¶
2.1 核心对比¶
| 维度 | notification 消息 | data 消息 |
|---|---|---|
| 字段 | 设置 notification |
只设置 data |
| 后台行为 | FCM SDK 自动显示在系统通知栏 | Android:唤醒 service 处理 iOS:需 content-available:1 唤醒 app 短时执行 |
| 前台行为 | 触发 onMessageReceived,不会自动显示,要 app 自己构造通知 |
触发 onMessageReceived |
| 灵活度 | 低(系统接管显示) | 高(app 完全控制) |
2.2 iGaming 推荐做法:混合消息 ⭐¶
同时设 notification + data:
- notification —— 后台时系统自动弹通知
- data —— 携带 promo_code、deeplink、campaign_id 等结构化字段
- 前台时:app 通过 onMessageReceived 拿到 data,自己构造通知
const message = {
topic: 'all_users',
notification: {
title: '11 点限时福利',
body: '输入 LUCKY888 领 588 红包',
},
data: {
promo_code: 'LUCKY888',
expires_at: '1735710000',
deeplink: 'igaming://promo/LUCKY888',
campaign_id: 'daily_11am_20260411',
},
};
2.3 ⚠️ data 字段的严格要求¶
- 所有 value 必须是 string
"count": 10❌ →"count": "10"✅"is_vip": true❌ →"is_vip": "true"✅- 不能使用保留 key:
frommessage_type- 任何
google./gcm./gcm.notification.开头的 key - 总大小:data + notification ≤ 4096 bytes(topic 消息 ≤ 2048 bytes)
三、消息大小与 TTL¶
| 项 | 值 |
|---|---|
| 单条消息 payload | 4,096 bytes(Notification + Data 合计) |
| Topic 消息 | 2,048 bytes |
| TTL 范围 | 0 ~ 2,419,200 秒(4 周) |
| TTL 默认值 | 4 周 |
iGaming 推荐 TTL:3,600 ~ 14,400 秒(1-4 小时)。超过优惠码截止时间后再送出去反而骚扰用户。
四、Android 平台字段详解¶
"android": {
"collapse_key": "promo", // 同 key 的后到消息会替换前面未送达的
"priority": "high", // "high" 才会立即送达;"normal" 会合并/延迟
"ttl": "14400s", // 0~2419200 秒(4 周)
"restricted_package_name": "com.igaming.platforma",
"data": { "k": "v" },
"notification": {
"channel_id": "promo", // ⚠️ Android 8+ 必填,否则不响
"sound": "default",
"click_action": "OPEN_PROMO",
"icon": "ic_promo",
"color": "#FFD700",
"image": "https://cdn.igaming.com/promo/lucky888.png",
"tag": "promo_2026_04_11", // 同 tag 会折叠
"title": "11 点限时福利",
"body": "输入 LUCKY888 领 588 红包"
},
"fcm_options": { "analytics_label": "daily_11am" }
}
4.1 关键字段¶
| 字段 | 必填 | 说明 |
|---|---|---|
priority |
推荐 "high" |
运营推送必须设 high,否则会被合并/延迟 |
notification.channel_id |
Android 8+ 必需 | 否则通知静默、不响也没声音 |
notification.image |
否 | 大图推送,限制 1 MB |
ttl |
推荐设 | 过期自动丢弃,避免离线用户收到过期优惠 |
collapse_key |
推荐 | 同 key 会替换未送达的,避免用户收到一堆同类通知 |
4.2 ⚠️ Android 8+ 必须设 channel_id¶
这是 iGaming 最常见的坑。客户端需要先创建 notification channel:
// Android 客户端代码(告知客户端工程师)
val channel = NotificationChannel(
"promo", // 必须和后端 channel_id 对应
"促销通知",
NotificationManager.IMPORTANCE_HIGH,
).apply {
description = "优惠码和限时福利通知"
enableLights(true)
enableVibration(true)
}
NotificationManagerCompat.from(context)
.createNotificationChannel(channel)
如果客户端没创建 promo channel,发送再多的 channel_id: "promo" 消息都不会响。
五、iOS (APNs) 平台字段详解¶
"apns": {
"headers": {
"apns-priority": "10", // 10=立即送达;5=节能模式(静默推送必须用 5)
"apns-push-type": "alert", // alert / background / voip / ...
"apns-collapse-id": "promo_daily",
"apns-expiration": "0" // 0=不过期就丢;其它是 Unix 秒绝对过期时间
},
"payload": {
"aps": {
"alert": { "title": "...", "body": "..." },
"sound": "default",
"badge": 1,
"category": "PROMO",
"thread-id": "promo",
"content-available": 1, // 1 = 静默推送(不能有 alert)
"mutable-content": 1 // 1 = 允许 NSE 修改(富通知/图片必填)
},
"promo_code": "LUCKY888" // 自定义键并列于 aps
},
"fcm_options": {
"image": "https://cdn.igaming.com/promo/lucky888.png",
"analytics_label": "daily_11am"
}
}
5.1 关键字段¶
| 字段 | 说明 |
|---|---|
headers.apns-priority |
"10" = 立即送达;"5" = 节能模式(静默推送必须用 5)。运营推送一律 "10" |
payload.aps.content-available |
1 = 静默推送,不能同时有 alert,否则 Apple 限频严重 |
payload.aps.mutable-content |
1 = 允许 Notification Service Extension 修改(富通知带图必填) |
payload.aps.category |
用于定义通知交互按钮,客户端要预注册 category |
5.2 ⚠️ iOS 富通知必须配套两件事¶
想在 iOS 推送里显示大图:
- 后端:
apns.payload.aps.mutable-content = 1+apns.fcm_options.image = "https://..." - 客户端:app 端要实现 Notification Service Extension(告知客户端工程师)
只设置其中一个都没用。
六、Web Push 平台字段详解¶
"webpush": {
"headers": { "TTL": "14400", "Urgency": "high" },
"data": { "k": "v" },
"notification": {
"title": "11 点限时福利",
"body": "输入 LUCKY888 领 588 红包",
"icon": "https://cdn.igaming.com/icon.png",
"image": "https://cdn.igaming.com/promo/banner.png",
"badge": "https://cdn.igaming.com/badge.png",
"actions": [
{ "action": "open", "title": "立即领取" },
{ "action": "dismiss", "title": "稍后" }
]
},
"fcm_options": {
"link": "https://igaming.com/promo/LUCKY888",
"analytics_label": "daily_11am"
}
}
6.1 关键字段¶
| 字段 | 说明 |
|---|---|
headers.Urgency |
very-low / low / normal / high,high 用于优惠码推送 |
notification.actions |
浏览器通知的交互按钮 |
fcm_options.link |
点击通知后打开的 URL(必填,否则点击无反应) |
七、完整 iGaming 优惠码消息示例¶
跨平台完美推送(Android / iOS / Web 都有最佳体验):
// iGaming 日常推送的标准 message 对象
const message = {
topic: 'all_users',
notification: {
title: '11 点限时福利',
body: '输入 LUCKY888 领 588 红包,限前 1000 名',
},
data: {
promo_code: 'LUCKY888',
expires_at: '1735710000',
deeplink: 'igaming://promo/LUCKY888',
campaign_id: 'daily_11am_20260411',
},
android: {
priority: 'high',
ttl: '14400s', // 4 小时
notification: {
channelId: 'promo_high', // 必填
sound: 'default',
color: '#FFD700',
imageUrl: 'https://cdn.igaming.com/promo/lucky888.png',
clickAction: 'OPEN_PROMO',
},
},
apns: {
headers: {
'apns-priority': '10',
'apns-push-type': 'alert',
'apns-collapse-id': 'promo_daily',
},
payload: {
aps: {
alert: { title: '11 点限时福利', body: '输入 LUCKY888 领 588 红包' },
sound: 'default',
badge: 1,
'mutable-content': 1, // 允许富通知
category: 'PROMO',
},
},
fcmOptions: {
image: 'https://cdn.igaming.com/promo/lucky888.png',
analyticsLabel: 'daily_11am',
},
},
webpush: {
headers: { TTL: '14400', Urgency: 'high' },
notification: {
icon: 'https://cdn.igaming.com/icon.png',
image: 'https://cdn.igaming.com/promo/lucky888.png',
},
fcmOptions: { link: 'https://igaming.com/promo/LUCKY888' },
},
fcmOptions: { analyticsLabel: 'daily_11am' },
};
await getMessaging().send(message);
这就是所有 5 次定时推送可以直接套用的标准模板,只需要换里面的 promo_code / title / body / image。
八、陷阱清单¶
-
Android 8+ 必须设
channel_id否则通知静默 —— 是 iGaming 最常见的坑。 -
iOS 富通知必须
mutable-content: 1+ Notification Service Extension 两者缺一不可,后端设了图但客户端没实现 NSE 等于白搭。 -
data字段的所有 value 必须是 string 数字 / 布尔 / 对象会被 FCM 直接拒绝INVALID_ARGUMENT。 -
data不能用保留字from/message_type/google.*/gcm.*都是保留的,用了会报错。 -
notification在 iOS 前台不会自动显示 需要 app 端UNUserNotificationCenterDelegate配合。 -
Web Push 的
fcm_options.link不设就没点击效果 一定要填,否则用户点了通知没反应。 -
消息总大小 ≤ 4096 bytes(Topic 消息 ≤ 2048) 超了直接
messaging/payload-size-limit-exceeded。
九、权威来源¶
- https://firebase.google.com/docs/cloud-messaging/customize-messages/cross-platform
- https://firebase.google.com/docs/cloud-messaging/customize-messages/set-message-type
- https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages
- https://firebase.google.com/docs/cloud-messaging/concept-options