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

无需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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 23:27:29