Laravel 13 广播系统详解
广播系统允许在服务器端和客户端之间实现实时通信,是构建实时应用的核心组件。本文将深入探讨 Laravel 13 的广播系统。
配置
广播配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| return [ 'default' => env('BROADCAST_CONNECTION', 'null'),
'connections' => [ 'pusher' => [ 'driver' => 'pusher', 'key' => env('PUSHER_APP_KEY'), 'secret' => env('PUSHER_APP_SECRET'), 'app_id' => env('PUSHER_APP_ID'), 'options' => [ 'cluster' => env('PUSHER_APP_CLUSTER'), 'encrypted' => true, 'host' => env('PUSHER_HOST'), 'port' => env('PUSHER_PORT', 443), 'scheme' => env('PUSHER_SCHEME', 'https'), ], ],
'ably' => [ 'driver' => 'ably', 'key' => env('ABLY_KEY'), ],
'log' => [ 'driver' => 'log', ],
'null' => [ 'driver' => 'null', ], ], ];
|
启用广播
1 2 3 4
| 'providers' => [ App\Providers\BroadcastServiceProvider::class, ],
|
定义广播事件
创建广播事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| <?php
namespace App\Events;
use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\PresenceChannel; use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; use App\Models\Order;
class OrderShipped implements ShouldBroadcast { use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct( public Order $order ) {}
public function broadcastOn(): array { return [ new PrivateChannel('user.'.$this->order->user_id), ]; }
public function broadcastAs(): string { return 'order.shipped'; }
public function broadcastWith(): array { return [ 'id' => $this->order->id, 'tracking_number' => $this->order->tracking_number, 'status' => $this->order->status, ]; }
public function broadcastQueue(): string { return 'broadcasts'; } }
|
频道类型
公共频道
1 2 3 4 5 6
| public function broadcastOn(): array { return [ new Channel('orders'), ]; }
|
私有频道
1 2 3 4 5 6
| public function broadcastOn(): array { return [ new PrivateChannel('user.'.$this->user->id), ]; }
|
存在频道
1 2 3 4 5 6
| public function broadcastOn(): array { return [ new PresenceChannel('chat.'.$this->chat->id), ]; }
|
授权频道
定义频道路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| use Illuminate\Support\Facades\Broadcast;
Broadcast::channel('user.{id}', function ($user, $id) { return (int) $user->id === (int) $id; });
Broadcast::channel('order.{id}', function ($user, $id) { return $user->orders()->where('id', $id)->exists(); });
Broadcast::channel('chat.{chatId}', function ($user, $chatId) { if ($user->chats()->where('id', $chatId)->exists()) { return [ 'id' => $user->id, 'name' => $user->name, ]; } });
|
频道类
1
| php artisan make:channel OrderChannel
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?php
namespace App\Broadcasting;
use App\Models\Order; use App\Models\User;
class OrderChannel { public function join(User $user, int $orderId): bool { return $user->orders()->where('id', $orderId)->exists(); } }
Broadcast::channel('order.{id}', OrderChannel::class);
|
客户端订阅
Laravel Echo
1 2 3 4 5 6 7 8 9 10 11
| import Echo from 'laravel-echo'; import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({ broadcaster: 'pusher', key: import.meta.env.VITE_PUSHER_APP_KEY, cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER, encrypted: true, });
|
监听公共频道
1 2 3 4
| Echo.channel('orders') .listen('OrderShipped', (e) => { console.log(e.order); });
|
监听私有频道
1 2 3 4 5 6 7
| Echo.private(`user.${userId}`) .listen('OrderShipped', (e) => { console.log(e.order); }) .listen('OrderDelivered', (e) => { console.log(e.order); });
|
监听存在频道
1 2 3 4 5 6 7 8 9 10 11 12 13
| Echo.join(`chat.${chatId}`) .here((users) => { console.log('Users in chat:', users); }) .joining((user) => { console.log('User joined:', user); }) .leaving((user) => { console.log('User left:', user); }) .listen('MessageSent', (e) => { console.log('New message:', e.message); });
|
广播通知
广播通知
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php
namespace App\Notifications;
use Illuminate\Bus\Queueable; use Illuminate\Notifications\Notification; use Illuminate\Notifications\Messages\BroadcastMessage;
class OrderShipped extends Notification { use Queueable;
public function toBroadcast($notifiable): BroadcastMessage { return new BroadcastMessage([ 'order_id' => $this->order->id, 'message' => 'Your order has been shipped!', ]); }
public function broadcastOn() { return new PrivateChannel('user.'.$this->notifiable->id); } }
|
监听通知
1 2 3 4
| Echo.private(`user.${userId}`) .notification((notification) => { console.log(notification.message); });
|
广播队列
队列广播
1 2 3 4 5 6 7 8 9
| class OrderShipped implements ShouldBroadcast { use InteractsWithQueue;
public function broadcastQueue(): string { return 'broadcasts'; } }
|
广播事件
分发广播事件
1 2 3 4 5
| use App\Events\OrderShipped;
OrderShipped::dispatch($order);
event(new OrderShipped($order));
|
条件广播
1 2 3 4 5 6 7
| class OrderShipped implements ShouldBroadcast { public function broadcastWhen(): bool { return $this->order->user->wantsNotifications(); } }
|
存在频道高级用法
用户加入/离开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Echo.join(`chat.${chatId}`) .here((users) => { this.users = users; }) .joining((user) => { this.users.push(user); this.notify(`${user.name} joined the chat`); }) .leaving((user) => { this.users = this.users.filter(u => u.id !== user.id); this.notify(`${user.name} left the chat`); }) .error((error) => { console.error(error); });
|
踢出用户
1 2 3 4 5 6 7 8 9 10
| Broadcast::channel('chat.{chatId}', function ($user, $chatId) { if ($user->isBannedFrom($chatId)) { return false; }
return [ 'id' => $user->id, 'name' => $user->name, ]; });
|
测试广播
广播伪造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| use Illuminate\Support\Facades\Event; use App\Events\OrderShipped;
public function test_order_shipped_broadcast(): void { Event::fake();
$order = Order::factory()->create();
OrderShipped::dispatch($order);
Event::assertDispatched(OrderShipped::class); Event::assertDispatched(function (OrderShipped $event) use ($order) { return $event->order->id === $order->id; }); }
|
频道断言
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| use Illuminate\Support\Facades\Broadcast;
public function test_channel_authorization(): void { $user = User::factory()->create();
$response = $this->actingAs($user) ->postJson('/broadcasting/auth', [ 'socket_id' => 'test', 'channel_name' => "private-user.{$user->id}", ]);
$response->assertOk(); }
|
最佳实践
1. 使用队列广播
1 2 3 4 5 6 7 8 9 10
| class OrderShipped implements ShouldBroadcast { use InteractsWithQueue; }
class OrderShipped implements ShouldBroadcastNow { }
|
2. 最小化广播数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public function broadcastWith(): array { return [ 'id' => $this->order->id, 'status' => $this->order->status, ]; }
public function broadcastWith(): array { return $this->order->toArray(); }
|
3. 使用频道类
1 2 3 4 5 6 7
| Broadcast::channel('order.{id}', OrderChannel::class);
Broadcast::channel('order.{id}', function ($user, $id) { return $user->orders()->where('id', $id)->exists(); });
|
总结
Laravel 13 的广播系统提供了强大的实时通信能力。通过合理使用公共频道、私有频道和存在频道,可以构建出各种实时应用场景。记住使用队列处理广播事件、最小化广播数据量、并使用频道类来组织授权逻辑。广播是构建现代 Web 应用的重要组件,掌握它对于开发现代实时应用至关重要。