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

PHP中FCM HTTP v1批量请求失败问题求助

问题解决方案

一、批量请求文件发送报400的排查与修复

你遇到的「写入文件后发送失败,但HEREDOC构造成功」问题,大概率是文件读写时的格式/编码问题,按以下步骤排查:

  1. 严格控制批量请求的换行符
    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";
  1. 用二进制模式写入文件
    避免系统自动转换换行符,写入文件时加上FILE_BINARY参数:
file_put_contents('batch_request.txt', $batchStr, LOCK_EX | FILE_BINARY);
  1. 检查文件是否有BOM头
    UTF-8 BOM头(\xef\xbb\xbf)会导致FCM解析失败。可以用文本编辑器(比如Notepad++)查看文件编码,选择「UTF-8无BOM」保存;或者用PHP写入前去除BOM:
$batchStr = preg_replace('/^\xef\xbb\xbf/', '', $batchStr);
  1. 确保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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 18:01:27