基于Firebase实现类WhatsApp的推送通知消息追加功能
实现类似WhatsApp的FCM合并通知功能
我来帮你捋清楚怎么用Firebase Cloud Messaging(FCM)实现这个类似WhatsApp的通知合并逻辑,核心是要结合FCM的通知标签(tag)和服务器端维护用户未读消息状态来完成,具体步骤和代码调整如下:
核心逻辑拆解
WhatsApp的通知规则本质是:
- 用户未读当前会话的通知时,新消息追加到原有通知内容,不弹出新通知
- 用户点击通知标记已读后,重置未读状态,后续新消息作为独立通知展示
要实现这个,你需要在服务器端为每个用户的每个会话(单聊/群聊)维护一个未读消息队列,配合FCM的tag属性复用同一个通知实例。
具体实现步骤
1. 服务器端维护未读消息状态
首先要在你的数据库里为每个用户的每个会话存储未读消息列表,比如用user_id + conversation_id作为联合键来关联。
当有新消息到来时,分两种情况处理:
- 如果用户该会话没有未读消息(或已标记已读):发送独立通知,并将这条消息加入未读列表
- 如果用户该会话已有未读消息:将新消息追加到未读列表,然后发送更新后的合并通知
2. 配置FCM关键参数
重点用好tag属性:
- 给同一个会话设置固定的tag值(比如用会话ID作为tag),这样FCM会自动复用这个tag对应的通知,不会弹出新的通知窗口,只会更新原有通知的内容
collapseKey主要针对后台数据消息,通知类的合并逻辑核心靠tag,可以保留但不用作为重点
3. 处理用户点击通知的已读逻辑
当用户点击通知跳转到你的网页时,要在页面初始化时向服务器发送请求,标记该会话的未读消息为已读,服务器收到请求后清空对应会话的未读消息列表。
修改后的代码示例
服务器端发送通知的PHP代码
// 假设你已经从数据库获取到接收消息的用户ID、会话ID,以及新消息内容 $userId = "接收消息的用户ID"; $conversationId = "当前会话的ID(单聊/群聊ID)"; $newMessageContent = "message2"; // 新收到的消息内容 $fcmToken = $key; // 用户的FCM推送令牌 // 从数据库获取该用户该会话的未读消息列表 function getUnreadMessages($userId, $conversationId) { // 这里写你的数据库查询逻辑,返回未读消息数组 return []; } // 更新数据库中的未读消息列表 function updateUnreadMessages($userId, $conversationId, $messages) { // 这里写你的数据库更新逻辑 } // 获取当前未读消息 $unreadMessages = getUnreadMessages($userId, $conversationId); // 构建通知内容 if (empty($unreadMessages)) { // 无未读消息,发送独立通知 $notificationTitle = "新消息"; $notificationBody = $newMessageContent; $unreadMessages[] = $newMessageContent; } else { // 有未读消息,合并内容 $unreadMessages[] = $newMessageContent; $notificationTitle = count($unreadMessages) . "条新消息"; $notificationBody = implode("\n", $unreadMessages); // 用换行拼接所有未读消息 } // 更新未读消息到数据库 updateUnreadMessages($userId, $conversationId, $unreadMessages); // 构建FCM请求数据 define( 'API_ACCESS_KEY', 'AAAAlHKlaCE_______________________-NdJXkojLf9Ap9mu' ); $data = array( "to" => $fcmToken, "priority" => "normal", "notification" => array( "title" => $notificationTitle, "body" => $notificationBody, "icon" => "../profiles/p1.jpg", "tag" => $conversationId, // 用会话ID作为tag,确保同一个会话的通知复用 "click_action" => "http://website.com?conversationId=" . $conversationId // 把会话ID传到页面 ) ); $data_string = json_encode($data); $headers = array ( 'Authorization: key=' . API_ACCESS_KEY, 'Content-Type: application/json' ); $ch = curl_init(); curl_setopt( $ch,CURLOPT_URL, 'https://fcm.googleapis.com/fcm/send' ); curl_setopt( $ch,CURLOPT_POST, true ); curl_setopt( $ch,CURLOPT_HTTPHEADER, $headers ); curl_setopt( $ch,CURLOPT_RETURNTRANSFER, true ); curl_setopt( $ch,CURLOPT_POSTFIELDS, $data_string); $result = curl_exec($ch); curl_close ($ch);
前端处理已读的JavaScript代码
在click_action指向的页面中,添加以下逻辑:
// 从URL参数中获取会话ID const urlParams = new URLSearchParams(window.location.search); const conversationId = urlParams.get('conversationId'); const currentUserId = "当前登录用户的ID"; // 从你的登录状态中获取 // 向服务器发送已读标记请求 fetch('/api/mark-unread-as-read', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ userId: currentUserId, conversationId: conversationId }), }) .then(response => response.json()) .then(() => { console.log("未读消息已标记为已读"); }) .catch(err => console.error("标记已读失败:", err));
服务器端处理已读请求的PHP接口
// /api/mark-unread-as-read 接口逻辑 $input = json_decode(file_get_contents('php://input'), true); $userId = $input['userId']; $conversationId = $input['conversationId']; // 清空该用户该会话的未读消息列表 function clearUnreadMessages($userId, $conversationId) { // 这里写你的数据库更新逻辑,清空对应未读消息 } clearUnreadMessages($userId, $conversationId); echo json_encode([ 'status' => 'success', 'message' => '未读消息已清空' ]);
额外注意事项
- Tag的唯一性:不同会话必须用不同的tag值,避免不同聊天的通知互相合并干扰
- 通知体验优化:可以在消息内容前加上发送者昵称,比如
"张三: 今晚一起吃饭?\n李四: 好的,几点?",更贴近WhatsApp的样式 - 前台消息处理:如果你的Web应用在前台运行,需要在Service Worker中监听
notificationclick事件,同样触发已读标记逻辑 - 数据库原子性:处理并发消息时,要确保未读消息的更新是原子操作,避免消息丢失
内容的提问来源于stack exchange,提问作者Ahsan Ahmed
相关产品推荐
相关产品推荐

