Laravel 13 事件广播:从私有频道授权到前端实时更新

作者:Laravel 文档贡献者。本文依据 Laravel 13.x 官方文档 Broadcasting完整正文翻译整理,核对日期为 2026-10-05。原文的 React、Vue、Svelte 同功能示例合并说明,保留各项接口与限制;编辑补充和代码调整均在相关位置标明。

适用范围是 Laravel 13、PHP 8.3 及以上,以及与所选广播驱动相匹配的 Echo 版本。阅读前应了解 Laravel 事件、认证与队列。文中所有命令和代码都只做静态核对,未安装依赖、未启动服务、未连接真实广播平台。

订单状态广播流程:事务提交后进入队列,经广播服务送往已通过频道归属授权的浏览器;请求携带的 X-Socket-ID 用于排除发起连接。
原创技术示意图,依据 Laravel 官方文档绘制;表示逻辑流程,不是运行截图或实测时序。

广播解决什么问题

很多应用需要在服务器数据变化后立即更新界面。WebSocket 让服务器通过持续连接推送消息,客户端不必反复轮询。例如,导出 CSV 并发送邮件可能需要数分钟:应用把工作交给队列,在任务完成后广播 App\Events\UserDataExported,浏览器收到事件后显示“导出文件已发送”,用户无需刷新页面。

Laravel 的广播机制让服务端事件和前端 JavaScript 共用事件名称与数据。前端订阅有名称的频道,后端向频道发送事件。事件中的附加数据就是客户端可读取的载荷。当前文档提供 Reverb、Pusher Channels、Ably 和 Mercure 四种服务端驱动;Mercure 使用服务器发送事件(SSE),因此不能把所有驱动都理解为相同的 WebSocket 传输。

频道有不同的访问边界:Channel 是公开频道,任何访问者均可订阅,无需认证或授权;PrivateChannel 要求认证和频道级授权;PresenceChannel 在私有频道基础上增加在线成员信息。订单、用户资料等业务数据应先确定订阅权限,再设计广播内容。

启用广播并选择服务端驱动

新建的 Laravel 应用默认不启用广播。安装命令会询问采用哪种广播服务,生成 config/broadcasting.php 与 routes/channels.php,后者用于注册频道授权规则。

php artisan install:broadcasting

广播配置集中在 config/broadcasting.php。除四种服务驱动外,还有用于本地调试的 log 驱动和用于关闭广播的 null 驱动。普通 ShouldBroadcast 事件通过队列发送,因此必须配置并运行 queue worker;后文的同步接口是显式例外。

命令边界:安装命令会下载 Composer/NPM 依赖、修改应用配置和 .env。下面是供人工采用的命令,不是已执行记录。请在版本控制下检查变更,使用项目锁文件约束依赖,并通过安全配置机制注入真实凭据。Pusher、Ably 等托管平台需要相应账户,可能产生费用;Reverb 可自行托管。

Reverb

自动安装会安装所需 Composer 和 NPM 包,并补充环境变量。

php artisan install:broadcasting --reverb

手动路径则先安装包,再发布配置与环境变量:

composer require laravel/reverb
php artisan reverb:install

服务运行、代理与部署细节请继续参照 Reverb 文档;安装成功本身不代表队列或广播服务器已运行。

Pusher Channels

自动安装命令会询问 Pusher 凭据,安装 PHP 与 JavaScript SDK,并更新 .env。手动方式使用相应 PHP SDK。

# 自动方式
php artisan install:broadcasting --pusher

# 手动方式所需 PHP 包
composer require pusher/pusher-php-server

配置文件已有 Pusher 连接示例。下面的值保留原文的占位性质,必须按实际应用填写:

BROADCAST_CONNECTION=pusher
PUSHER_APP_ID="your-pusher-app-id"
PUSHER_APP_KEY="your-pusher-key"
PUSHER_APP_SECRET="your-pusher-secret"
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME="https"
PUSHER_APP_CLUSTER="mt1"

连接的 options 可配置集群等 Channels 支持的选项。若需端到端加密私有频道,在 Pusher 的 options 中加入:

'encryption_master_key_base64' => env('PUSHER_ENCRYPTION_MASTER_KEY'),

它应保存一个 32 字节密钥的 Base64 表示,原文给出的生成命令是 openssl rand -base64 32。这是秘密材料,不能写进前端 VITE_ 变量或提交到代码库。本次未生成实际密钥。

Ably

原文此处采用 Ably 的 Pusher 兼容模式,必须先在 Ably 应用的 Protocol Adapter Settings 中启用 Pusher 协议。Ably 团队也维护专用 broadcaster 与 Echo 客户端,以使用平台特有功能;采用原生方案时参照 Ably Laravel broadcaster,不要混用两套配置。

# 自动方式
php artisan install:broadcasting --ably

# 手动方式所需 PHP 包
composer require ably/ably-php
BROADCAST_CONNECTION=ably
ABLY_KEY=your-ably-key

真实的 ABLY_KEY 留在服务端。配置中的连接与凭据通过 config/broadcasting.php 读取。

Mercure

本次核对的文档提供自动安装选项,以及手动安装 Symfony Mercure 组件与 JWT 库的方式:

# 自动方式
php artisan install:broadcasting --mercure

# 手动方式
composer require symfony/mercure:^0.8 web-token/jwt-library:^4.1
BROADCAST_CONNECTION=mercure
MERCURE_URL=https://mercure.example.com/.well-known/mercure
MERCURE_PUBLIC_URL=https://mercure.example.com/.well-known/mercure
MERCURE_JWT_SECRET=<your-mercure-jwt-secret>

MERCURE_URL 是 Laravel 发布更新时访问的地址;MERCURE_PUBLIC_URL 是浏览器订阅使用的地址,两者可因网络拓扑而不同。Hub 与应用必须配置一致的 JWT secret。端到端加密私有频道还需要 32 字节的 MERCURE_ENCRYPTION_KEY。尖括号内为待替换说明,不是真实有效凭据。

在浏览器中配置 Laravel Echo

Laravel Echo 负责订阅频道和监听广播。安装向导通常会写入配套前端配置;手动安装时,可以在 resources/js/app.js 初始化。Reverb 和 Pusher 兼容客户端都需要:

npm install --save-dev laravel-echo pusher-js

Reverb 使用 Pusher 协议处理连接、频道与消息;原文要求 laravel-echo 至少为 1.16.0,但这一最低版本说明不能证明后文所有新版 API 在 1.16.0 中都存在。

import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;

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 ?? 80,
    wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
    forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
});

静态审查补充:该原文示例允许通过环境配置使用明文 ws;有真实业务数据时应配置 TLS/WSS,并按部署要求限制允许的来源。示例中的 app key 与 secret 不是同一概念,所有 VITE_ 值均会进入可公开读取的前端构建产物。

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,
});

在 .env 中提供客户端所需的公开连接参数,不能加入 VITE_PUSHER_APP_SECRET:

VITE_APP_NAME="${APP_NAME}"
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

已有预配置 Pusher 实例时,Echo 可以接受 client,避免重复配置:

const options = {
    broadcaster: 'pusher',
    key: import.meta.env.VITE_PUSHER_APP_KEY,
    cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
    forceTLS: true,
};
window.Echo = new Echo({ ...options, client: new Pusher(options.key, options) });

此处相较原文的简短复用示例,编辑补入了集群与 TLS 参数,使它与前述 Pusher 配置一致;仍须核对实际集群。

Ably 的原生 JavaScript 兼容模式同样使用 Pusher 客户端,但连接到 Ably 提供的协议端点:

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: import.meta.env.VITE_ABLY_PUBLIC_KEY,
    wsHost: 'realtime-pusher.ably.io',
    wsPort: 443,
    disableStats: true,
    encrypted: true,
});

VITE_ABLY_PUBLIC_KEY 只取 Ably key 中冒号 : 之前的公开部分,不能把完整 key 交给浏览器。Mercure 的前端只需 laravel-echo,使用:

window.Echo = new Echo({
    broadcaster: 'mercure',
    host: import.meta.env.VITE_MERCURE_HUB_URL,
});
VITE_MERCURE_HUB_URL="${MERCURE_PUBLIC_URL}"

Mercure 的 host 默认是当前源上的 /.well-known/mercure。React、Vue、Svelte 用户可分别从 @laravel/echo-react、@laravel/echo-vue、@laravel/echo-svelte 导入 configureEcho,再传入 { broadcaster: 'reverb' }、'pusher'、'ably' 或 'mercure';这些封装按所选驱动读取对应配置,也可显式覆盖 key、host、port 等选项。

完成配置后构建前端资源。原文的 Reverb/Pusher 示例使用 npm run build,Ably 示例使用 npm run dev。后者是开发工作流,不能把开发服务器当作生产构建部署。本次没有执行这些脚本;项目内的 npm scripts 也应先检查再运行。

用订单状态构建完整广播链路

设想订单页面显示物流状态,服务端在状态变化时触发 OrderShipmentStatusUpdated。事件需要实现 Illuminate\Contracts\Broadcasting\ShouldBroadcast,并通过 broadcastOn() 返回频道或频道数组。随后照常派发事件,Laravel 就会安排广播任务。

下面把原文分散的订单、载荷和事务示例整合为一个事件类。编辑调整:把订单模型设为受保护属性,使用 broadcastWith() 仅发送订单 ID 与状态;加入 Dispatchable、InteractsWithSockets 和提交后派发接口。假设项目已有 Order 模型且含 id、status、user_id 属性;这些是示例业务结构,需与真实模型对应。

<?php
namespace App\Events;

use App\Models\Order;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderShipmentStatusUpdated implements ShouldBroadcast, ShouldDispatchAfterCommit
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(protected Order $order) {}

    public function broadcastOn(): array
    {
        return [new PrivateChannel('orders.'.$this->order->id)];
    }

    public function broadcastWith(): array
    {
        return [
            'order' => [
                'id' => $this->order->id,
                'status' => $this->order->status,
            ],
        ];
    }
}

原文也用 ServerCreated 示范相同结构:构造器接收公开的 User $user,broadcastOn() 返回 new PrivateChannel('user.'.$this->user->id)。无论频道名称如何选择,私有性都必须通过实际授权规则落实。

在 routes/channels.php 中绑定订单并核对当前用户的归属:

use App\Models\Order;
use App\Models\User;
use Illuminate\Support\Facades\Broadcast;

Broadcast::channel('orders.{order}', function (User $user, Order $order) {
    return $user->id === $order->user_id;
});

回调的第一个参数是认证用户,后面依次对应频道中的通配参数。原文另一个等价场景先接收整数 $orderId,再用 Order::findOrNew($orderId)->user_id 比较;这里选用原文也支持的模型绑定,避免把查不到的订单当作新对象处理。严格相等比较要求两边 ID 类型一致,应在模型类型转换处统一。

派发事件后,浏览器订阅同一名称的私有频道:

use App\Events\OrderShipmentStatusUpdated;

OrderShipmentStatusUpdated::dispatch($order);
Echo.private(`orders.${orderId}`)
    .listen('OrderShipmentStatusUpdated', (event) => {
        // 用项目的状态更新函数更新该订单。
        console.log(event.order.id, event.order.status);
    });

日志只是原文演示接收数据的方式,正式业务应接入界面状态管理,并避免记录敏感载荷。频道授权只控制谁能订阅,写订单的 HTTP 接口仍须独立认证、授权与校验。

事件名称、载荷、队列和提交时机

自定义名称和数据

默认广播名称来自事件类名。定义 broadcastAs(): string 可以改成 server.created 等名称;Echo 监听时要加前导句点,写作 .listen('.server.created', callback),避免自动添加应用事件命名空间。

默认情况下,事件的所有公开属性会被序列化为广播载荷。公开的 Eloquent 模型也会成为可发送数据的一部分。broadcastWith(): array 可以显式返回所需字段,例如 return ['id' => $this->user->id];。这不仅控制大小,也使数据暴露范围可审查。不要因频道是私有的就默认整个用户或订单模型都适合发送。

决定队列与广播条件

默认队列连接与队列名称来自 config/queue.php。本次 Laravel 13 文档使用属性配置连接和队列:

use Illuminate\Queue\Attributes\Connection;
use Illuminate\Queue\Attributes\Queue;

#[Connection('redis')]
#[Queue('default')]
class ServerCreated implements ShouldBroadcast
{
    // 仍需实现 broadcastOn()。
}

也可以通过 broadcastQueue(): string 返回队列名;如果所有广播事件都使用同一队列,可按 队列文档将 ShouldBroadcast 契约路由到该队列。若明确需要同步广播,用 ShouldBroadcastNow 替换 ShouldBroadcast,它使用 sync 队列,广播耗时与失败会更直接地影响当前请求。

broadcastWhen(): bool 决定是否发送某个事件,例如:

public function broadcastWhen(): bool
{
    return $this->order->value > 100;
}

这是业务触发条件,不是频道授权的替代品。

等数据库事务提交

在事务内部派发广播事件时,queue worker 可能在事务提交前取走任务。此时数据库还没有新值,甚至尚不存在刚创建的记录,事件反序列化或查询就可能失败。如果队列连接的 after_commit 为 false,可以让特定事件实现 Illuminate\Contracts\Events\ShouldDispatchAfterCommit,明确等所有打开的数据库事务提交后再派发。前面的整合事件已加上该接口。

编辑补充:“提交后派发”解决读取未提交数据的时序问题,不等同于消息必达、严格顺序或业务幂等保证;本文没有验证跨服务故障恢复。客户端在重连后如何获取最新状态仍属于应用设计的一部分。

把频道授权落实到应用

Echo 在订阅私有频道时自动向 Laravel 发送携带频道名称的 HTTP 授权请求。安装广播时 Laravel 会尝试注册 /broadcasting/auth;如果未自动注册,可在 bootstrap/app.php 的路由配置中指向频道文件:

->withRouting(
    web: __DIR__.'/../routes/web.php',
    channels: __DIR__.'/../routes/channels.php',
    health: '/up',
)

php artisan channel:list 可列出注册的频道授权回调。本次未运行该命令。

频道支持隐式与显式模型绑定,但不支持 HTTP 路由那样的自动隐式绑定作用域限制。例如频道名同时含租户 ID 和订单 ID,并不意味着框架已经验证订单属于该租户。应在回调或政策中明确核对业务关系。

私有频道和 presence 频道默认使用应用的默认认证 guard。未认证的请求会被直接拒绝,授权回调不会执行。需要多个自定义 guard 时,可传入第三个参数:

Broadcast::channel('channel', function () {
    // 返回基于实际业务关系的授权结果。
}, ['guards' => ['web', 'admin']]);

上例只是 guard 配置形状,不能把空回调直接当作可用授权规则。应只启用应用确实需要的认证路径。

频道多时,可用 php artisan make:channel OrderChannel 生成 App/Broadcasting/OrderChannel.php,再在路由中注册:

use App\Broadcasting\OrderChannel;
Broadcast::channel('orders.{order}', OrderChannel::class);

授权逻辑放在频道类的 join(User $user, Order $order): array|bool,例如返回 $user->id === $order->user_id。频道类由服务容器解析,可在构造器中类型提示所需依赖;模型绑定仍然可用。

避免发起请求的连接重复更新

考虑新增任务:HTTP 请求 /task 返回新任务 JSON,浏览器立即将它插入列表;服务器又广播同一任务的创建事件,监听器再次插入,于是列表出现两条。此时可排除发起请求的 socket:

broadcast(new OrderShipmentStatusUpdated($order))->toOthers();

事件必须使用 Illuminate\Broadcasting\InteractsWithSockets。Echo 初始化连接时得到 socket ID;使用全局 Axios 实例时,该 ID 会自动附在请求的 X-Socket-ID 头中,Laravel 读取它并要求广播服务排除相同 socket。若用自定义 HTTP 客户端,应自行将 Echo.socketId() 加到发往应用的相关请求头中。

需要精确理解:toOthers() 按 socket ID 排除连接,不是按用户账户排除其所有浏览器、所有标签页。它也不是可靠的业务去重机制或授权凭据;如果请求没有相应头,不能指望排除生效。

切换连接、匿名事件和容错

对特定事件选择其他广播连接,可写 broadcast(new OrderShipmentStatusUpdated($order))->via('pusher')。也可在事件构造器内调用 $this->broadcastVia('pusher'),但须使用 InteractsWithBroadcasting trait。广播连接与负责运行任务的队列连接是两件事。

不需要专用事件类时,Broadcast::on('channel')->send() 可以发送匿名事件,默认名称为 AnonymousEvent,默认数据为空;as() 设置名称,with() 设置数据。send() 入队,sendNow() 立即发送。private()、presence() 分别选择私有和 presence 频道,toOthers() 同样可用。

原文用公开的 Broadcast::on('orders.'.$order->id)->with($order) 展示 API。编辑调整:订单示例改为私有频道并选取字段,避免把整份订单公开广播:

Broadcast::private('orders.'.$order->id)
    ->as('OrderPlaced')
    ->with(['id' => $order->id, 'status' => $order->status])
    ->toOthers()
    ->send();

队列服务不可用或广播抛出异常时,可能影响用户请求。事件实现 Illuminate\Contracts\Broadcasting\ShouldRescue 后,Laravel 会通过 rescue helper 捕获广播尝试中的异常,向异常处理器报告,再继续流程。它适合把广播作为补充功能的场景;编辑补充:捕获并记录失败并不等于成功投递,也不应遮蔽对失败的监控。

监听、退出和框架 Hook

Echo 的 channel() 获取公开频道,private() 获取私有频道,随后通过 listen() 注册事件处理器。同一频道可串联多个 listen()。调用 stopListening('OrderShipmentStatusUpdated') 只停止特定事件的监听,不退出频道。

leaveChannel(name) 退出一个指定频道;leave(name) 还会退出与其对应的 private 与 presence 频道。页面卸载时清理订阅,能避免重复处理和不必要的连接活动。

Echo 默认假设事件位于 App\Events 命名空间,初始化时可用 namespace: 'App.Other.Namespace' 覆盖。监听名称前加 . 会禁用自动命名空间前缀,例如 .listen('.Namespace\\Event\\Class', callback)。

React、Vue、Svelte 的 useEcho 默认监听私有频道,并在消费它的组件卸载时自动离开。三个包中的接口同名,下面以 React 的 TypeScript 写法展示;Vue 在 setup 中导入 @laravel/echo-vue,Svelte 从 @laravel/echo-svelte 导入:

import { useEcho } from '@laravel/echo-react';

type OrderData = { order: { id: number; status: string } };
const { leaveChannel, leave, stopListening, listen } = useEcho<OrderData>(
    `orders.${orderId}`,
    ['OrderShipmentStatusUpdated', 'OrderShipped'],
    (event) => {
        console.log(event.order.id, event.order.status);
    },
);

第二个参数可以是单个事件名或多个事件名数组。类型参数提供编辑器检查,不会自动校验网络载荷。返回的 stopListening()、listen() 可以暂停或恢复监听;leaveChannel() 与 leave() 可手动结束订阅。

接口 用途
useEchoPublic('posts', 'PostPublished', callback) 监听公开频道。
useEchoPresence('posts', 'PostPublished', callback) 连接 presence 频道。
useConnectionStatus() 返回随连接变化更新的状态。
useSocketId() 返回当前 socket ID,重连获得新 ID 时更新。

状态值包括 connected(已连接)、connecting(初次连接中)、reconnecting(断线重连中)、disconnected(断开且未重连)、failed(连接失败并停止重试)。React 直接使用 Hook 返回的值;Vue 在模板中响应式展示;原文 Svelte 示例通过 status()、socketId() 读取。

Presence 频道:在授权基础上显示在线成员

Presence 频道可用于聊天室成员列表、协同查看等场景。它同样是私有频道,不过授权成功时应返回可公开给同频道成员的用户信息数组,而不是只返回 true;拒绝则返回 false 或 null。

Broadcast::channel('chat.{roomId}', function (User $user, int $roomId) {
    if ($user->canJoinRoom($roomId)) {
        return ['id' => $user->id, 'name' => $user->name];
    }
    return false;
});

canJoinRoom() 是原文假定由业务实现的方法。这里显式添加拒绝返回值,与原文隐式返回 null 的含义相同。不要把邮件地址、访问令牌等敏感字段加入 presence 用户数组。

Echo.join(`chat.${roomId}`)
    .here((users) => { /* 初始化成员列表 */ })
    .joining((user) => { /* 添加成员 */ })
    .leaving((user) => { /* 移除成员 */ })
    .error((error) => { /* 处理授权或响应解析错误 */ })
    .listen('NewMessage', (event) => { /* 展示消息 */ });

here 在成功加入后立即运行,接收当前频道成员信息;joining 和 leaving 响应成员加入与离开;授权接口返回非 200 或 JSON 解析失败时会触发 error。服务端从 broadcastOn() 返回 new PresenceChannel('chat.'.$this->message->room_id) 即可向同一频道广播;普通 broadcast() 和 toOthers() 的规则仍适用。

加密私有频道

普通私有频道限制谁可以订阅,但广播服务处理的数据本身仍可读。当前文档说明 Pusher Channels 和 Mercure 可使用端到端加密私有频道,使应用及获得授权的客户端能够读取事件数据,而广播服务不能直接读取明文载荷。它与仅使用 TLS 的连接加密不是同一层保护。

配置前述驱动的加密密钥后,事件返回 Illuminate\Broadcasting\EncryptedPrivateChannel:

public function broadcastOn(): array
{
    return [new EncryptedPrivateChannel('orders.'.$this->order->id)];
}

授权沿用普通私有频道的 orders.{orderId} 回调。前端使用 Echo.encryptedPrivate(`orders.${orderId}`) 订阅,再调用 listen()。Pusher 的默认 pusher-js 构建不含解密代码,需改用:

import Pusher from 'pusher-js/with-encryption';
window.Pusher = Pusher;

应同时核对服务器、Echo 与客户端 SDK 的实际版本和支持情况;本文没有把此能力推定为所有广播驱动都具备。

直接广播 Eloquent 模型变化

如果只是把模型创建、更新或删除反映到界面,逐个创建事件类可能重复。模型可以使用 Illuminate\Database\Eloquent\BroadcastsEvents trait,并实现 broadcastOn(string $event): array。原文以 Post 为例,user() 是 belongsTo(User::class),广播频道返回 [$this, $this->user]。

框架会在实例发生 created、updated、deleted、trashed、restored 事件时广播,并把相应操作字符串传给 broadcastOn()。需要抑制某种操作时,返回空数组:

public function broadcastOn(string $event): array
{
    return match ($event) {
        'deleted' => [],
        default => [$this, $this->user],
    };
}

模型直接作为返回值时,Laravel 按完整类名和主键构造私有频道,例如 ID 为 1 的 App\Models\User 对应 App.Models.User.1。也可显式返回 new PrivateChannel('user.'.$this->id);频道构造器还能接收模型,并按相同约定产生名称。需要查看名称时调用 $user->broadcastChannel()。

原文边界补充:若显式使用 new Channel($this->user),虽然名称仍遵从模型命名约定,频道却是公开的;构造器种类决定公开或私有,不能仅靠名称推断权限。

默认事件名是模型短类名加操作名:Post 更新产生 PostUpdated,User 删除产生 UserDeleted。默认载荷包含 model 及模型可广播属性,也可能包含 socket 信息。模型内可实现 broadcastAs(string $event): string|null 与 broadcastWith(string $event): array 分别自定义名称和内容;broadcastAs() 返回 null 时恢复默认命名。

public function broadcastAs(string $event): string|null
{
    return $event === 'created' ? 'post.created' : null;
}

public function broadcastWith(string $event): array
{
    return match ($event) {
        'created' => ['title' => $this->title],
        default => ['model' => $this],
    };
}

这是原文展示接口的逻辑。默认分支仍发送完整模型;正式采用前应审查模型的可序列化字段,必要时改为逐字段白名单。

要控制底层事件创建,可覆写 newBroadcastableEvent(string $event),返回 Illuminate\Database\Eloquent\BroadcastableModelEventOccurred。例如在创建后调用 dontBroadcastToCurrentUser() 排除当前连接:

protected function newBroadcastableEvent(string $event): BroadcastableModelEventOccurred
{
    return (new BroadcastableModelEventOccurred($this, $event))
        ->dontBroadcastToCurrentUser();
}

该片段需导入上面的事件类。前端监听模型事件时加前导句点,因为它并不对应 App\Events 内的普通事件类:

Echo.private(`App.Models.User.${userId}`)
    .listen('.UserUpdated', (event) => {
        console.log(event.model);
    });

三个前端集成都提供 useEchoModel,例如 useEchoModel('App.Models.User', userId, ['UserUpdated'], callback)。TypeScript 可用 useEchoModel<User, 'App.Models.User'>(...) 指定模型载荷类型,获得 event.model.id 等属性的编辑提示。类型声明应与实际广播白名单一致。

客户端事件与实时通知

打字提示等短暂消息可以由客户端直接发给其他连接,绕过 Laravel 请求处理。在 Pusher Channels 中,需先在应用后台 App Settings 启用 Client Events。Echo 的发送和接收接口是 whisper() 与 listenForWhisper():

Echo.private(`chat.${roomId}`)
    .whisper('typing', { name: user.name });

Echo.private(`chat.${roomId}`)
    .listenForWhisper('typing', (event) => {
        console.log(event.name);
    });

Hook 用户可从 useEcho(...) 返回值获取 channel,然后调用 channel().whisper(...) 和 channel().listenForWhisper(...)。静态审查补充:客户端事件由对端客户端提供,不能把其中的姓名、用户 ID 或业务状态当作已由服务器验证;它适合输入提示,不适合直接确认支付或修改权限。展示字符串时使用框架转义或文本节点,避免拼进 HTML。

Laravel 通知也可通过广播送达浏览器。先按 广播通知文档将通知配置为使用 broadcast channel,再按接收实体的类名与 ID 订阅:

const callback = (notification) => {
    console.log(notification.type);
};

Echo.private(`App.Models.User.${userId}`).notification(callback);

// 不离开频道,只停止这个通知回调;必须传入同一函数对象。
Echo.private(`App.Models.User.${userId}`)
    .stopListeningForNotification(callback);

默认频道文件含 App.Models.User.{id} 的授权回调。React、Vue、Svelte 可从 useEchoModel('App.Models.User', userId) 获取 channel(),再注册 notification()。

采用前的核对重点

一条完整的实时更新链路需要同时满足:事件在恰当时机派发,队列与广播服务可用,频道订阅通过认证及业务归属检查,载荷字段适合暴露,前端按正确名称监听并在生命周期结束时清理。事务提交与 X-Socket-ID 分别处理数据可见性和发起连接的重复更新,不能互相替代。

本次静态检查确认了原文示例中的公开频道、默认序列化、可配置明文 WebSocket、客户端事件信任边界,以及来源段落所示版本差异;未执行 PHP、JavaScript、Artisan、Composer、NPM 或 OpenSSL 示例,也未测试平台服务和实际授权配置。静态审查没有发现某类问题,不代表应用不存在漏洞。

来源与归属:Laravel 文档贡献者,原始入口;采用 13.x 版本文档,并参考 官方部署要求确认 PHP 8.3+。原始文档与代码的权利归原作者及项目权利人;不把框架许可证自动推定为所有站点内容的许可证。本文配图为原创流程示意图。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容