无需FCM/APN及Node.js支持,实现网站关闭时的Web推送通知方案问询
解决方案:无需FCM/APN的Web Push自定义实现
嘿,我刚好踩过类似的坑!其实你完全不需要依赖FCM、APN或者Node.js——Web Push API本身就支持自定义后端实现,只要你的服务器能发送符合标准的HTTP请求就行。之前你试过Service Worker但效果不好,大概率是没结合Web Push的完整流程来做。下面是一步步的落地方案:
1. 先搞定VAPID密钥对
VAPID(Voluntary Application Server Identification)是用来让浏览器的推送服务识别你的服务器,确保推送请求合法的机制。你可以用OpenSSL命令生成密钥对(本地跑就行,不用依赖任何在线工具):
# 生成私钥(存在服务器,别泄露) openssl ecparam -genkey -name prime256v1 -out private_key.pem # 生成公钥(转换为前端能用的base64 URL格式) openssl ec -in private_key.pem -pubout -outform DER | tail -c 65 | base64 | tr '+/' '-_' | tr -d '='
把生成的公钥存到前端代码里,私钥妥善存在服务器端。
2. 客户端:注册Service Worker并订阅推送
首先注册Service Worker,然后请求用户的通知权限,最后订阅浏览器的推送服务,把订阅信息传到你的服务器存起来(比如数据库)。
// 检查浏览器支持 if ('serviceWorker' in navigator && 'PushManager' in window) { window.addEventListener('load', async () => { try { // 注册Service Worker const registration = await navigator.serviceWorker.register('/sw.js'); console.log('Service Worker 注册成功'); // 请求通知权限 const permission = await Notification.requestPermission(); if (permission !== 'granted') { throw new Error('用户拒绝了通知权限,没法推送哦'); } // 替换成你生成的VAPID公钥 const vapidPublicKey = '你的VAPID公钥'; const convertedKey = urlBase64ToUint8Array(vapidPublicKey); // 订阅推送服务,拿到订阅对象 const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, // 必须设为true,所有推送都要显示给用户 applicationServerKey: convertedKey }); // 把订阅信息发送到服务器保存 await fetch('/api/save-subscription', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(subscription) }); } catch (error) { console.error('订阅失败:', error); } }); } // 辅助函数:把base64 URL格式的公钥转成Uint8Array(前端需要这个格式) function urlBase64ToUint8Array(base64String) { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/'); const rawData = window.atob(base64); return Uint8Array.from([...rawData].map(char => char.charCodeAt(0))); }
3. Service Worker:接收推送并显示通知
创建sw.js文件,监听push事件,当服务器的推送到达时,就弹出通知;另外处理通知的点击事件,跳转到指定页面。
// 监听推送事件 self.addEventListener('push', (event) => { // 解析推送过来的内容,没有的话用默认值 const data = event.data?.json() || { title: '新通知', body: '你有一条未读消息' }; const notificationOptions = { body: data.body, icon: '/path-to-your-icon.png', // 替换成你的通知图标 badge: '/path-to-your-badge.png', data: { url: data.url || '/' } // 点击通知要跳转的URL }; // 显示通知,用waitUntil确保Service Worker不会提前终止 event.waitUntil( self.registration.showNotification(data.title, notificationOptions) ); }); // 监听通知点击事件 self.addEventListener('notificationclick', (event) => { event.notification.close(); // 关闭通知 // 打开指定页面 event.waitUntil( clients.openWindow(event.notification.data.url) ); });
4. 服务器端:发送推送请求(以PHP为例)
既然你的服务器不支持Node.js,那用PHP来实现就很合适——大部分服务器都支持PHP。这里推荐用minishlink/web-push这个库来简化逻辑(可以用Composer安装,要是服务器不让用Composer,也可以手动引入源码):
<?php // 引入库(如果用Composer的话) require __DIR__ . '/vendor/autoload.php'; use Minishlink\WebPush\WebPush; use Minishlink\WebPush\Subscription; // 替换成你生成的VAPID密钥对 $vapidKeys = [ 'publicKey' => '你的VAPID公钥', 'privateKey' => '你的VAPID私钥' ]; // 从数据库里取出之前保存的用户订阅信息(这里是示例数据) $userSubscription = [ 'endpoint' => '用户订阅的推送端点(比如Chrome的FCM地址,但我们不用Firebase SDK)', 'keys' => [ 'p256dh' => '用户订阅的p256dh密钥', 'auth' => '用户订阅的auth密钥' ] ]; // 构造订阅对象 $subscription = Subscription::create($userSubscription); // 要发送的通知内容(转成JSON) $payload = json_encode([ 'title' => '自定义推送', 'body' => '这是不依赖FCM的推送消息!', 'url' => '/notification-detail' ]); // 初始化WebPush实例 $webPush = new WebPush($vapidKeys); // 发送推送 $webPush->sendNotification( $subscription, $payload, function ($subscription, $status) { if ($status['success']) { echo '推送发送成功'; } else { echo '推送失败: ' . $status['message']; // 如果订阅失效了(比如用户清了浏览器数据),记得从数据库删掉 if ($status['expired']) { // 这里写删除订阅记录的逻辑 } } } ); // 确保所有请求都发送出去 $webPush->flush(); ?>
几个关键注意点
- HTTPS必须有:Web Push API只能在HTTPS环境下用(本地开发的
localhost除外),所以你的服务器得配好SSL证书。 - 订阅失效要处理:用户可能会清数据、卸载浏览器或者关通知,这时候订阅端点就没用了,服务器发推送会收到错误,记得及时删掉失效的订阅。
- Payload别太大:不同浏览器对推送内容的大小限制不一样,一般控制在4KB以内比较稳妥。
- 浏览器兼容性:现在主流浏览器(Chrome、Firefox、Edge、Safari 16.4+)都支持Web Push,Safari 16.4之后不用APN了,完全兼容标准。
这样一来,就算用户关闭了你的网站,只要浏览器在后台运行,Service Worker就能收到推送并弹出通知,完全不需要依赖FCM/APN的SDK,也不用Node.js服务器。
内容的提问来源于stack exchange,提问作者Sriramselvakumar S
相关产品推荐
相关产品推荐

