PHP实现Web端FCM推送通知:从AJAX触发到浏览器正确接收的完整指南

夏涛吖_1621

夏涛吖_1621

2026-09-26

767人浏览

原创

PHP实现Web端FCM推送通知:从AJAX触发到浏览器正确接收的完整指南

本文详解如何在php后端(以codeigniter 4为例)配合前端firebase messaging,通过ajax安全触发fcm推送,并解决“响应成功却无通知弹出”的典型问题——核心在于理解浏览器通知的可见性约束、service worker生命周期及fcm v1 api升级要求。

本文详解如何在php后端(以codeigniter 4为例)配合前端firebase messaging,通过ajax安全触发fcm推送,并解决“响应成功却无通知弹出”的典型问题——核心在于理解浏览器通知的可见性约束、service worker生命周期及fcm v1 api升级要求。

在Web端使用FCM向用户设备(如PC浏览器)发送通知,看似只需调用/fcm/send接口即可,但实践中常出现"success":1却收不到通知的困惑——正如提问者所遇:curl_exec()返回了合法的multicast_id和message_id,但页面前台无任何提示。这并非代码错误,而是对Web Push机制本质的理解偏差所致。

? 根本原因:浏览器通知的“不可见性”与上下文限制

FCM向浏览器发送的是Web Push消息,其展示完全依赖于已注册并处于激活状态的 Service Worker。关键规则如下:

  • ✅ 通知仅在页面后台(非聚焦)或已关闭时显示:当用户当前正浏览你的网站(即页面处于前台/聚焦状态),浏览器默认不自动弹出通知——这是W3C规范强制行为,防止干扰用户体验。此时,消息会静默送达Service Worker,需由开发者手动调用self.registration.showNotification()处理。
  • ❌ 本地开发环境(http://localhost)无法获取有效Push Token:现代浏览器(Chrome ≥91、Firefox ≥89)要求Web Push必须在HTTPS环境下运行(或localhost例外)。但即使使用localhost,若未正确注册Service Worker、缺少manifest.json或未在firebase-messaging-sw.js中配置getMessaging(),messaging.getToken()将返回null或抛出权限异常——提问者“本地无法生成token”即源于此。

? 验证技巧:打开浏览器开发者工具 → Application → Service Workers,确认worker已注册且状态为activated;再检查Console是否有Failed to register a ServiceWorker报错。

✅ 正确实现路径(CodeIgniter 4 + Firebase v9+)

1. 前端:确保Service Worker就绪并监听消息

在项目根目录创建 firebase-messaging-sw.js(必须位于域名根路径):

btpanel phpsite 宝塔面板PHP网站
btpanel phpsite 宝塔面板PHP网站

宝塔面板 PHP 网站管理:站点创建、删除、启停、PHP 版本切换、域名管理、SSL证书管理、伪静态管理、数据库管理

下载
// firebase-messaging-sw.js
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.23.0/firebase-app-compat.js";
import { getMessaging, onBackgroundMessage } from "https://www.gstatic.com/firebasejs/9.23.0/firebase-messaging-compat.js";

const firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_PROJECT_ID.firebaseapp.com",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_PROJECT_ID.appspot.com",
  messagingSenderId: "SENDER_ID",
  appId: "APP_ID"
};

const app = initializeApp(firebaseConfig);
const messaging = getMessaging(app);

// 后台消息处理(页面关闭时触发通知)
onBackgroundMessage(messaging, (payload) => {
  console.log("[firebase-messaging-sw.js] Received background message ", payload);
  const notificationTitle = payload.notification.title;
  const notificationOptions = {
    body: payload.notification.body,
    icon: "/icons/icon-192x192.png" // 确保图标路径可访问
  };
  self.registration.showNotification(notificationTitle, notificationOptions);
});

2. 前端JS:安全获取Token并发送AJAX

// 确保在HTTPS或localhost下执行
if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    try {
      const registration = await navigator.serviceWorker.register('/firebase-messaging-sw.js');
      const messaging = getMessaging();
      const token = await getToken(messaging, { 
        vapidKey: 'YOUR_VAPID_PUBLIC_KEY_BASE64' // 从Firebase Console获取
      });
      console.log('FCM Token:', token);

      // 绑定登录按钮事件(避免重复绑定)
      $('#login_btn').off('click').on('click', function() {
        const email = $('input[name=email]').val();
        const pass = $('input[name=pass]').val();
        if (email && pass) {
          $.ajax({
            url: '= base_url('Login/aksiLogin') ?>',
            type: 'POST',
            data: { email, pass, device_token: token },
            success: () => alert('登录成功!通知已发送'),
            error: (xhr) => console.error('推送失败:', xhr.responseText)
          });
        }
      });

    } catch (err) {
      console.error('获取Token失败:', err);
      // 提示用户启用通知或检查HTTPS
      alert('请确保网站运行在HTTPS环境,或允许浏览器通知权限');
    }
  });
}

3. PHP后端:升级至FCM HTTP v1 API(关键!)

旧版https://fcm.googleapis.com/fcm/send(Legacy API)已于2023年弃用,必须迁移到v1 API,否则虽返回成功,但实际无法触达客户端:

// app/Helpers/Notification_helper.php
if (!function_exists('send_notif')) {
    function send_notif($token, $title, $body) {
        // ✅ 使用FCM HTTP v1 API(需OAuth2令牌)
        $url = 'https://fcm.googleapis.com/v1/projects/YOUR-FIREBASE-PROJECT-ID/messages:send';

        // 获取Access Token(推荐使用Firebase Admin SDK或服务账号密钥)
        $accessToken = get_fcm_access_token(); // 实现见下方

        $payload = [
            'message' => [
                'token' => $token,
                'notification' => [
                    'title' => $title,
                    'body' => $body
                ],
                'webpush' => [ // 显式指定Web Push字段
                    'fcm_options' => ['link' => 'https://yourdomain.com/'] // 点击跳转链接
                ]
            ]
        ];

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer {$accessToken}",
                "Content-Type: application/json"
            ],
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POSTFIELDS => json_encode($payload),
            CURLOPT_SSL_VERIFYPEER => false, // 生产环境请启用证书验证
            CURLOPT_TIMEOUT => 30
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        // 记录日志并清理失效Token
        if ($httpCode !== 200) {
            error_log("FCM Error [{$httpCode}]: {$response}");
            // 若返回404或400,建议从数据库删除该token
        }
        return $response;
    }
}

// 获取FCM Access Token(使用服务账号JSON文件)
function get_fcm_access_token() {
    $serviceAccountPath = WRITEPATH . 'firebase-service-account.json'; // 安全存放路径
    $key = json_decode(file_get_contents($serviceAccountPath), true);

    $issuedAt = time();
    $expiresAt = $issuedAt + 3600; // 1小时有效期

    $jwtHeader = ['alg' => 'RS256', 'typ' => 'JWT'];
    $jwtPayload = [
        'iss' => $key['client_email'],
        'scope' => 'https://www.googleapis.com/auth/firebase.messaging',
        'aud' => 'https://oauth2.googleapis.com/token',
        'exp' => $expiresAt,
        'iat' => $issuedAt
    ];

    // 使用openssl_sign生成JWT(需PHP openssl扩展)
    $privateKey = openssl_pkey_get_private($key['private_key']);
    $jwt = base64_encode(json_encode($jwtHeader)) . '.' 
         . base64_encode(json_encode($jwtPayload));
    openssl_sign($jwt, $signature, $privateKey, 'sha256');

    $jwt .= '.' . base64_encode($signature);

    // 请求Access Token
    $tokenUrl = 'https://oauth2.googleapis.com/token';
    $postData = http_build_query([
        'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        'assertion' => $jwt
    ]);

    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL => $tokenUrl,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $postData,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded']
    ]);
    $response = json_decode(curl_exec($ch), true);
    curl_close($ch);

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

⚠️ 关键注意事项

  • 绝不硬编码Server Key或私钥:server_key 和 firebase-service-account.json 必须存放在Web根目录外(如WRITEPATH),并通过环境变量或配置中心管理。
  • Token失效处理:FCM响应中若含"error": "InvalidRegistration",需立即从数据库删除该token,避免后续推送失败。
  • HTTPS是硬性要求:生产环境必须部署SSL证书;开发测试可临时使用ngrok或localtunnel提供HTTPS隧道。
  • 调试利器:使用FCM Diagnostics Tool验证Token有效性及消息路由。

✅ 总结

“FCM返回成功却无通知”的本质,是混淆了消息投递成功(服务器接收)与用户感知成功(浏览器渲染)两个阶段。真正的解决方案是:
① 确保Service Worker正确注册并实现onBackgroundMessage;
② 升级至FCM v1 API并使用OAuth2认证;
③ 接受“前台页面不弹窗”的规范约束,转而优化后台消息体验;
④ 严格遵循HTTPS与权限模型。

至此,你的CodeIgniter 4应用即可稳定、合规地向用户浏览器发送高到达率的推送通知。

php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!

相关文章

PHP速学教程(入门到精通)
PHP速学教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

php ajax 浏览器

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
php文件怎么打开
php文件怎么打开

打开php文件步骤:1、选择文本编辑器;2、在选择的文本编辑器中,创建一个新的文件,并将其保存为.php文件;3、在创建的PHP文件中,编写PHP代码;4、要在本地计算机上运行PHP文件,需要设置一个服务器环境;5、安装服务器环境后,需要将PHP文件放入服务器目录中;6、一旦将PHP文件放入服务器目录中,就可以通过浏览器来运行它。

2023.09.01

9364

6

php怎么取出数组的前几个元素
php怎么取出数组的前几个元素

取出php数组的前几个元素的方法有使用array_slice()函数、使用array_splice()函数、使用循环遍历、使用array_slice()函数和array_values()函数等。本专题为大家提供php数组相关的文章、下载、课程内容,供大家免费下载体验。

2023.10.11

5661

5

php反序列化失败怎么办
php反序列化失败怎么办

php反序列化失败的解决办法检查序列化数据。检查类定义、检查错误日志、更新PHP版本和应用安全措施等。本专题为大家提供php反序列化相关的文章、下载、课程内容,供大家免费下载体验。

2023.10.11

2035

5

php怎么连接mssql数据库
php怎么连接mssql数据库

连接方法:1、通过mssql_系列函数;2、通过sqlsrv_系列函数;3、通过odbc方式连接;4、通过PDO方式;5、通过COM方式连接。想了解php怎么连接mssql数据库的详细内容,可以访问下面的文章。

2023.10.23

3528

4

php连接mssql数据库的方法
php连接mssql数据库的方法

php连接mssql数据库的方法有使用PHP的MSSQL扩展、使用PDO等。想了解更多php连接mssql数据库相关内容,可以阅读本专题下面的文章。

2023.10.23

4214

6

html怎么上传
html怎么上传

html通过使用HTML表单、JavaScript和PHP上传。更多关于html的问题详细请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.03

3291

9

PHP出现乱码怎么解决
PHP出现乱码怎么解决

PHP出现乱码可以通过修改PHP文件头部的字符编码设置、检查PHP文件的编码格式、检查数据库连接设置和检查HTML页面的字符编码设置来解决。更多关于php乱码的问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.09

4677

8

php文件怎么在手机上打开
php文件怎么在手机上打开

php文件在手机上打开需要在手机上搭建一个能够运行php的服务器环境,并将php文件上传到服务器上。再在手机上的浏览器中输入服务器的IP地址或域名,加上php文件的路径,即可打开php文件并查看其内容。更多关于php相关问题,详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.13

3662

8

sprintf函数用法详解
sprintf函数用法详解

sprintf函数的用法:1、格式化字符串;2、指定输出宽度和精度;3、返回值。更多关于sprintf函数用法详解的内容,大家可以阅读下面的文章。

2023.11.27

11682

4

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
墨刀帮助中心
墨刀帮助中心

共0课时 | 0人学习

MyEclipse学习中心
MyEclipse学习中心

共0课时 | 0人学习

Apache Subversion 官方手册
Apache Subversion 官方手册

共0课时 | 0人学习