FCM v1发送通知遇404(UNREGISTERED)错误及批量发送方案咨询
解决FCM Token过期404错误与批量发送优化
一、修复Token过期导致的404(UNREGISTERED)错误
错误原因
返回的UNREGISTERED错误明确表示目标FCM Token已失效,常见触发场景:
- 用户卸载应用或清除应用数据
- 设备恢复出厂设置
- Token长时间未刷新,被FCM服务器标记为无效
- APP端未同步最新Token到后端
解决方案
实时捕获并清理无效Token
发送通知后解析FCM响应,一旦检测到UNREGISTERED错误,立即从数据库删除对应Token:$response = json_decode($result, true); if (isset($response['error']['details'][0]['errorCode']) && $response['error']['details'][0]['errorCode'] === 'UNREGISTERED') { $support->where('fcm_token', $fcmToken)->delete('app_fcm_token'); }优化Token存储逻辑
- 要求APP端每次启动/登录时,强制同步最新FCM Token到后端
- 数据库中每个用户仅保留最新的有效Token,避免存储过期或重复记录
- 可在
app_fcm_token表添加is_valid字段,定期清理标记为无效的Token
APP端配合Token刷新
移动端集成Firebase SDK时,监听Token刷新事件,获取新Token后立即调用后端接口更新数据库,确保Token始终处于有效状态。
二、FCM v1版本批量发送通知优化
当前代码循环单个发送通知,效率低下且易触发频率限制。FCM v1提供messages:sendAll接口,支持一次性发送最多500条消息,大幅提升发送效率。
重构后的批量发送代码
function sendNotificationBulk($message = null, $title = null, $imageUrl = null, $usernamesString = null) { $usernamesString = '"fktmctsantosh","fblurojit","fktmanil111","ctdeveloper","fltpmin11","fblubishnu7"'; $message = "Test"; $title = "Notification Testing"; $imageUrl = "2024-10-06_11153_recharge_325_new_offer.jpeg&share=2024-10-06_11153.jpeg"; $serviceAccountFile = '/Users/rajivbhandari/Desktop/classic-tech-firebase.json'; $accessToken = $this->getAccessToken($serviceAccountFile); $support = $this->load->database('test', TRUE); $batchSize = 500; // FCM sendAll接口最大支持500条消息 $offset = 0; $image_path = !empty($imageUrl) ? "https://fileserver.classic.com.np/api/files/cat?path=%2F" . $imageUrl : ""; $responseArray = array(); if (!empty($usernamesString)) { do { $sql = "SELECT fcm.fcm_token, fcm.username, fcm.platform FROM app_fcm_token AS fcm WHERE id IN (SELECT MAX(id) FROM app_fcm_token WHERE username IN ($usernamesString) GROUP BY username) ORDER BY fcm.created_date DESC LIMIT $batchSize OFFSET $offset "; $fcmList = $support->query($sql); $queryResultCount = $fcmList->num_rows(); if ($queryResultCount === 0) break; $resultSet = $fcmList->result_array(); $batchMessages = []; // 组装批量消息数组 foreach ($resultSet as $row) { $fcmMsg = [ 'body' => $message, 'title' => $title ]; $batchMessages[] = [ 'token' => $row['fcm_token'], 'notification' => $fcmMsg, 'android' => [ 'priority' => 'high', 'notification' => [ 'sound' => 'default' ] ], 'apns' => [ 'payload' => [ 'aps' => [ 'mutable-content' => 1, 'content-available' => 1 ] ], 'fcm_options' => [ 'image' => $image_path ] ], 'webpush' => [ 'fcm_options' => [ 'link' => $image_path ] ] ]; } // 构造批量请求Payload $payload = [ 'messages' => $batchMessages ]; $headers = [ 'Authorization: Bearer ' . $accessToken, 'Content-Type: application/json' ]; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, 'https://fcm.googleapis.com/v1/projects/test-tech/messages:sendAll'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); $result = curl_exec($ch); if ($result === false) { $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); log_message('error', 'FCM Batch Send Error: ' . curl_error($ch) . ', Status Code: ' . $status_code); } else { $batchResponse = json_decode($result, true); // 遍历批量响应,处理每个消息结果 foreach ($batchResponse['responses'] as $index => $resp) { $row = $resultSet[$index]; $responseArray[$row['username']] = $resp; // 检测无效Token并删除 if (isset($resp['error']['details'][0]['errorCode']) && $resp['error']['details'][0]['errorCode'] === 'UNREGISTERED') { $support->where('fcm_token', $row['fcm_token'])->delete('app_fcm_token'); } } } curl_close($ch); $offset += $batchSize; } while ($queryResultCount > 0); } echo "<pre>"; print_r($responseArray); exit(); }
批量发送注意事项
messages:sendAll接口单次请求最多支持500条消息,超过需拆分批次处理- 批量响应会返回每个消息的单独状态,需逐个处理失败场景
- 确保每个消息的结构与单个发送时一致,避免格式错误导致批量失败
内容的提问来源于stack exchange,提问作者Rajiv Bhandari
相关产品推荐
相关产品推荐

