PHP中FCM HTTP v1批量请求失败问题求助
问题解决方案
一、批量请求文件发送报400的排查与修复
你遇到的「写入文件后发送失败,但HEREDOC构造成功」问题,大概率是文件读写时的格式/编码问题,按以下步骤排查:
- 严格控制批量请求的换行符
FCM批量请求要求所有分隔行必须用\r\n(CRLF),不能用PHP的PHP_EOL(Windows下是CRLF,但Linux下是LF)。构造字符串时直接写\r\n,比如:
$boundary = 'batch_boundary_123'; $batchStr = "--{$boundary}\r\n"; $batchStr .= "Content-Type: application/http\r\n"; $batchStr .= "Content-Transfer-Encoding: binary\r\n\r\n"; $batchStr .= "POST /fcm/send HTTP/1.1\r\n"; $batchStr .= "Content-Type: application/json\r\n\r\n"; $batchStr .= json_encode($singleMessage) . "\r\n\r\n"; // 追加更多请求... $batchStr .= "--{$boundary}--\r\n";
- 用二进制模式写入文件
避免系统自动转换换行符,写入文件时加上FILE_BINARY参数:
file_put_contents('batch_request.txt', $batchStr, LOCK_EX | FILE_BINARY);
- 检查文件是否有BOM头
UTF-8 BOM头(\xef\xbb\xbf)会导致FCM解析失败。可以用文本编辑器(比如Notepad++)查看文件编码,选择「UTF-8无BOM」保存;或者用PHP写入前去除BOM:
$batchStr = preg_replace('/^\xef\xbb\xbf/', '', $batchStr);
- 确保cURL正确读取文件内容
发送时直接读取文件原始内容,不要做额外处理:
$ch = curl_init('https://fcm.googleapis.com/batch'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, file_get_contents('batch_request.txt')); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . $yourAccessToken, "Content-Type: multipart/mixed; boundary={$boundary}" ]); // 其他cURL设置... $response = curl_exec($ch);
二、firebase-php库完整示例(含所有需求)
直接用官方封装的库比自己写批量API更省心,以下是覆盖「JSON认证、Token管理、通知/数据构建、批量发送」的完整代码:
首先安装依赖:
composer require kreait/firebase-php kreait/firebase-tokens
然后编写代码:
<?php require __DIR__.'/vendor/autoload.php'; use Kreait\Firebase\Factory; use Kreait\Firebase\Messaging\MulticastMessage; use Kreait\Firebase\Messaging\Notification; use Kreait\Firebase\Exception\Messaging\InvalidRegistrationToken; // 1. JSON文件认证 $firebase = (new Factory)->withServiceAccount(__DIR__.'/your-service-account.json'); $messaging = $firebase->createMessaging(); // 2. Token管理(验证有效性,过滤无效Token) $targetTokens = ['token_abc', 'token_def', 'token_ghi']; $validTokens = []; foreach ($targetTokens as $token) { try { // 验证Token是否有效 $messaging->validateRegistrationToken($token); $validTokens[] = $token; } catch (InvalidRegistrationToken $e) { // 无效Token,记录日志或从数据库移除 error_log("无效Token: {$token} - {$e->getMessage()}"); } } // 3. 构建通知与数据数组 $notification = Notification::create('新消息提醒', '您有一条未读消息'); $customData = [ 'message_id' => '12345', 'type' => 'chat', 'url' => '/chat/123' ]; // 4. 批量发送通知 $batchMessage = MulticastMessage::new() ->setNotification($notification) ->setData($customData) ->setTokens($validTokens) ->setPriority('high'); // 高优先级确保及时送达 try { $sendResult = $messaging->sendMulticast($batchMessage); // 处理发送结果 echo "成功发送: {$sendResult->successCount()} 条\n"; echo "发送失败: {$sendResult->failureCount()} 条\n"; // 逐个处理失败的请求 foreach ($sendResult->failures() as $index => $failure) { $failedToken = $validTokens[$index]; error_log("Token {$failedToken} 发送失败: {$failure->getMessage()}"); // 如果是Token失效,执行移除逻辑 if ($failure->isInvalidRegistrationToken()) { // 示例:DELETE FROM user_tokens WHERE token = ? } } } catch (\Exception $e) { error_log("批量发送出错: {$e->getMessage()}"); }
三、循环单条发送丢消息的解决办法
循环单条发送出现部分无法送达,主要是以下原因:
- FCM限流:短时间内大量请求会触发FCM的限流机制,导致请求被拒绝。解决办法:改用批量发送(上面的Multicast方式),或者在循环中添加延迟(比如每发送10条暂停1秒)。
- 无效Token未过滤:部分Token已经失效(用户卸载APP、Token过期),这些请求会直接失败,看起来像「无法送达」。必须在发送前验证Token有效性(如上面示例中的
validateRegistrationToken)。 - 网络波动/超时:循环发送时可能遇到网络超时,建议添加重试机制:
function sendSingleMessage($messaging, $token, $message) { $retryTimes = 2; $attempt = 0; while ($attempt < $retryTimes) { try { $messaging->sendToDevice($token, $message); return true; } catch (\Exception $e) { $attempt++; sleep(1); // 重试前暂停1秒 } } return false; }
- 消息优先级设置:如果设置了低优先级,FCM可能延迟送达。发送时设置高优先级:
$message = CloudMessage::new() ->setNotification($notification) ->setData($customData) ->setPriority('high');
内容的提问来源于stack exchange,提问作者chuck fenley
相关产品推荐
相关产品推荐

