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

Laravel10中使用FCM API V1批量推送通知遇400错误求助

解决FCM API V1批量推送的400 Bad Request错误

问题背景

此前使用Cloud Messaging API (Legacy),单次CURL请求可向1000台设备推送通知。切换至Firebase Cloud Messaging API (V1)后,循环单推耗时久且增加PHP 8.1.0应用服务器性能开销,已在Firebase控制台删除旧服务器密钥。编写代码尝试批量推送时触发400错误,需解决该问题并实现高效批量推送。

现有代码

FcmService服务类

namespace App\Services;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Firebase\JWT\JWT;

class FcmService
{
    protected $client;
    protected $url;
    protected $credentialsFilePath;

    public function __construct()
    {
        $this->client = new Client();
        $this->credentialsFilePath = public_path('xxxxxx.json');
        $this->url = 'https://fcm.googleapis.com/v1/projects/xxxx/messages:send';
    }

 public function sendMultiNotification(array $tokens, string $title, string $body)
{
    $responses = [];
    $batchSize = 500; // Maximum allowed tokens per request

    // Split tokens into batches
    foreach (array_chunk($tokens, $batchSize) as $tokenBatch) {
        $message = [
            'message' => [
                'tokens' => $tokenBatch, // Use 'tokens' for multiple device tokens
                'notification' => [
                    'title' => $title,
                    'body' => $body,
                ],
            ],
        ];

        try {
            $response = $this->client->post($this->url, [
                'headers' => [
                    'Authorization' => 'Bearer ' . $this->generateAccessToken(),
                    'Content-Type' => 'application/json',
                ],
                'json' => $message,
            ]);

            // Log the response status and body
            \Log::info('FCM Response Status: ' . $response->getStatusCode());
            \Log::info('FCM Response Body: ' . $response->getBody());

            $responses[] = json_decode($response->getBody(), true);
        } catch (RequestException $e) {
            \Log::error('FCM Error: ' . $e->getMessage());
            $responses[] = ['error' => $e->getMessage()];
        }
    }

    return $responses; // Return all responses for each batch
}

    protected function generateAccessToken()
    {
        $credentials = json_decode(file_get_contents($this->credentialsFilePath), true);
        $now = time();
        $payload = [
            'iss' => $credentials['client_email'],
            'sub' => $credentials['client_email'],
            'iat' => $now,
            'exp' => $now + 3600,
            'aud' => 'https://fcm.googleapis.com/',
        ];

        return JWT::encode($payload, $credentials['private_key'], 'RS256');
    }
}

控制器代码

namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\FcmService;
use Illuminate\Http\Request;

class FcmController1 extends Controller
{
    protected $fcmService;

    public function __construct(FcmService $fcmService)
    {
        $this->fcmService = $fcmService; // Assign the injected service to the class property
    }


    public function send(Request $request)
    {
        $tokens =['xxxxx',xxx2'];
$title = 'Your Notification Title';
$body = 'Your Notification Body';

$responses = $this->fcmService->sendMultiNotification($tokens, $title, $body);

        return response()->json($response);
    }
}

错误信息

"error": "Client error: POST https://fcm.googleapis.com/v1/projects/newbonuscard/messages:send resulted in a 400 Bad Request response:
{
"error": {
"code": 400,
"message": "Invalid JSON payload received. Unknown name "tokens" at 'message': Prot (truncated...)
"

解决方法

1. 修正API端点与请求体结构

FCM V1 API的messages:send接口仅支持单设备推送,批量推送需使用messages:sendAll接口,且请求体需构造为包含多个单设备消息的数组(无tokens字段,每个消息用token指定设备)。

修改FcmService的关键代码:

public function __construct()
{
    $this->client = new Client();
    $this->credentialsFilePath = public_path('xxxxxx.json');
    // 替换为批量推送接口
    $this->url = 'https://fcm.googleapis.com/v1/projects/xxxx/messages:sendAll';
}

public function sendMultiNotification(array $tokens, string $title, string $body)
{
    $responses = [];
    $batchSize = 500; // FCM sendAll接口最多支持500条消息/请求

    foreach (array_chunk($tokens, $batchSize) as $tokenBatch) {
        // 构造批量消息数组
        $messages = [];
        foreach ($tokenBatch as $token) {
            $messages[] = [
                'notification' => [
                    'title' => $title,
                    'body' => $body,
                ],
                'token' => $token
            ];
        }

        $requestBody = [
            'messages' => $messages
        ];

        try {
            $response = $this->client->post($this->url, [
                'headers' => [
                    'Authorization' => 'Bearer ' . $this->generateAccessToken(),
                    'Content-Type' => 'application/json',
                ],
                'json' => $requestBody,
            ]);

            \Log::info('FCM Response Status: ' . $response->getStatusCode());
            \Log::info('FCM Response Body: ' . $response->getBody());

            $responses[] = json_decode($response->getBody(), true);
        } catch (RequestException $e) {
            \Log::error('FCM Error: ' . $e->getMessage());
            $responses[] = ['error' => $e->getMessage()];
        }
    }

    return $responses;
}

2. 修正控制器中的语法错误

  • 令牌数组中字符串需加引号:$tokens = ['xxxxx', 'xxx2'];
  • 返回变量名需匹配:return response()->json($responses);

修改后的控制器代码片段:

public function send(Request $request)
{
    $tokens = ['xxxxx', 'xxx2'];
    $title = 'Your Notification Title';
    $body = 'Your Notification Body';

    $responses = $this->fcmService->sendMultiNotification($tokens, $title, $body);

    return response()->json($responses);
}

3. 验证权限与密钥

确保服务账号JSON文件拥有cloudmessaging.messages.create权限,且服务器能正常读取该文件。

内容的提问来源于stack exchange,提问作者Qasim Boqaai

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 14:20:55