React聊天PWA集成FCM实现按需触发新消息推送的问题咨询
实现方案
1. 前端页面活跃状态检测
核心通过监听浏览器事件判断用户是否在当前应用页面,状态同步到本地存储供后续逻辑使用
import { useEffect } from 'react'; const App = () => { useEffect(() => { const updateActiveStatus = () => { // 标签页未隐藏 + 窗口处于激活状态才判定为用户在应用内 const isAppActive = !document.hidden && document.hasFocus(); localStorage.setItem('app_is_active', isAppActive ? '1' : '0'); // 可选:将状态同步到后端,用于消息已读标记逻辑 }; // 绑定状态监听事件 document.addEventListener('visibilitychange', updateActiveStatus); window.addEventListener('focus', updateActiveStatus); window.addEventListener('blur', updateActiveStatus); // 初始化状态 updateActiveStatus(); return () => { // 组件销毁时解绑事件 document.removeEventListener('visibilitychange', updateActiveStatus); window.removeEventListener('focus', updateActiveStatus); window.removeEventListener('blur', updateActiveStatus); }; }, []); return <>{/* 应用其余逻辑 */}</> }; export default App;
2. React端FCM接入逻辑
首先安装依赖:npm install firebase @firebase/messaging
FCM初始化封装
// src/firebase.js import { initializeApp } from "firebase/app"; import { getMessaging, getToken, onMessage } from "firebase/messaging"; const firebaseConfig = { // 替换为你自己的Firebase项目配置 apiKey: "YOUR_API_KEY", authDomain: "YOUR_AUTH_DOMAIN", projectId: "YOUR_PROJECT_ID", storageBucket: "YOUR_STORAGE_BUCKET", messagingSenderId: "YOUR_SENDER_ID", appId: "YOUR_APP_ID" }; const app = initializeApp(firebaseConfig); export const messaging = getMessaging(app); // 获取FCM Token并同步到后端 export const initFcm = async () => { // 先申请通知权限 const permission = await Notification.requestPermission(); if (permission !== 'granted') return; try { const currentToken = await getToken(messaging, { vapidKey: "YOUR_VAPID_PUBLIC_KEY", serviceWorkerRegistration: await navigator.serviceWorker.register('/firebase-messaging-sw.js') }); if (currentToken) { // 上传Token到Laravel后端 await fetch('/api/user/fcm-token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ token: currentToken }) }); } } catch (e) { console.error('FCM初始化失败', e); } }; // 监听前台推送消息 export const listenForegroundMsg = (onReceiveMsg) => { onMessage(messaging, (payload) => { const isAppActive = localStorage.getItem('app_is_active') === '1'; if (isAppActive) { // 应用在前台时仅走内部消息更新,不弹系统通知 onReceiveMsg(payload); } else { // 应用不在前台时手动弹系统通知 new Notification(payload.notification.title, { body: payload.notification.body, icon: '/icons/notification-icon.png', data: payload.data }); } }); };
Service Worker配置
在项目public目录下新建firebase-messaging-sw.js,用于处理后台推送:
// 可将对应Firebase JS文件下载到本地托管,避免引用外部资源 importScripts('/static/firebase-app-compat.js'); importScripts('/static/firebase-messaging-compat.js'); firebase.initializeApp({ // 和前端一致的Firebase配置 apiKey: "YOUR_API_KEY", authDomain: "YOUR_AUTH_DOMAIN", projectId: "YOUR_PROJECT_ID", storageBucket: "YOUR_STORAGE_BUCKET", messagingSenderId: "YOUR_SENDER_ID", appId: "YOUR_APP_ID" }); const messaging = firebase.messaging(); // 处理后台/应用关闭时的推送 messaging.onBackgroundMessage((payload) => { const options = { body: payload.notification.body, icon: '/icons/notification-icon.png', data: payload.data }; self.registration.showNotification(payload.notification.title, options); }); // 监听通知点击事件,跳转到对应聊天页面 self.addEventListener('notificationclick', (event) => { event.notification.close(); event.waitUntil( clients.openWindow(`/chat/${event.notification.data.sender_id}`) ); });
3. Laravel后端FCM对接
首先安装Firebase PHP SDK:composer require kreait/firebase-php
配置Firebase
将Firebase控制台下载的服务账号JSON文件放到storage/firebase-service-account.json,在config/services.php中新增配置:
'firebase' => [ 'credentials' => storage_path('firebase-service-account.json'), ],
相关接口实现
// routes/api.php Route::middleware('auth:sanctum')->group(function () { // 保存前端上传的FCM Token Route::post('/user/fcm-token', function (Request $request) { $request->validate(['token' => 'required|string']); $request->user()->update(['fcm_token' => $request->token]); return response()->json(['status' => 'ok']); }); // 发送消息接口 Route::post('/message/send', [\App\Http\Controllers\MessageController::class, 'send']); });
// app/Http/Controllers/MessageController.php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\Message; use Kreait\Firebase\Messaging\CloudMessage; use Kreait\Firebase\Messaging\Notification; class MessageController extends Controller { public function send(Request $request) { $request->validate([ 'receiver_id' => 'required|exists:users,id', 'content' => 'required|string' ]); // 保存消息到数据库 $message = Message::create([ 'sender_id' => auth()->id(), 'receiver_id' => $request->receiver_id, 'content' => $request->content ]); // 给接收方发推送 $receiver = \App\Models\User::find($request->receiver_id); if ($receiver->fcm_token) { $notification = Notification::create(auth()->user()->name, $request->content); $fcmMsg = CloudMessage::withTarget('token', $receiver->fcm_token) ->withNotification($notification) ->withData([ 'sender_id' => auth()->id(), 'message_id' => $message->id ]); app('firebase.messaging')->send($fcmMsg); } return response()->json($message); } }
注意事项
- 每次应用启动时都要校验FCM Token是否更新,及时同步到后端
- 该方案仅适用于支持FCM的地区,国内环境需要替换为对应的推送服务商
- 用户表需要新增
fcm_token字段存储用户设备的推送令牌
内容的提问来源于stack exchange,提问作者Nazmul Hossain
相关产品推荐
相关产品推荐

