web push通知不能由php直接发送给浏览器,php仅负责存储endpoint、加密payload并调用fcm/webpush api;需用web-push-php库处理vapid签名与aes-gcm加密,避免手写加密逻辑,并严格校验响应以清理失效endpoint。

Web Push 通知不能靠 PHP 直接发给浏览器
PHP 本身没有能力直接向用户浏览器发送 Web Push 通知——它既不持有推送端点(endpoint),也不管理用户授权状态,更无法加密消息并调用 https://fcm.googleapis.com/v1/projects/... 这类推送服务接口。真正触发通知的是前端注册的 ServiceWorker,后端(PHP)只负责把加密后的消息 POST 到推送服务(如 Firebase Cloud Messaging 或 WebPush 服务)。
PHP 要做的三件事:存 endpoint、加密 payload、调用 FCM / WebPush API
用户在前端点击“允许通知”后,registration.pushManager.subscribe() 返回一个 PushSubscription 对象,其中的 endpoint、keys.p256dh、keys.auth 必须由 PHP 安全存储(比如数据库),后续推送时才能复用。
- 用
curl_init()向https://fcm.googleapis.com/v1/projects/YOUR-PROJECT-ID/messages:send发送 POST 请求(需 OAuth2 Token) - 或使用标准 WebPush 协议(推荐):用
web-push-php库(如minishlink/web-push),它自动处理 VAPID 签名、AES-GCM 加密、HTTP/2 头设置 - 不要手写加密逻辑——
libsodium的sodium_crypto_aead_aes256gcm_encrypt()参数顺序、nonce 长度、padding 方式极易出错 - PHP 7.2+ 必须启用
sodium扩展;若用minishlink/web-push,还要确保cURL支持 HTTP/2(curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0))
常见错误:401 Unauthorized、410 Gone、400 Bad Request
这些不是 PHP 写错了,而是推送链路中某环断了:
-
401 Unauthorized:VAPID 公钥没配到前端subscribe()的applicationServerKey,或私钥签名时用了错误的aud(应为推送服务域名,如https://fcm.googleapis.com) -
410 Gone:前端返回的endpoint已失效(用户清除站点数据、禁用通知、换设备),PHP 必须捕获该响应并从数据库删掉这条记录 -
400 Bad Request:payload 超过 4KB(含加密开销)、ttl设为负数、或contentEncoding头没设成aes128gcm - 别忽略
Content-Encoding: aes128gcm和Encryption、Crypto-Key这三个 header——minishlink/web-push会自动生成,手动发请求时漏一个就 400
简单示例:用 web-push-php 发一条通知
安装:composer require minishlink/web-push
立即学习“PHP免费学习笔记(深入)”;
use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;
$auth = [
'VAPID' => [
'subject' => 'mailto:admin@example.com',
'publicKey' => 'BEm...xxx', // base64url 编码
'privateKey' => 'oFm...xxx',
],
];
$webPush = new WebPush($auth);
// 从数据库查出用户的 subscription 数据
$subscription = Subscription::create([
'endpoint' => 'https://fcm.googleapis.com/fcm/send/xxx',
'publicKey' => 'xxx',
'authToken' => 'xxx',
]);
$webPush->sendOneNotification($subscription, '{"title":"Hi","body":"From PHP"}');
注意:sendOneNotification() 返回的是 Response 对象,必须检查 $response->isSuccess() 和 $response->getReasonPhrase(),否则失败静默,你根本不知道通知没发出去。
VAPID 私钥绝对不能硬编码在 PHP 文件里,也不能进 Git;endpoint 和 keys 存数据库时建议加密(如用 openssl_encrypt + 主密钥)。推送不是发邮件,一次失败就得清理脏数据,不然下次还试。











