
本文详解如何在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(必须位于域名根路径):
// 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免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











