ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

AngularFire Cloud Messaging 实战指南:从 FCM 实例注册到服务端通知发送的完整实现

2026/9/27 8:48:02 拓冰建站 浏览量
AngularFire Cloud Messaging 实战指南:从 FCM 实例注册到服务端通知发送的完整实现 后端【免费下载链接】angularfireAngular Firebase ❤️项目地址https://gitcode.com/gh_mirrors/an/angularfire点击查看免费下载Firebase Cloud MessagingFCM让你能为每台设备注册唯一的 FCM tokenFirebase Installation ID随后通过 Firebase Cloud Functions 在服务端按需向这些 token 编程式推送通知。本文以 AngularFire 的现代 tree-shakable APIangular/fire/messaging为主线完整讲解从依赖注入、Service Worker 搭建、register/onRegistered实例注册到使用 Firebase Admin SDK 发送通知含 token 缓存的端到端实战方案。读完本文你将能在 Angular 应用中安全地请求通知权限、注册并跟踪 FCM 实例、接收前台消息并在 Cloud Functions 中按用户精准推送通知。FCM 在 Angular 应用中的角色Firebase Cloud Messaging 允许你把每台设备注册为唯一的 FCM token之后就可以通过 Firebase Cloud Functions 在服务端向这些 token 编程式发送通知。是否把这些 token 同步到 Firebase 数据库、用于应用的其他层级完全由应用自己决定——例如向所有管理员发送通知这类场景你通常需要把 FCM token 存放在 user 集合、其子集合或另一个权限不同的集合中这样才能在服务端按用户查询到对应 token。从 AngularFire 21 开始getToken和deleteToken在 firebase 12.18 中被弃用并将被移除官方推荐改用registeronRegistered、unregisteronUnregistered这套新模型。这一版本对齐要求也体现在 docs/version-21-upgrade.md 中ng update angular/fire会把firebase依赖对齐到^12.18.0避免新旧 SDK 副本并存导致运行时对象互相排斥。本文全部基于新 API 展开。依赖注入提供 Messaging 实例前置条件是把 AngularFire 添加进项目ng add angular/fire该命令会安装 AngularFire、Firebase SDK并将provideFirebaseApp(...)以及你选中的各功能 provider 写入应用配置如app.config.tsFirebase 配置会以内联方式写入不创建环境文件详见 docs/install-and-setup.md。随后在应用的app.config.ts中提供 Cloud Messaging 实例import { ApplicationConfig } from angular/core; import { provideFirebaseApp, initializeApp } from angular/fire/app; import { provideMessaging, getMessaging } from angular/fire/messaging; export const appConfig: ApplicationConfig { providers: [ provideFirebaseApp(() initializeApp({ ... })), provideMessaging(() getMessaging()), ... ], ... }接着把Messaging注入到组件中import { Component, inject} from angular/core; import { Messaging } from angular/fire/messaging; Component({ ... }) export class AppComponent { private messaging inject(Messaging); ... }provideMessaging 的底层行为从源码看src/messaging/messaging.module.tsprovideMessaging(fn, ...deps)返回EnvironmentProviders其中默认实例工厂defaultMessagingInstanceFactory会在服务端isPlatformServer(platformId)为真时返回null从而避免在 SSR 环境下创建无法工作的 Messaging 实例客户端侧则通过ɵgetDefaultInstanceOf从已提供的实例中取出默认实例。同时它还会调用registerVersion(angularfire, VERSION.full, fcm)向 Firebase 注册版本信息。Messaging本身是一个接口继承firebase/messaging的Messaging、并用构造器返回底层实例的轻量包装类见 src/messaging/messaging.ts。模块还导出了MessagingInstances与messagingInstance$每 300ms 轮询一次所有已注册 Messaging 实例并去重用于多实例场景。测试 src/messaging/messaging.spec.ts 验证了Messaging可注入且注入结果与provideMessaging提供的实例一致在isSupported返回 false 的环境下则为null。创建 Firebase Messaging Service WorkerFirebase Messaging 由两部分组成Service Worker 与 DOM API。AngularFire Messaging 负责 DOM 侧的请求权限、注册应用实例、监听注册/注销状态变化以及订阅消息而要真正接收通知必须搭建 Service Worker。具体搭建细节以 Firebase 官方客户端文档 为准。在 src/assets 下创建 firebase-messaging-sw.js/* Replace firebase-version with the firebase version in your package.json. The service * worker and your application have to load the same version. */ import { initializeApp } from https://www.gstatic.com/firebasejs/firebase-version/firebase-app.js; import { getMessaging } from https://www.gstatic.com/firebasejs/firebase-version/firebase-messaging-sw.js; const firebaseApp initializeApp({ apiKey: , authDomain: , projectId: , storageBucket: , messagingSenderId: , appId: , }); const messaging getMessaging(firebaseApp);两点重要提醒版本必须严格匹配firebase-version要替换为package.json中firebase的实际版本。Service Worker 与应用必须加载同一版本版本不匹配可能导致前台收不到消息。每次升级firebase依赖后都要同步更新该文件兼容版文档 docs/compat/messaging/messaging.md 也特别标注了这一 Warning。多环境适配建议在不同环境开发/生产下使用 Angular 的 file replacements 或 environment 配置来切换此文件。如果你的项目使用 Angular Service Worker需要注意兼容版 API 并非开箱即用Angular 官方 issue 跟踪中可改用 Workbox、手动注册 Angular Service Worker或直接使用上面的 Firebase Messaging Service Worker 方案。注册应用实例register 与 onRegisteredFirebase 已在 firebase 12.18 弃用getToken/deleteToken并将移除应改用register替代getToken配合onRegistered监听注册结果unregister替代deleteToken配合onUnregistered监听注销结果。Firebase 官方客户端指南描述了这套模型并明确要求不要混用新旧两套 API。复制下面的示例前有三点必须先弄清楚先监听、后注册onRegistered必须在register执行前就已处于监听状态这也是示例代码先订阅的原因。否则register会抛出No onRegistered callback handler was provided or registered.自行请求通知权限应在调用register之前请求权限如示例所做。否则register会在 AngularFire 包裹的调用内部代为弹出权限请求导致客户端应用在对话框关闭前一直处于 unstable 状态——这会延迟 Angular 事件重放与服务端渲染 DOM 的清理并在十秒后打印一条仅开发期可见的警告。先主动请求权限即可完全避免该行为可能在未来的版本中改变。标识符通过回调而非返回值送达register不直接返回 installation ID且每当其变化时回调会再次触发所以应从回调内部保存它而不是在启动时只保存一次。示例一个完整的 Messaging Service下面是一个可复制的FcmService完整实现import { EnvironmentInjector, Injectable, inject, runInInjectionContext } from angular/core; import { Messaging, MessagePayload, onMessage, onRegistered, onUnregistered, register, unregister } from angular/fire/messaging; import { Observable, tap } from rxjs; Injectable({ providedIn: root }) export class FcmService { private readonly injector inject(EnvironmentInjector); /* onMessage converted to an observable. Returns the unsubscribe function returned by * onMessage to stop the listener when the last subscriber unsubscribes. */ message$ new ObservableMessagePayload( subscriber onMessage(this.msg, (msg) subscriber.next(msg)) ).pipe(tap((msg) console.log(My Firebase Cloud Message, msg))); constructor(private msg: Messaging) { // Set listeners before calling register to avoid throwing. this.listenForRegistrationChanges(); this.registerForMessages(); } private listenForRegistrationChanges() { onRegistered(this.msg, (installationId) { /* This is a good place to store it in your database for each user. * This callback fires whenever installationId changes. */ console.log(my installation id, installationId); }); onUnregistered(this.msg, (installationId) { // Drop it from your database. Sending messages to an unregistered ID results in a 404. console.log(no longer registered, installationId); }); } private async registerForMessages() { /* Request notification permission before calling register, otherwise * register holds the app unstable until the user answers. */ if ( Notification.permission default await Notification.requestPermission() ! granted ) { return; } // Register the service worker. const serviceWorkerRegistration await navigator.serviceWorker .register(/assets/firebase-messaging-sw.js, { type: module }); /* Run register inside an injection context. Outside one AngularFire cannot wrap it, and * warns. See zones.md for what wrapping adds. */ runInInjectionContext(this.injector, () register(this.msg, { vapidKey: an optional public VAPID key you generate for your Firebase project, serviceWorkerRegistration, }).catch((error) console.error(could not register for messages, error)) ); } // Called from your app, for example when a user turns notifications off. async unregister() { // This calls the imported unregister, not this method. Class methods are not in lexical scope. await unregister(this.msg); } }这个示例中的几个关键设计点值得展开监听先于注册构造函数中先调用listenForRegistrationChanges()再调用registerForMessages()正是为了避免先注册后监听导致的No onRegistered callback handler was provided or registered.错误。权限请求前置仅在Notification.permission default时请求权限若用户拒绝则直接返回不继续注册。注入上下文register放在runInInjectionContext(this.injector, ...)中执行。AngularFire 的 Zone 包装机制详见 docs/zones.md需要依赖注入环境才能包裹 Firebase API 调用在异步回调等上下文已失效的位置直接调用AngularFire 只能发出警告并放弃包装可能造成应用不稳定、变更检测与 SSR 等待异常。unregister的命名遮蔽类方法unregister()与导入的unregister同名但方法体内的await unregister(this.msg)引用的仍是导入的函数类方法不在词法作用域内注释对此有明确说明。Zone 包装与 firebase.ts 的 API 层从 src/messaging/firebase.ts 可以看到angular/fire/messaging重新导出了firebase/messaging的全部符号并对关键 API 做了ɵzoneWrap包装onMessage、onRegistered、onUnregistered、isSupported不视为阻塞调用false而getToken、deleteToken、register、unregister、getMessaging则被标记为会保持应用 unstabletrue的调用。这正是文档中register 会把客户端应用 hold 住直到权限对话框关闭这一行为的技术根源。测试与发送通知方式一Firebase 控制台发送测试通知在 Firebase 控制台的Engage Messaging New Campaign Notifications中可以直接点击发送测试消息快速验证端到端链路是否打通。方式二通过 Cloud Functions 编程式发送Firebase 允许通过 Cloud Functions 编程式发送通知。下面是一个极简的 Node 示例。注意其token字段在迁移期内仍接受 Firebase Installation ID因此无论你是用旧getToken还是新register注册的都适用Firebase Admin SDK 的发送指南提供了专用的fid字段作为后续迁移目标。export const sendTestMessage onRequest(async (_, res) { try { const message { notification: { title: Test Title, body: Test Body, }, token: your token here, you can store these and retreive as you please, }; await admin.messaging().send(message); res.sendStatus(200); } catch (error) { console.error(error); res.sendStatus(500); } });下面是一个更贴近真实业务的示例监听评论集合的新文档给帖子作者推送通知同时把通知写入 Firestore 缓存方便用户在应用内点击查看exports.onPostReply onDocumentCreated(comments/{commentId}, async (event) { if (!event) throw new Error(No event found for document creation); const snapshot event.data; if (!snapshot) { throw new Error(No data associated with the event); } const data snapshot.data(); if (!data.postId) { throw new Error(No post ID found); } const postRef await firestore.collection(posts).doc(data.postId).get(); const postData postRef.data(); if (!postData) { throw new Error(No postData found); } // userUid will be the post authors id. const {userUid} postData; if (!userUid) { throw new Error( Could not find the userUid for the post author for post reply ); } const messageForNotification { title: You have a new reply on your post, body: , }; await createNotificationAndCache(messageForNotification, userUid); }); // If you want to cache notifications a number of times, abstracting this // to a function can bring a lot of value. interface NotificationProps { title: string; body: string; } async function createNotificationAndCache( notificationProps: NotificationProps, userAuthUid: string) { const userRef await firestore.collection(users).where(authUid, , userAuthUid).get(); const userData userRef.docs[0].data(); const promises: Promiseany[] []; // This sample application has seperate fcm tokens for web and mobile if (userData.mobileToken) { const message { notification: notificationProps, token: userData.mobileToken, }; const promise admin.messaging().send(message); promises.push(promise); } if (userData.webToken) { const message { notification: notificationProps, token: userData.webToken, }; const promise admin.messaging().send(message); promises.push(promise); } const notificationCacheValue { userAuthUid: userAuthUid, tokenTitle: notificationProps.title, tokenBody: notificationProps.body, isActive: true, // This determines whether a notification has been seen }; promises.push( firestore.collection(notificationCache).add(notificationCacheValue)); await Promise.all(promises); }发送通知的关键设计原则服务端发送、客户端只注册发送通知必须调用服务端接口。你可以直接用 HTTP 调用也可以在事件触发时用 Cloud Function 发送。Cloud Function 触发器是更理想的选择因为它拥有受信任的数据库访问权限可以安全地查找 token 并发送给正确的用户。保护 HTTP 发送接口如果通过 HTTP 请求发送推送必须保护该 API 调用通常用 Firebase Auth UID 鉴权服务端用 Firebase Admin SDK 校验 UID 后才允许获取用户的推送 ID。令牌与用户关联用户授予权限后应把 token 存入 Realtime Database 或 Cloud Firestore并与唯一标识如 Firebase Auth UID关联之后创建 Cloud Function 触发器在业务事件发生时按 UID 查 token 再推送。注销即删除onUnregistered回调中应从数据库移除该 Installation ID——向已注销的 ID 发送消息会得到 404。分离 web/mobile token示例中用户文档分别存webToken与mobileToken适用于同一用户在 Web 与移动端各有独立 FCM token 的场景。通知缓存notificationCache集合配合isActive字段可记录通知是否已被查看实现点击跳转的应用内通知中心体验。兼容版 API 的参考对照如果你的项目仍在使用旧式 NgModule 兼容 APIangular/fire/compat/messaging其对应文档见 docs/compat/messaging/messaging.md。兼容版以AngularFireMessagingModuleAngularFireMessaging服务形式提供requestPermission、getToken、tokenChanges、messages、requestToken、deleteToken等 Observable 属性实现见 src/compat/messaging/messaging.ts。其核心思路与新 API 一致先搭建 Service Worker、再请求权限、获取 token 并保存到服务端、订阅前台消息区别在于兼容版通过 RxJS Observable 串联各步骤且需在angular.json的assets中显式配置firebase-messaging-sw.js与manifest.json的拷贝。AngularFire 已全面转向新的 tree-shakable API新项目应直接采用本文的provideMessaging方案。总结完整的 FCM 接入闭环包含四个环节搭建 Service Worker版本与应用严格一致、注册实例先onRegistered再register权限请求前置register在注入上下文中执行、保存与同步 token在onRegistered/onUnregistered回调中写入/删除数据库记录、服务端发送Cloud Functions 中按 UID 查 token 推送并缓存通知供应用内查看。遵循新旧 API 不混用、先监听后注册、先权限后注册这三个原则就能避开 FCM 接入中最常见的坑。赞分享后端【免费下载链接】angularfireAngular Firebase ❤️项目地址https://gitcode.com/gh_mirrors/an/angularfire点击查看免费下载相关推荐SWE-RL异步推理架构OpenAIClient并发请求与指数退避重试实现SWE RL异步推理架构OpenAIClient并发请求与指数退避重试实现 SWE RL异步推理架构是开源项目 SWE RLNeurIPS25 官方代码库为什么这份system-design-notes值得收藏28章系统设计面试笔记全览为什么这份system design notes值得收藏28章系统设计面试笔记全览 system design notes 是一份基于《System D文档教程后端iOS应用Firebase Cloud Messaging推送通知完整实现指南iOS应用Firebase Cloud Messaging推送通知完整实现指南 Firebase Cloud MessagingFCM是Google提供的跨移动开发后端认证鉴权上一篇OctoPrint打印质量优化终极指南提升3D打印成功率的10个技巧下一篇iOS文本性能分析工具TTTAttributedLabel优化辅助创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考