You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FCM v1发送通知遇404(UNREGISTERED)错误及批量发送方案咨询

解决FCM Token过期404错误与批量发送优化

一、修复Token过期导致的404(UNREGISTERED)错误

错误原因

返回的UNREGISTERED错误明确表示目标FCM Token已失效,常见触发场景:

  • 用户卸载应用或清除应用数据
  • 设备恢复出厂设置
  • Token长时间未刷新,被FCM服务器标记为无效
  • APP端未同步最新Token到后端

解决方案

  1. 实时捕获并清理无效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');
    }
    
  2. 优化Token存储逻辑

    • 要求APP端每次启动/登录时,强制同步最新FCM Token到后端
    • 数据库中每个用户仅保留最新的有效Token,避免存储过期或重复记录
    • 可在app_fcm_token表添加is_valid字段,定期清理标记为无效的Token
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 13:36:10