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

如何用PHP通过FCM HTTP v1发送推送通知?代码无输出排查

排查FCM HTTP v1 PHP推送无输出无响应问题

你的代码核心问题是错误使用服务账号私钥作为Bearer令牌——FCM HTTP v1 API要求使用OAuth2访问令牌,而非直接使用私钥或旧版的Server Key,这是v1与旧版API的核心差异。以下是具体修复步骤:

1. 先获取OAuth2访问令牌

需要通过服务账号私钥向Google令牌端点请求有效期1小时的访问令牌,实现逻辑如下:

function getFcmAccessToken($serviceAccount) {
    $privateKey = $serviceAccount['private_key'];
    $clientEmail = $serviceAccount['client_email'];
    $tokenUrl = 'https://oauth2.googleapis.com/token';

    $jwtHeader = json_encode([
        'alg' => 'RS256',
        'typ' => 'JWT'
    ]);
    $jwtClaimSet = json_encode([
        'iss' => $clientEmail,
        'scope' => 'https://www.googleapis.com/auth/firebase.messaging',
        'aud' => $tokenUrl,
        'exp' => time() + 3600,
        'iat' => time()
    ]);

    // 编码JWT的Header和Claim Set
    $encodedHeader = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($jwtHeader));
    $encodedClaimSet = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($jwtClaimSet));
    $signatureInput = $encodedHeader . '.' . $encodedClaimSet;

    // 用私钥生成签名
    openssl_sign($signatureInput, $signature, $privateKey, 'SHA256');
    $encodedSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));

    $jwt = $encodedHeader . '.' . $encodedClaimSet . '.' . $encodedSignature;

    // 请求令牌
    $ch = curl_init($tokenUrl);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
        'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        'assertion' => $jwt
    ]));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = json_decode(curl_exec($ch), true);
    curl_close($ch);

    return $response['access_token'] ?? null;
}

2. 修改FCM推送代码

调用上述函数获取令牌后,再发送推送请求,同时添加curl错误排查逻辑:

<?php
$serviceAccount = json_decode(file_get_contents('privatekey.json'), true);
$accessToken = getFcmAccessToken($serviceAccount);

if (!$accessToken) {
    die('Failed to get access token');
}

$projectId = $serviceAccount['project_id'];
$data = [
    'message' => [
        'token' => 'd6dYpnaLSP2zRk4o6dFfFZ:APA91bG-ZB7XFB-01K54cEVAiyMuqbKyBGhYX-XeiR2QcC3QKv1-MXI4wAy_cwL-d1TLNOUkF-pqt0WQed4b2VcL8OeBxXOPVwrMiWv69TkDRRidOPUw_YjA-ISsgCNoxlWUIb5V_PV6',
        'notification' => [
            'title' => 'Your Title',
            'body' => 'Your Message',
        ],
    ],
];

$headers = [
    'Authorization: Bearer ' . $accessToken,
    'Content-Type: application/json',
];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://fcm.googleapis.com/v1/projects/{$projectId}/messages:send");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// 建议开启SSL验证,生产环境不要关闭
// curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

$result = curl_exec($ch);
if ($result === false) {
    echo 'Curl Error: ' . curl_error($ch);
} else {
    echo $result;
}
curl_close($ch);

// 令牌获取函数
function getFcmAccessToken($serviceAccount) {
    $privateKey = $serviceAccount['private_key'];
    $clientEmail = $serviceAccount['client_email'];
    $tokenUrl = 'https://oauth2.googleapis.com/token';

    $jwtHeader = json_encode([
        'alg' => 'RS256',
        'typ' => 'JWT'
    ]);
    $jwtClaimSet = json_encode([
        'iss' => $clientEmail,
        'scope' => 'https://www.googleapis.com/auth/firebase.messaging',
        'aud' => $tokenUrl,
        'exp' => time() + 3600,
        'iat' => time()
    ]);

    $encodedHeader = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($jwtHeader));
    $encodedClaimSet = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($jwtClaimSet));
    $signatureInput = $encodedHeader . '.' . $encodedClaimSet;

    openssl_sign($signatureInput, $signature, $privateKey, 'SHA256');
    $encodedSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));

    $jwt = $encodedHeader . '.' . $encodedClaimSet . '.' . $encodedSignature;

    $ch = curl_init($tokenUrl);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
        'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        'assertion' => $jwt
    ]));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = json_decode(curl_exec($ch), true);
    curl_close($ch);

    return $response['access_token'] ?? null;
}
?>

3. 额外排查要点

  • 确认privatekey.json路径正确,且文件内容完整无格式错误
  • 确保服务账号拥有Firebase Messaging Admin权限(在Google Cloud IAM中配置)
  • 生产环境不要关闭CURLOPT_SSL_VERIFYPEER,避免安全风险
  • 检查设备令牌是否有效,旧版API能成功不代表v1也能,确保令牌未过期或被撤销

内容的提问来源于stack exchange,提问作者Mohsin S S

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 21:54:51