如何用PHP项目实现Web Push?从零搭建浏览器推送通知系统
目录导读
- Web Push 技术原理与适用场景
- PHP 实现 Web Push 的核心组件
- 服务端准备工作:生成 VAPID 密钥对
- 客户端:注册 Service Worker 与订阅推送
- 服务端:使用 PHP 发送推送通知
- 数据库存储与用户管理策略
- 安全性考量与常见问题避坑
- FAQ:开发者最关心的 5 个问题
Web Push 技术原理与适用场景
Web Push(浏览器推送通知)允许网站在用户未打开网页时,仍能通过操作系统向用户推送消息,这项技术基于 Push API (客户端) 和 Web Push Protocol (服务端) 实现,核心工作流如下:

- 用户访问网站 → 同意接收通知 → 浏览器生成
PushSubscription对象(包含推送端点、公钥和身份令牌) - 服务端将
PushSubscription保存到数据库 - 当需要推送时,服务端通过 HTTP POST 请求将加密消息发送到推送端点(通常由浏览器厂商提供,如 FCM、APNs)
- 浏览器解密消息后,触发 Service Worker 显示通知
适用场景:电商促销提醒、博客更新通知、即时通讯消息提醒、SaaS 服务状态变更通知等。
PHP 实现 Web Push 的核心组件
要用 PHP 实现完整的 Web Push,你需要以下组件:
| 组件 | 说明 |
|---|---|
| VAPID 密钥 | 用于服务端身份验证,需生成一对公钥和私钥 |
| Service Worker | JavaScript 文件,负责接收推送事件并显示通知 |
| 客户端 JS | 注册 Service Worker、创建订阅、发送订阅数据到服务器 |
| PHP 服务端 | 保存订阅、调用 Web Push 库发送通知 |
| 数据库 | 存储用户的 PushSubscription 对象 |
推荐 PHP 库:minishlink/web-push(PHP 7.4+ 兼容,Github 2.5k+ Stars),它封装了加密、VAPID 签名和 HTTP 请求。
服务端准备工作:生成 VAPID 密钥对
VAPID (Voluntary Application Server Identification) 是 Web Push 的强制要求,在 PHP 项目根目录执行:
composer require minishlink/web-push
然后通过 PHP 代码生成密钥对:
require 'vendor/autoload.php'; use Minishlink\WebPush\VAPID; $keys = VAPID::createVapidKeys(); echo "Public Key: " . $keys['publicKey'] . "\n"; echo "Private Key: " . $keys['privateKey'] . "\n";
将生成的公钥和私钥保存到环境变量或配置文件中(如 .env),切勿硬编码在源码中。
客户端:注册 Service Worker 与订阅推送
Step 1:创建 sw.js (Service Worker 文件)
self.addEventListener('push', event => {
const data = event.data.json();
const options = {
body: data.body,
icon: '/icon.png',
badge: '/badge.png'
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});
Step 2:客户端 JS 请求权限并订阅
if ('Notification' in window && 'serviceWorker' in navigator) {
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
navigator.serviceWorker.register('/sw.js').then(registration => {
// 使用 VAPID 公钥创建订阅
const applicationServerKey = urlBase64ToUint8Array('你的VAPID公钥');
registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerKey
}).then(subscription => {
// 将 subscription 对象发送到 PHP 后端保存
fetch('/save-subscription.php', {
method: 'POST',
body: JSON.stringify(subscription),
headers: { 'Content-Type': 'application/json' }
});
});
});
}
});
}
注意:applicationServerKey 需要从 Base64 转换为 Uint8Array(参考 MDN 提供的转换函数)。
服务端:使用 PHP 发送推送通知
保存订阅的 PHP 端点(save-subscription.php)
<?php
require 'vendor/autoload.php';
$input = json_decode(file_get_contents('php://input'), true);
$subscription = [
'endpoint' => $input['endpoint'],
'keys' => [
'p256dh' => $input['keys']['p256dh'],
'auth' => $input['keys']['auth']
]
];
// 保存到数据库(使用 PDO 示例)
$db = new PDO('sqlite:subscriptions.db');
$stmt = $db->prepare('INSERT INTO subscriptions (endpoint, p256dh, auth) VALUES (?, ?, ?)');
$stmt->execute([$subscription['endpoint'], $subscription['keys']['p256dh'], $subscription['keys']['auth']]);
echo 'ok';
发送推送通知(send-push.php)
<?php
require 'vendor/autoload.php';
use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;
$auth = [
'VAPID' => [
'subject' => 'mailto:your-email@example.com', // 必须为邮箱格式
'publicKey' => '你的公钥',
'privateKey' => '你的私钥'
]
];
$webPush = new WebPush($auth);
// 从数据库读取所有订阅
$db = new PDO('sqlite:subscriptions.db');
$rows = $db->query('SELECT * FROM subscriptions')->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
$subscription = Subscription::create([
'endpoint' => $row['endpoint'],
'publicKey' => $row['p256dh'],
'authToken' => $row['auth'],
]);
$payload = json_encode([
'title' => '新消息通知',
'body' => '您有一条新的订单状态更新'
]);
$webPush->queueNotification($subscription, $payload);
}
// 发送所有队列中的通知
foreach ($webPush->flush() as $report) {
if (!$report->isSuccess()) {
// 处理失败:如果端点返回 410 Gone,删除该订阅
error_log('推送失败: ' . $report->getEndpoint());
}
}
数据库存储与用户管理策略
推荐表结构(MySQL/MariaDB 示例):
CREATE TABLE push_subscriptions (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT, -- 关联你的用户系统
endpoint TEXT NOT NULL,
p256dh VARCHAR(255) NOT NULL,
auth VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY unique_endpoint (endpoint(255))
);
优化建议:
- 设置
endpoint唯一索引,避免重复订阅 - 定期清理失效订阅(推送失败时删除对应记录)
- 关联用户 ID 实现定向推送
安全性考量与常见问题避坑
- HTTPS 强制要求:Web Push 只能在 HTTPS 或 localhost 环境下工作
- 权限管理:始终检查
Notification.permission,不要擅自弹出请求 - 消息加密:
minishlink/web-push库自动处理加密,无需手动实现 - 错误处理:推送失败时检查响应状态码:
410 Gone:订阅已过期,立即删除该记录429 Too Many Requests:降级推送频率
- 测试环境:Chrome DevTools → Application → Service Workers 中手动触发推送测试
FAQ:开发者最关心的 5 个问题
Q1:为什么我的推送通知只有 Chrome 能收到,Safari 收不到?
A:Safari 14 以后开始支持 Web Push,但要求:
- 使用 macOS 或 iOS 16.4+
- 必须先将网站添加到主屏幕(PWA)
- 需要使用 Apple 的推送证书(非 VAPID),建议查阅苹果开发者文档
Q2:用户关闭浏览器后推送还能收到吗?
A:只要操作系统推送服务(如 FCM/APNs)可达,即使浏览器完全关闭也能收到,但如果用户清除了网站数据或禁用了通知权限,则无法接收。
Q3:推送通知有大小限制吗?
A:推荐 < 4KB,实际限制由浏览器决定(Chrome 约 4KB, Firefox 约 64KB),大负载请使用推送通知中的 data 字段传递 ID,客户端通过 API 获取完整内容。
Q4:如何实现“只向特定用户”推送?
A:在保存订阅时关联用户 ID,发送时根据业务逻辑查询符合条件的用户订阅列表,循环发送。
Q5:免费方案能支撑多少用户?
A:Web Push 本身不产生直接成本(通过浏览器厂商的推送端点发送),但大量推送可能触发服务商的速率限制,对于小型项目(< 1 万订阅),免费方案通常够用。
PHP 实现 Web Push 的路径清晰:VAPID 密钥生成 → Service Worker 注册 → 订阅保存 → 后端推送,关键点在于正确处理订阅的生命周期(添加、失效、删除)和加密通信,对于大型生产环境,建议添加队列系统(如 Redis)异步处理推送,避免 PHP 进程阻塞。
扩展阅读:
- MDN:Push API 文档
- GitHub:
minishlink/web-push项目 Wiki - Google Developers:Web Push 最佳实践
注意:推送通知对用户体验影响大,请确保只在必要时推送有价值内容,避免过度打扰用户。