《Web 到 HarmonyOS》-- 业务分析:消息推送业务方法

系列:《Web 到 HarmonyOS》· 插播(不占正篇序号)

适合人群:会 Web 通知 / 后端发消息,准备理解鸿蒙怎么把一条业务消息送到系统通知栏


推送不是页面功能,是一条跨端通道:业务服务器决定推给谁,通道负责送到设备,应用只负责领身份、开权限、处理点击。

鸿蒙侧离线通道仍是华为 Push Kit 。业务层不要再直连华为、小米、苹果各写一套。在线走个推通道,离线由个推代发到厂商通道。业务服务器只认 CID / 别名,不认各家 Token。

下面用正在看的 entry 说明方案落在哪一层,再写推送业务方法。模块代码只作对照,不代表推送已经接进工程。


一、一个简单的项目示例

假如工程是单模块应用,包名 com.example.myapplication,入口模块类型是 entry,只声明了手机。系统从 module.json5mainElement: EntryAbility 拉起应用,窗口创建后加载 pages/Index。 已注册页面:pages/Indexpages/loginpages/Main/home

位置 模块里已有的职责 方案落点
EntryAbility 设颜色模式、注册 iconfont、loadContent('pages/Index') 领 CID、听 Token 变化、处理点击。属于应用壳,不放进某个页面
pages/login.ets 账号密码 + 短信倒计时 登录成功后把设备身份和业务账号绑在一起;短信组件不参与推送
pages/Main/main.ets 底栏双 Tab,首页 / 个人中心各一把 NavPathStack 点通知后按业务类型推进对应栈,不另开一套路由
HomePage / ProfilePage 首页、个人中心 消息落地用现有页面,方案里不单建消息中心
http/HttpClient.ets 类似 axios 的统一请求 上报 CID、绑定账号走它,不在 Ability 里再写一套请求
api/dictionary 按业务拆 API 的写法 推送上报同样拆成独立 API,不把地址写死在 Ability 里

对前端的映射:

Web 当前 entry
main.ts 挂载应用 EntryAbility.onWindowStageCreate
登录页 router.replace 进后台 登录成功后进入主框架
后台布局 + 子路由 Main 的 Tabs + 两把导航栈
axios 实例 HttpClient

方案只涉及三处:EntryAbility(领身份、收点击)、登录成功(绑定账号)、module.json5(厂商通道所需的 client_id)。页面层不承担推送。


二、业务上要回答的四件事

问题 Push Kit 自己做 多端用个推时
怎么认设备 pushService.getToken(),得到华为 Push Token PushManager.initialize,得到个推 CID
怎么认用户 登录后把 Token 和 userId 存到自己的库;或 bindAppProfileId 绑匿名账号 用业务账号做 别名,一个别名最多绑 10 个 CID
怎么发出去 自己拿 Client Secret 换 Access Token,调华为 v3 messages:send 业务服务器调个推 REST,个推按在线 / 离线选通道
点了去哪 onCreate + onNewWantwant.parameters 同样读 Want;离线点击还要把个推任务号埋点补回报表
flowchart LR A[业务事件] --> B[业务服务器] B --> C{目标端} C -->|只做鸿蒙且自建| D[华为 OAuth + Push API v3] C -->|Android iOS 鸿蒙| E[个推 REST] E -->|应用在前台| F[个推通道] E -->|后台 锁屏 进程不在| G[厂商通道] G --> H[鸿蒙侧即 Push Kit] D --> H F --> I[系统通知栏或透传回调] H --> I I --> J[EntryAbility 处理点击]

鸿蒙设备上,进程不在也能出通知,靠的是系统级长连接,不是页面在后台轮询。这和浏览器里的 Notification 不一样:Web 通知跟着页面或 Service Worker 走,关掉标签往往就断了。


三、Push Kit 业务方法

把客户端收成一条线:开通服务、配置 Client ID、获取 Token、接收消息、处理通知跳转。服务端只交代怎么发,重点在客户端。Android 要分别接华为、小米、OPPO、vivo、魅族;鸿蒙是一套 Push Kit。多端产品仍然建议业务层走个推,鸿蒙这一跳由个推代发到 Push Kit。

官方五步。个推接入之后,业务服务器不直接打华为,第 3 步改成打个推,由个推在离线时代发到推送云:

sequenceDiagram participant App as 应用 participant Kit as PushKit participant S as 应用服务器 participant Cloud as 华为推送云 App->>Kit: 1. 申请 Push Token Kit-->>App: 返回 Push Token App->>S: 2. 上报 Token 与 AAID S->>Cloud: 3. 发送推送请求 Cloud-->>S: 响应结果 Cloud->>Kit: 4. 下发到设备 Kit->>App: 5. 通知栏展示或透传回调

1. 写代码之前:开通、Client ID、测试设备

控制台步骤官方文档已有,编码前必须完成三件:

  1. 在 AppGallery Connect 创建应用并开通推送服务。
  2. 在应用信息页拿到 Client ID
  3. 联调阶段把调试设备的 Push Token 填进 测试设备列表,否则测试消息下不去。

client_id 配在 entrymodule.json5,它是 Push Kit 认应用的标识,配错就收不到:

json 复制代码
{
  "module": {
    "name": "entry",
    "metadata": [
      {
        "name": "client_id",
        "value": "AGC 上的 Client ID"
      }
    ]
  }
}

工程级 build-profile.json5products.runtimeOS 必须是 HarmonyOSclient_id 是厂商通道认应用用的,和「业务是否已经在用个推」是两件事:个推负责多端,这条配置只服务鸿蒙离线。

自分类权益要单独申请。不开通时,通知会被当成资讯营销,每天每设备大约只能到 2 条 。IM、订单、物流必须带对 category,否则同样被降级限流。

类别 典型业务 频控 权益
IM 聊天、通话 申请通过后不按营销限额 需申请
SERVICE / 服务与通讯 订单、物流、账号安全 同上 需申请
MARKETING / 资讯营销 活动、推荐 每天每设备约 2~5 条 默认有,但是紧的

从 API 12 起,服务端用 category 区分类型,客户端用 notificationManager 建通知渠道(例如 order_channel、重要性 HIGH),用户才能按类开关,而不是整应用一刀切。

测试消息 testMessage=true 每个项目每天大约 1000 条,且目标 Token 很少,只能联调。

2. 领 Token,并盯着它变

Push Token 是这台设备上这个应用的推送标识,服务端靠它定向。在 EntryAbility.onCreate 里取,不要等首页 aboutToAppear

typescript 复制代码
const pushToken = await pushService.getToken();
this.uploadToken(pushToken);

三个注意点:

  1. getToken() 是异步的。第一次调用会向华为推送服务器注册设备,必须有网。
  2. Token 会变(应用升级、恢复出厂、重装)。必须在 onCreate 里听 tokenUpdate,变了就重新上报。不听的话,服务器一直拿旧 Token,用户就再收不到。
  3. 这次失败不代表推送坏了。做重试,或下次启动再取。
typescript 复制代码
pushService.on('tokenUpdate', (token: string) => {
  this.uploadToken(token);
});

上报时带上 AAIDAAID.getAAID())。它用来做到达率去重和统计,不是用户账号:

特性 含义
应用隔离 同一台设备,每个应用的 AAID 不同,也读不到别人的
匿名 不绑定 IMEI、SN 这类硬件号
会重置 卸载重装或清除应用数据后改变

和登录的关系(方案,不是本仓库已经接上的步骤):

  1. 取到 Token 和 AAID,先落本地。用户还没登录就先存着,不要用空账号去绑。
  2. 登录成功之后,把 Token、AAID 和业务账号一起上报。直连华为时用可映射的匿名 profileIdbindAppProfileId),不要上传明文用户 id。已有个推时,这一步是给 CID 绑别名,别名用业务账号。
  3. 退出登录时解除绑定,避免下一任用户收到上一任的消息。

通知开关默认是关的。在合适时机调 requestEnableNotification(进消息相关页,而不是冷启动第一秒)。拒绝时常见错误码 1600004

3. 两种消息,前台拦得住的只有透传

类型 谁展示 应用要做什么
通知消息 系统通知栏、锁屏、横幅、角标,应用不用写展示代码 服务端配标题、正文、图片、category、点击动作
数据消息 / 透传 系统不展示 EntryAbility 里自己处理,适合静默更新或自定义样式

透传在 Ability 的接收回调里拿 data。例如聊天类可以自己决定要不要弹一条本地通知:

typescript 复制代码
onReceive(want: Want, data: Record<string, string>): void {
  if (data['msg_type'] === 'chat') {
    this.showChatNotification(data['sender'], data['content']);
  }
}

应用在前台时,系统默认仍会弹通知栏。聊天页想「直接插一条气泡、不弹系统通知」,只能对透传 注册 receiveMessage,再用 AppStorage 通知 UI:

typescript 复制代码
pushService.on('receiveMessage', (data: Record<string, string>) => {
  if (data['type'] === 'new_message') {
    AppStorage.setOrCreate('newMessage', data);
  }
});

receiveMessage 只对透传生效。通知消息的前台展示由系统控制,客户端拦截不了。要「前台不打扰、自己画 UI」,这条业务必须发透传,不能发通知消息。

点通知后的动作:

actionType 行为 方案里怎么用
0 打开应用首页 进首页或登录后的主框架
1 打开应用内指定页 按业务类型推进对应导航栈,并在 Ability skills 里声明 action
2 打开网页 没有 Web 容器就不选
3 富媒体 没有富媒体页就不选

冷启动走 onCreate,进程已在且 Ability 为单例时走 onNewWant。只写一处,另一种点击没反应。排查「发出去了但点了没反应」时,两处都打日志,把 want 打出来:

typescript 复制代码
hilog.info(0x0000, 'PushDebug', 'onCreate, want: %{public}s', JSON.stringify(want));
hilog.info(0x0000, 'PushDebug', 'onNewWant, want: %{public}s', JSON.stringify(want));

4. 服务端下发:客户端要知道的字段

服务端三步:用 Client ID + Client Secret 换 Access Token,调 Push API,由推送云下到设备。正式地址是 POST https://push-api.cloud.huawei.com/v3/{projectId}/messages:send,Access Token 大约 1 小时,要缓存。成功码常见 80000000。V3 面向 HarmonyOS NEXT / 5 及以后。

客户端要对齐的字段:

字段 作用
target.token 指定设备
target.topic 按主题,例如订了 order 的设备
payload.notification 通知的标题、正文、图片
payload.data 透传,客户端在接收回调里拿
payload.harmony 鸿蒙专用配置;payload.android 是安卓侧,不要混用

消息要带 notifyId 才能撤回。撤回只覆盖还没下发、或已展示但用户还没点的那条。失效 Token(例如 80000001)要从库里清掉。

鸿蒙厂商通道往往不直接回点击报表。用个推时,离线通知的 Want 里要带上任务号,在 onCreate / onNewWant 里补报点击。


四、为什么多端用个推,而不是业务层直连 Push Kit

Push Kit 解决的是「鸿蒙这一台设备」。产品如果同时有 Android、iOS,直连华为意味着:

  • Android 还要再接华为、荣耀、小米、OPPO、vivo、魅族的厂商通道,应用被杀后个推自己的长连接不可靠,到达率靠厂商;
  • iOS 还要单独维护 APNs 证书;
  • 业务服务器要为每个平台写一套鉴权、消息体、频控和失败重试。

个推把这三端收成一次调用。客户端 SDK 覆盖 Android、iOS、HarmonyOS NEXT;服务端用同一套 REST:按 CID、别名、标签或全量选人。

和「多端已经有个推」这个前提对齐的几点:

  1. 身份和登录模型对齐。 CID 是这台设备上这个应用的推送身份,登录前就能拿到。别名是业务账号(邮箱、手机号、loginName)。同一账号登录最多 10 台设备时,按别名推一次,这些设备都能收到。这比「一张表只存一个华为 Token、后登录覆盖先登录」更符合多端。
  2. 在线 / 离线不用业务层判断。 应用在前台,走个推通道(push_message)。在后台、锁屏或进程已退出,走厂商通道(push_channel,鸿蒙段就是华为 Push Kit)。通道策略可以按端配置:在线个推、离线厂商;只走厂商;只走个推;或厂商失败再回个推。
  3. 鸿蒙离线仍然要开华为。 只集成个推主包,只有在线推送。要锁屏和杀进程后仍能通知,还得在 AGC 开通 Push Kit、上传服务账号密钥、在 module.json5 配华为 client_id。个推会自己取 Push Token 并和 CID 绑定,业务代码不必再维护一套华为 OAuth。
  4. 运营和补量在同一处。 标签圈人、分组对比、撤回、覆盖、未送达查询、短信补量,都在个推侧。自建华为 Token 表只适合「只有鸿蒙、且必须自建」。多端已经用个推时,再为鸿蒙单独养一套,会和 Android / iOS 分裂成两套人马。

个推不是替代 Push Kit。鸿蒙离线的最后一跳仍是华为。个推省的是业务服务器和客户端的多端分叉。


五、业务顺序

顺序按「个推已经是多端入口」来排。推送逻辑放在 Ability 和登录成功之后,不散进页面。

sequenceDiagram participant U as 用户 participant A as EntryAbility participant L as login.ets participant S as 业务服务器 participant G as 个推 participant H as 华为PushKit A->>G: 初始化,拿到 CID A->>S: 未登录则只缓存 CID U->>L: 登录成功 L->>G: 绑定别名等于业务账号 L->>S: 上报 CID 与账号 S->>G: 按别名推一条消息 alt 应用在前台 G-->>A: 个推通道 else 应用不在前台 G->>H: 厂商通道 H-->>U: 系统通知栏 end U->>A: 点击通知 A->>A: onCreate 或 onNewWant A->>A: 按 type 推进已有导航栈

落点只说明职责,不表示这些调用已经写进工程:

  • 初始化EntryAbility.onCreate,和应用壳里的全局初始化同级,不属于页面。
  • 绑定:登录成功之后绑别名、上报 CID。验证码倒计时不参与推送。
  • 点击回跳:用主框架已有的导航栈。例如个人类消息进个人中心栈,不为推送再开一套页面栈。
  • 网络:上报走统一 HTTP 客户端,推送接口单独成模块。
  • 配置module.json5 需要华为 client_id,供个推走鸿蒙厂商通道;通知权限文案单独做字符串资源。

消息分类按业务定,不要全标营销:

业务 建议类别 别名
登录地异常、验证码已使用 服务与通讯 当前登录账号
运营提醒、内容推荐 营销,且每天克制 同上,或按标签
多设备登录互踢 服务与通讯 同一别名下的其它 CID

六、和 Web 对照,避免用错心智

Web 习惯 鸿蒙推送
页面里 new Notification() 通知由系统展示,页面可以已经销毁
登录态存在内存 / localStorage 设备身份(CID / Token)和登录态分开存,登录成功才绑定
一个接口打到自己的后端就结束 后端还要再打个推;鸿蒙离线时个推再打华为
路由 query 带上来源 点击参数在 Want,冷启动和热启动各读一次
浏览器通知权限是站点级 应用通知权限默认关,要显式申请

前端经验仍然有用的部分:消息体用类型描述(typetitlebody、业务 id),点击后的跳转就是现有导航。变的是这条消息的生命周期不在 Vue 组件里。


七、结论

业务方法收成五步:AGC 开通并申请自分类(给鸿蒙离线用)→ Ability 里拿 CID(底层仍是 Push Token)→ 登录成功后用别名绑定账号 → 服务器只调个推 → 点击在 onCreateonNewWant 都处理,再推进已有导航栈。

只有鸿蒙、且必须自建时,才直连 Push Kit。多端已经是个推,就不要再为鸿蒙单独养一套华为 Token 表。

相关推荐
GreenTea2 小时前
发布 3 天登顶 HN:不生成一个字的模型 Jev,我把它的源码和黑料都扒了一遍
前端·后端·算法
前端小崔2 小时前
Three.js 与 Cesium 融合实战问题汇总
前端·three.js·cesium
wizardpisces2 小时前
Claude Code 是 Angular,Codex 是 Vue,DeepSeek 想当 React
前端·人工智能
计算机魔术师3 小时前
纽约时报诉 OpenAI 案新解封文件:微软与 OpenAI 内部承认 LLM 建立在窃取之上并引发 Doom Loop
前端
PedroQue993 小时前
uni-app x 事件通信插件重磅上线
前端·uni-app
lichenyang4533 小时前
ASCF WebView:H5 为什么收不到元服务消息?
前端
莪_幻尘3 小时前
从 0 到 1 搭建你的 AI Agent 平台:当 Agent 有了工厂,人人都能造同事
前端·ai编程
yuzhiboyouye3 小时前
XML写接口适用场景举例
java·服务器·前端
IMPYLH3 小时前
HTML 的 <slot> 元素
前端·网络·html