Laravel 中实现 Web Push(网页推送通知)

在 Laravel 中实现 Web Push(网页推送通知)可以通过结合 Laravel 的通知系统和专门的 Web Push 扩展包来完成。以下是详细的实现步骤:

一、准备工作

Web Push 依赖以下技术:

  • Service Worker:运行在浏览器后台的脚本,负责接收推送消息
  • VAPID:用于服务器与浏览器之间的身份验证
  • Push API:浏览器提供的接收推送的接口
  • Notification API:用于显示系统通知

我们将使用 laravel-notification-channels/webpush 扩展包,它简化了 Laravel 中 Web Push 的实现。

二、安装与配置

1. 安装扩展包

bash

composer require laravel-notification-channels/webpush

2. 发布配置文件和迁移文件

bash

php artisan vendor:publish --provider="NotificationChannels\WebPush\WebPushServiceProvider"

这会生成:

  • 配置文件:config/webpush.php
  • 迁移文件:database/migrations/xxxx_xx_xx_xxxxxx_create_web_push_subscriptions_table.php

3. 生成 VAPID 密钥

VAPID(Voluntary Application Server Identification)用于验证服务器身份,运行以下命令生成:

bash

php artisan webpush:vapid

生成的密钥会自动写入 .env 文件:

env

WEBPUSH_VAPID_PUBLIC_KEY=your-public-key
WEBPUSH_VAPID_PRIVATE_KEY=your-private-key
WEBPUSH_VAPID_SUBJECT=mailto:your-email@example.com  # 通常是管理员邮箱

4. 运行迁移

创建存储用户订阅信息的数据表:

bash

php artisan migrate

三、创建订阅逻辑

1. 定义用户模型关联

User 模型中添加与订阅的关联:

php

// app/Models/User.php
use NotificationChannels\WebPush\Models\WebPushSubscription;

public function webPushSubscriptions()
{
    return $this->hasMany(WebPushSubscription::class);
}

2. 创建订阅控制器

新建控制器处理用户订阅请求:

bash

php artisan make:controller WebPushController

实现订阅和取消订阅的逻辑:

php

// app/Http/Controllers/WebPushController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use NotificationChannels\WebPush\Models\WebPushSubscription;

class WebPushController extends Controller
{
    // 保存用户订阅
    public function store(Request $request)
    {
        $request->validate([
            'endpoint' => 'required|url',
            'keys.p256dh' => 'required',
            'keys.auth' => 'required',
        ]);

        $subscription = WebPushSubscription::updateOrCreate(
            [
                'endpoint' => $request->endpoint,
            ],
            [
                'user_id' => auth()->id(), // 关联当前登录用户
                'public_key' => $request->keys['p256dh'],
                'auth_token' => $request->keys['auth'],
                'content_encoding' => 'aesgcm',
            ]
        );

        return response()->json(['success' => true]);
    }

    // 取消订阅
    public function destroy($endpoint)
    {
        WebPushSubscription::where('endpoint', $endpoint)->delete();
        return response()->json(['success' => true]);
    }
}

3. 注册路由

routes/web.php 中添加路由:

php

use App\Http\Controllers\WebPushController;

Route::post('/web-push/subscribe', [WebPushController::class, 'store'])->middleware('auth');
Route::delete('/web-push/unsubscribe/{endpoint}', [WebPushController::class, 'destroy'])->middleware('auth');

四、创建推送通知类

1. 生成通知类

bash

php artisan make:notification NewsNotification

2. 实现 Web Push 通知

php

// app/Notifications/NewsNotification.php
namespace App\Notifications;

use Illuminate\Notifications\Notification;
use NotificationChannels\WebPush\WebPushChannel;
use NotificationChannels\WebPush\WebPushMessage;

class NewsNotification extends Notification
{
    private $news;

    public function __construct($news)
    {
        $this->news = $news; // 传递需要推送的内容
    }

    // 指定通知渠道
    public function via($notifiable)
    {
        return [WebPushChannel::class];
    }

    // 构建 Web Push 消息
    public function toWebPush($notifiable, $notification)
    {
        return (new WebPushMessage)
            ->title('新消息通知')
            ->body($this->news['content'])
            ->icon('/icon.png') // 通知图标
            ->action('查看详情', 'view_details') // 通知按钮
            ->data([
                'url' => route('news.show', $this->news['id']), // 点击通知跳转的链接
            ])
            ->options(['TTL' => 60 * 15]); // 消息存活时间(15分钟)
    }
}

五、前端实现(关键)

1. 注册 Service Worker

public 目录下创建 service-worker.js

javascript

// public/service-worker.js
self.addEventListener('push', function(event) {
    const data = event.data?.json() ?? { title: '新通知' };
    
    const options = {
        body: data.body,
        icon: data.icon || '/icon.png',
        data: { url: data.url }, // 点击通知跳转的URL
        actions: data.actions || [],
    };

    event.waitUntil(
        self.registration.showNotification(data.title, options)
    );
});

// 点击通知事件
self.addEventListener('notificationclick', function(event) {
    event.notification.close();
    // 跳转到指定页面
    event.waitUntil(
        clients.openWindow(event.notification.data.url || '/')
    );
});

2. 前端订阅逻辑

在页面中添加订阅代码(可放在 Blade 模板或 Vue/React 组件中):

html

预览

<!-- 引入 Web Push 库 -->
<script src="https://cdn.jsdelivr.net/npm/web-push@3.6.7/build/web-push.min.js"></script>

<script>
// 初始化订阅
async function initializeWebPush() {
    // 检查浏览器支持
    if (!('serviceWorker' in navigator && 'PushManager' in window)) {
        console.log('浏览器不支持 Web Push');
        return;
    }

    try {
        // 注册 Service Worker
        const registration = await navigator.serviceWorker.register('/service-worker.js');
        
        // 获取 VAPID 公钥(从后端获取)
        const publicKey = '{{ config('webpush.vapid.public_key') }}';
        
        // 订阅推送
        const subscription = await registration.pushManager.subscribe({
            userVisibleOnly: true, // 所有推送都必须显示给用户
            applicationServerKey: urlBase64ToUint8Array(publicKey)
        });

        // 将订阅信息发送到后端保存
        await fetch('/web-push/subscribe', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRF-TOKEN': '{{ csrf_token() }}'
            },
            body: JSON.stringify(subscription)
        });

        console.log('订阅成功');
    } catch (error) {
        console.error('订阅失败:', error);
    }
}

// 辅助函数:将 VAPID 公钥转换为 Uint8Array
function urlBase64ToUint8Array(base64String) {
    const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
    const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
    const rawData = atob(base64);
    return Uint8Array.from([...rawData].map(char => char.charCodeAt(0)));
}

// 页面加载时初始化
window.addEventListener('load', initializeWebPush);
</script>

六、发送推送通知

在需要发送通知的地方(如控制器)调用通知:

php

// 示例:当有新消息时发送推送
public function sendNewsNotification()
{
    $user = auth()->user(); // 目标用户
    $news = [
        'id' => 1,
        'content' => '这是一条新消息通知!'
    ];
    
    $user->notify(new \App\Notifications\NewsNotification($news));
    
    return back()->with('message', '通知已发送');
}

七、测试与注意事项

  1. HTTPS 环境:Web Push 仅在 HTTPS 环境下工作(本地开发可使用 localhost)。
  2. 浏览器支持:主流浏览器(Chrome、Firefox、Edge)均支持,Safari 需 v16+。
  3. 用户授权:首次使用需用户允许通知权限。
  4. 调试工具:使用浏览器的「应用程序」面板查看 Service Worker 和推送状态。
  5. 通知图标:建议准备 192x192px 的图标,放在 public 目录下。

通过以上步骤,你可以在 Laravel 中实现完整的 Web Push 功能,向用户发送实时通知,提升用户活跃度和互动性。

posted @ 2025-08-04 10:48  pine007  阅读(37)  评论(0)    收藏  举报