PHP项目Laravel广播事件驱动实时更新

wen PHP项目 3

本文目录导读:

PHP项目Laravel广播事件驱动实时更新

  1. 项目设置
  2. 创建广播事件
  3. 创建实时通知事件
  4. 创建模型事件监听
  5. 创建控制器
  6. 前端JavaScript设置
  7. Blade模板
  8. 启动服务
  9. 路由配置
  10. 测试事件
  11. 注意事项

我将为您创建一个完整的Laravel广播事件驱动实时更新系统示例。

项目设置

安装必要的包

# 安装Laravel Reverb(Laravel 11+)
composer require laravel/reverb
# 或使用Pusher
composer require pusher/pusher-php-server
# 前端依赖
npm install --save laravel-echo pusher-js

配置.env文件

# Reverb配置
BROADCAST_CONNECTION=reverb
REVERB_APP_ID=local
REVERB_APP_KEY=local
REVERB_APP_SECRET=secret
REVERB_HOST="127.0.0.1"
REVERB_PORT=8080
REVERB_SCHEME=http
# 或 Pusher配置
# BROADCAST_CONNECTION=pusher
# PUSHER_APP_ID=your-app-id
# PUSHER_APP_KEY=your-key
# PUSHER_APP_SECRET=your-secret
# PUSHER_APP_HOST=127.0.0.1
# PUSHER_APP_PORT=443
# PUSHER_APP_SCHEME=https

创建广播事件

创建订单事件

<?php
// app/Events/OrderCreated.php
namespace App\Events;
use App\Models\Order;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderCreated implements ShouldBroadcastNow
{
    use Dispatchable, InteractsWithSockets, SerializesModels;
    public $order;
    /**
     * Create a new event instance.
     */
    public function __construct(Order $order)
    {
        $this->order = $order;
    }
    /**
     * Get the channels the event should broadcast on.
     *
     * @return array<int, \Illuminate\Broadcasting\Channel>
     */
    public function broadcastOn(): array
    {
        return [
            new Channel('orders'),
        ];
    }
    /**
     * 自定义广播数据
     */
    public function broadcastWith(): array
    {
        return [
            'order_id' => $this->order->id,
            'customer_name' => $this->order->customer_name,
            'total_amount' => $this->order->total_amount,
            'status' => $this->order->status,
            'created_at' => $this->order->created_at->toISOString(),
        ];
    }
    /**
     * 自定义广播事件名称
     */
    public function broadcastAs(): string
    {
        return 'order.created';
    }
}

创建订单更新事件

<?php
// app/Events/OrderStatusUpdated.php
namespace App\Events;
use App\Models\Order;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderStatusUpdated implements ShouldBroadcastNow
{
    use Dispatchable, InteractsWithSockets, SerializesModels;
    public $order;
    public $oldStatus;
    public $newStatus;
    public function __construct(Order $order, $oldStatus, $newStatus)
    {
        $this->order = $order;
        $this->oldStatus = $oldStatus;
        $this->newStatus = $newStatus;
    }
    public function broadcastOn(): array
    {
        return [
            new Channel('orders'),
            new PrivateChannel('user.' . $this->order->user_id),
        ];
    }
    public function broadcastWith(): array
    {
        return [
            'order_id' => $this->order->id,
            'old_status' => $this->oldStatus,
            'new_status' => $this->newStatus,
            'updated_at' => now()->toISOString(),
        ];
    }
    public function broadcastAs(): string
    {
        return 'order.status.updated';
    }
}

创建实时通知事件

<?php
// app/Events/NewNotification.php
namespace App\Events;
use App\Models\Notification;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class NewNotification implements ShouldBroadcastNow
{
    use Dispatchable, InteractsWithSockets, SerializesModels;
    public $notification;
    public function __construct(Notification $notification)
    {
        $this->notification = $notification;
    }
    public function broadcastOn(): array
    {
        return [
            new Channel('notifications.' . $this->notification->user_id),
        ];
    }
    public function broadcastWith(): array
    {
        return [
            'id' => $this->notification->id,
            'type' => $this->notification->type,
            'message' => $this->notification->message,
            'data' => $this->notification->data,
            'read' => $this->notification->read_at !== null,
            'created_at' => $this->notification->created_at->toISOString(),
        ];
    }
    public function broadcastAs(): string
    {
        return 'notification.created';
    }
}

创建模型事件监听

Order模型

<?php
// app/Models/Order.php
namespace App\Models;
use App\Events\OrderCreated;
use App\Events\OrderStatusUpdated;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
    use HasFactory;
    protected $fillable = [
        'user_id',
        'customer_name',
        'customer_email',
        'total_amount',
        'status',
        'items'
    ];
    protected $casts = [
        'items' => 'array',
        'total_amount' => 'decimal:2',
    ];
    protected $dispatchesEvents = [
        'created' => OrderCreated::class,
    ];
    // 状态更新自动触发事件
    public function updateStatus(string $newStatus)
    {
        $oldStatus = $this->status;
        $this->update(['status' => $newStatus]);
        event(new OrderStatusUpdated($this, $oldStatus, $newStatus));
        return $this;
    }
    // 关联用户
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

创建控制器

<?php
// app/Http/Controllers/OrderController.php
namespace App\Http\Controllers;
use App\Events\OrderCreated;
use App\Events\OrderStatusUpdated;
use App\Models\Order;
use App\Models\Notification;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class OrderController extends Controller
{
    /**
     * 创建订单并触发事件
     */
    public function store(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'customer_name' => 'required|string|max:255',
            'customer_email' => 'required|email',
            'total_amount' => 'required|numeric|min:0',
            'items' => 'required|array',
        ]);
        if ($validator->fails()) {
            return response()->json(['errors' => $validator->errors()], 422);
        }
        $order = Order::create([
            'user_id' => auth()->id(),
            ...$request->all()
        ]);
        // 手动触发事件
        event(new OrderCreated($order));
        // 创建通知
        $notification = Notification::create([
            'user_id' => auth()->id(),
            'type' => 'order_confirmation',
            'message' => "Order #{$order->id} confirmed successfully",
            'data' => [
                'order_id' => $order->id,
                'total' => $order->total_amount
            ]
        ]);
        // 触发通知事件
        event(new NewNotification($notification));
        return response()->json([
            'message' => 'Order created successfully',
            'order' => $order
        ], 201);
    }
    /**
     * 更新订单状态
     */
    public function updateStatus(Request $request, $id)
    {
        $validator = Validator::make($request->all(), [
            'status' => 'required|in:pending,processing,shipped,delivered,cancelled'
        ]);
        if ($validator->fails()) {
            return response()->json(['errors' => $validator->errors()], 422);
        }
        $order = Order::findOrFail($id);
        $order->updateStatus($request->status);
        // 创建状态更新通知
        $notification = Notification::create([
            'user_id' => $order->user_id,
            'type' => 'order_status',
            'message' => "Order #{$order->id} status changed to {$request->status}",
            'data' => [
                'order_id' => $order->id,
                'status' => $request->status
            ]
        ]);
        event(new NewNotification($notification));
        return response()->json([
            'message' => 'Order status updated successfully',
            'order' => $order
        ]);
    }
    /**
     * 标记通知为已读
     */
    public function markNotificationRead($notificationId)
    {
        $notification = Notification::findOrFail($notificationId);
        $notification->update(['read_at' => now()]);
        return response()->json(['message' => 'Notification marked as read']);
    }
}

前端JavaScript设置

创建resources/js/bootstrap.js配置

// resources/js/bootstrap.js
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
// Reverb配置
window.Echo = new Echo({
    broadcaster: 'reverb',
    key: import.meta.env.VITE_REVERB_APP_KEY,
    wsHost: import.meta.env.VITE_REVERB_HOST,
    wsPort: import.meta.env.VITE_REVERB_PORT ?? 8080,
    wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
    enabledTransports: ['ws', 'wss'],
});
// Pusher配置(如果使用Pusher)
// window.Echo = new Echo({
//     broadcaster: 'pusher',
//     key: import.meta.env.VITE_PUSHER_APP_KEY,
//     cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
//     forceTLS: true
// });

创建实时监听组件

// resources/js/live/realtime-handlers.js
export class RealtimeHandler {
    constructor() {
        this.orderChannel = null;
        this.notificationChannels = [];
        this.init();
    }
    init() {
        // 监听所有订单事件
        this.setupOrderListeners();
        // 监听用户私有通知(需要认证)
        this.setupNotificationListeners();
    }
    setupOrderListeners() {
        this.orderChannel = window.Echo.channel('orders');
        // 监听新订单
        this.orderChannel.listen('.order.created', (data) => {
            this.handleNewOrder(data);
        });
        // 监听订单状态更新
        this.orderChannel.listen('.order.status.updated', (data) => {
            this.handleOrderStatusUpdate(data);
        });
    }
    setupNotificationListeners() {
        // 获取当前用户ID
        const userId = document.querySelector('meta[name="user-id"]')?.content;
        if (userId) {
            this.setupPrivateChannel(userId);
        }
    }
    setupPrivateChannel(userId) {
        console.log(`Setting up private channel for user: ${userId}`);
        // 监听用户私有通知通道
        const channel = window.Echo.channel(`user.${userId}`);
        // 监听订单状态更新
        channel.listen('.order.status.updated', (data) => {
            this.handlePrivateOrderUpdate(data);
        });
        // 监听新通知
        const notificationChannel = window.Echo.channel(`notifications.${userId}`);
        notificationChannel.listen('.notification.created', (data) => {
            this.handleNewNotification(data);
        });
        this.notificationChannels.push(channel, notificationChannel);
    }
    handleNewOrder(data) {
        console.log('New order received:', data);
        // 创建通知DOM元素
        const notification = this.createNotificationElement(
            'New Order',
            `Order #${data.order_id} from ${data.customer_name} for $${data.total_amount}`,
            'success'
        );
        this.appendNotification(notification);
        // 播放声音提示
        this.playNotificationSound();
        // 更新订单计数
        this.updateOrderCounter();
        // 触发自定义事件
        window.dispatchEvent(new CustomEvent('order:created', { detail: data }));
    }
    handleOrderStatusUpdate(data) {
        console.log('Order status updated:', data);
        // 更新UI
        this.updateOrderStatusUI(data.order_id, data.new_status);
        // 显示通知
        const notification = this.createNotificationElement(
            'Order Updated',
            `Order #${data.order_id} has been ${data.new_status}`,
            'info'
        );
        this.appendNotification(notification);
        // 触发自定义事件
        window.dispatchEvent(new CustomEvent('order:status-updated', { detail: data }));
    }
    handleNewNotification(data) {
        console.log('New notification:', data);
        const notification = this.createNotificationElement(
            'Notification',
            data.message,
            'info'
        );
        this.appendNotification(notification);
        this.updateNotificationBadge();
        // 触发自定义事件
        window.dispatchEvent(new CustomEvent('notification:received', { detail: data }));
    }
    createNotificationElement(title, message, type) {
        const container = document.createElement('div');
        container.className = `notification-item alert alert-${type} fade-in`;
        container.innerHTML = `
            <div class="notification-content">
                <h4>${title}</h4>
                <p>${message}</p>
            </div>
            <button class="close-btn" onclick="this.parentElement.remove()">×</button>
        `;
        return container;
    }
    appendNotification(notification) {
        const notificationContainer = document.querySelector('#notification-container');
        if (notificationContainer) {
            notificationContainer.appendChild(notification);
            // 自动移除通知
            setTimeout(() => {
                notification.classList.add('fade-out');
                setTimeout(() => notification.remove(), 300);
            }, 5000);
        }
    }
    updateOrderStatusUI(orderId, status) {
        const orderElement = document.querySelector(`[data-order-id="${orderId}"]`);
        if (orderElement) {
            const statusElement = orderElement.querySelector('.order-status');
            if (statusElement) {
                statusElement.textContent = status;
                statusElement.className = `order-status badge-${status}`;
            }
        }
    }
    playNotificationSound() {
        const audio = new Audio('/sounds/notification.mp3');
        audio.play().catch(() => console.log('Sound playback blocked'));
    }
    updateOrderCounter() {
        const counter = document.querySelector('#order-counter');
        if (counter) {
            const current = parseInt(counter.textContent) || 0;
            counter.textContent = current + 1;
        }
    }
    updateNotificationBadge() {
        const badge = document.querySelector('#notification-badge');
        if (badge) {
            const current = parseInt(badge.dataset.count) || 0;
            badge.dataset.count = current + 1;
            badge.textContent = current + 1;
        }
    }
    destroy() {
        // 清理监听器
        this.orderChannel?.stopListening('.order.created');
        this.orderChannel?.stopListening('.order.status.updated');
        this.notificationChannels.forEach(channel => {
            channel.stopListening('.order.status.updated');
            channel.stopListening('.notification.created');
        });
    }
}
// 初始化实时处理程序
export const realtimeHandler = new RealtimeHandler();

在应用入口引入

// resources/js/app.js
import './bootstrap';
import './live/realtime-handlers';

Blade模板

{{-- resources/views/orders/index.blade.php --}}
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="user-id" content="{{ auth()->id() }}">Live Orders</title>
    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
    <div class="container">
        <h1>Live Orders Dashboard</h1>
        <!-- 通知区域 -->
        <div id="notification-container"></div>
        <!-- 统计信息 -->
        <div class="stats-grid">
            <div class="stat-card">
                <h3>New Orders</h3>
                <span id="order-counter">0</span>
            </div>
            <div class="stat-card">
                <h3>Notifications</h3>
                <span id="notification-badge" data-count="0">0</span>
            </div>
        </div>
        <!-- 订单列表 -->
        <div class="orders-list">
            @foreach($orders as $order)
            <div class="order-card" data-order-id="{{ $order->id }}">
                <div class="order-header">
                    <h3>Order #{{ $order->id }}</h3>
                    <span class="order-status badge-{{ $order->status }}">{{ $order->status }}</span>
                </div>
                <div class="order-body">
                    <p>Customer: <strong>{{ $order->customer_name }}</strong></p>
                    <p>Total: <strong>${{ number_format($order->total_amount, 2) }}</strong></p>
                    <p>Created: <strong>{{ $order->created_at->format('Y-m-d H:i:s') }}</strong></p>
                </div>
                <div class="order-actions">
                    <select class="status-select" onchange="updateStatus({{ $order->id }}, this.value)">
                        <option value="">Select Status</option>
                        @foreach(['pending', 'processing', 'shipped', 'delivered', 'cancelled'] as $status)
                        <option value="{{ $status }}" {{ $order->status === $status ? 'selected' : '' }}>
                            {{ ucfirst($status) }}
                        </option>
                        @endforeach
                    </select>
                </div>
            </div>
            @endforeach
        </div>
    </div>
    <script>
        // 额外的自定义函数
        function updateStatus(orderId, status) {
            fetch(`/api/orders/${orderId}/status`, {
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json',
                    'X-CSRF-TOKEN': '{{ csrf_token() }}'
                },
                body: JSON.stringify({ status: status })
            })
            .then(response => response.json())
            .then(data => {
                console.log('Order updated:', data);
            })
            .catch(error => console.error('Error:', error));
        }
        // 监听自定义事件
        window.addEventListener('order:created', (e) => {
            console.log('Custom event - Order created:', e.detail);
        });
        window.addEventListener('order:status-updated', (e) => {
            console.log('Custom event - Order status updated:', e.detail);
        });
        window.addEventListener('notification:received', (e) => {
            console.log('Custom event - Notification received:', e.detail);
        });
    </script>
</body>
</html>

启动服务

# 启动Reverb服务
php artisan reverb:start
# 启动队列(如果使用队列)
php artisan queue:work
# 开发环境启动
npm run dev
# 生产环境构建
npm run build

路由配置

<?php
// routes/api.php
use App\Http\Controllers\OrderController;
use App\Http\Controllers\NotificationController;
Route::middleware('auth:sanctum')->group(function () {
    Route::post('/orders', [OrderController::class, 'store']);
    Route::put('/orders/{id}/status', [OrderController::class, 'updateStatus']);
    Route::post('/notifications/{id}/read', [OrderController::class, 'markNotificationRead']);
});

测试事件

# 使用Artisan测试事件
php artisan tinker
# 创建测试订单
$order = App\Models\Order::create([
    'user_id' => 1,
    'customer_name' => 'John Doe',
    'customer_email' => 'john@example.com',
    'total_amount' => 100.00,
    'status' => 'pending',
    'items' => json_encode([['product' => 'Test', 'quantity' => 1, 'price' => 100]])
]);
# 这将自动触发OrderCreated事件

注意事项

  1. 性能优化:大量并发广播时,建议使用Redis队列
  2. 认证安全:私有频道需要实现授权逻辑
  3. 重连机制:考虑实现WebSocket断线重连
  4. 错误处理:实现适当的错误处理和日志记录
  5. 前端优化:合理使用事件节流和防抖

这个系统实现了实时订单更新、状态变更通知、用户通知等功能,可基于此扩展更多实时功能。

抱歉,评论功能暂时关闭!