Laravel, web 工匠最喜欢的 PHP 框架,在其武器库中新添了一个强大的工具:Reverb。作为 Laravel 的官方包之一,这个 WebSocket 服务器应用程序可以让你轻松地将实时功能集成到你的 Laravel 应用程序中,从而将交互提升到一个全新的水平。
什么是 Laravel Reverb?
Reverb 充当你的 Laravel 应用程序及其用户之间的中介。它基于 WebSocket 技术建立双向、实时通信,允许网页在不刷新页面的情况下接收服务器上的更新。这意味着你的用户可以更动态、更快速地体验你的应用程序。
Laravel Reverb 的主要功能
超快速度: 为实时信息提供卓越的性能,没有延迟。
可扩展性: 随着你的应用程序增长,可处理不断增加的用户流量。
无缝集成: 与 Laravel 和 Laravel Echo 中添加的广播功能协同工作,使开发变得简单。
推送更新: 将更新、消息或事件推送到客户端,以便立即分享你的信息。
内置安全: 数据加密和身份验证保证,确保安全通信
将 Laravel Reverb 添加到你的聊天项目
使用 Laravel Reverb,你可以构建动态的聊天应用程序。消息会立即发布,让用户全面参与。以下是涉及的步骤分解
步骤 1:设置你的 Laravel 项目
-
确保你已经设置了一个 Laravel 应用程序(推荐版本 11 或更高)。
-
如果你从头开始,请使用
composer create-project laravel/laravel your-chat-app-name
。
步骤 2:安装和配置 Reverb
通过运行以下命令安装 Laravel Reverb
php artisan install:broadcasting
安装 Reverb 后,现在可以从 config/reverb.php
文件中修改其配置。为了建立与 Reverb 的连接,需要在客户端和服务器之间交换一组 Reverb “应用程序”凭据。这些凭据在服务器上配置,用于验证来自客户端的请求。可以使用以下环境变量定义这些凭据
BROADCAST_DRIVER=reverbREVERB_APP_ID=my-app-idREVERB_APP_KEY=my-app-keyREVERB_APP_SECRET=my-app-secret
它还会自动在 resources/js
目录中创建 echo.js
文件。
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'],});
请参考 Laravel 文档,了解特定于你的应用程序服务器的配置步骤 https://laravel.net.cn/docs/11.x/reverb
步骤 3:运行服务器
可以使用 reverb:start
Artisan 命令启动 Reverb 服务器
php artisan reverb:start
默认情况下,Reverb 服务器将在 0.0.0.0:8080
启动,使其可从所有网络接口访问。
如果你想设置特定的主机或端口,可以在启动服务器时使用 –host
和 –port
选项。
php artisan reverb:start --host=127.0.0.1 --port=9000
你也可以在应用程序的 .env
配置文件中定义 REVERB_SERVER_HOST
和 REVERB_SERVER_PORT
环境变量。
步骤 4:设置数据库
打开你的 .env
文件,并调整设置以设置你的数据库。以下是用 SQLite 为简单起见的一个例子
DB_CONNECTION=sqliteDB_DATABASE=/path/to/database.sqlite
你可以通过简单地运行以下命令来创建 SQLite 数据库
touch /path/to/database.sqlite
对于这个演示,我们将创建五个预定义的房间。让我们从生成一个带有迁移的模型 ChatMessage
开始,用于 chat_messages
表。
php artisan make:model ChatMessage --migration
为了简化操作,仅为这个模型创建 name 属性,并迁移它。
Schema::create('chat_messages', function (Blueprint $table) { $table->id(); $table->foreignId('receiver_id'); $table->foreignId('sender_id'); $table->text('text'); $table->timestamps();});
php artisan migrate
现在,让我们在 ChatMessage
模型中添加必要的关联。打开 app/Models
目录中的 ChatMessage.php
文件,并按如下方式更新它
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory;use Illuminate\Database\Eloquent\Model; class ChatMessage extends Model{ use HasFactory; protected $fillable = [ 'sender_id', 'receiver_id', 'text' ]; public function sender() { return $this->belongsTo(User::class, 'sender_id'); } public function receiver() { return $this->belongsTo(User::class, 'receiver_id'); }}
步骤 5:创建事件
为了处理消息的广播,我们将创建一个名为 MessageSent
的事件。这个事件将实现 Laravel 的 ShouldBroadcastNow
接口,该接口允许通过 WebSockets 立即广播,无需排队。请按照以下步骤创建和设置事件
-
在
App\Events
目录中创建一个新的 PHP 文件,并将其命名为MessageSent.php
。 -
打开新创建的文件,并添加以下代码
<?php namespace App\Events; use App\Models\ChatMessage;use Illuminate\Broadcasting\InteractsWithSockets;use Illuminate\Broadcasting\PrivateChannel;use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;use Illuminate\Foundation\Events\Dispatchable;use Illuminate\Queue\SerializesModels; class MessageSent implements ShouldBroadcastNow{ use Dispatchable; use InteractsWithSockets; use SerializesModels; /** * Create a new event instance. */ public function __construct(public ChatMessage $message) { // } /** * Get the channels the event should broadcast on. * * @return array<int, \Illuminate\Broadcasting\Channel> */ public function broadcastOn(): array { return [ new PrivateChannel("chat.{$this->message->receiver_id}"), ]; }}
步骤 6:创建私有频道路由
Laravel 应用程序中的 channels.php
文件在定义用于实时通信功能的广播频道中起着至关重要的作用。
<?php use Illuminate\Support\Facades\Broadcast; Broadcast::channel('chat.{id}', function ($user, $id) { return (int) $user->id === (int) $id;});
此代码使用 Laravel 的 Broadcast
门面定义了一个名为 chat.{id}
的私有频道。私有频道会根据用户身份验证和授权逻辑限制访问。
步骤 7:定义路由
1. 聊天室路由
Route::get('/chat/{friend}', function (User $friend) { return view('chat', [ 'friend' => $friend ]);})->middleware(['auth'])->name('chat');
这个路由负责渲染聊天界面。它接受一个动态参数 {friend}
,代表用户的聊天伙伴。
3. 获取聊天消息路由
Route::get('/messages/{friend}', function (User $friend) { return ChatMessage::query() ->where(function ($query) use ($friend) { $query->where('sender_id', auth()->id()) ->where('receiver_id', $friend->id); }) ->orWhere(function ($query) use ($friend) { $query->where('sender_id', $friend->id) ->where('receiver_id', auth()->id()); }) ->with(['sender', 'receiver']) ->orderBy('id', 'asc') ->get();})->middleware(['auth']);
这个路由检索已认证用户和指定好友({friend}
)之间交换的聊天消息。该查询确保它检索用户是发送者或接收者的消息,包括对话的两个方向。
4. 发送聊天消息路由
Route::post('/messages/{friend}', function (User $friend) { $message = ChatMessage::create([ 'sender_id' => auth()->id(), 'receiver_id' => $friend->id, 'text' => request()->input('message') ]); broadcast(new MessageSent($message)); return $message;});
创建消息后,它利用 Laravel 的广播功能,使用 broadcast(new MessageSent($message))
。这一行将新创建的消息广播给所有连接的用户,通过 Reverb 实现实时聊天功能。
步骤 8:创建 Blade 视图
为了渲染聊天界面,你需要创建一个 Blade 视图文件。在 resources/views
目录中创建一个名为 chat.blade.php
的新文件,并添加以下代码
<x-app-layout> <x-slot name="header"> <h2 class="text-xl font-semibold leading-tight text-gray-800"> {{ $friend->name }} </h2> </x-slot> <div class="py-12"> <div class="mx-auto max-w-7xl sm:px-6 lg:px-8"> <div class="overflow-hidden bg-white shadow-sm sm:rounded-lg"> <div class="p-6 bg-white border-b border-gray-200"> <chat-component :friend="{{ $friend }}" :current-user="{{ auth()->user() }}" /> </div> </div> </div> </div></x-app-layout>
这里的关键元素是 <chat-component :friend="{{ $friend }}" :current-user="{{ auth()->user() }}" />
。这一行渲染了一个名为 chat-component
的 Vue.js 组件。
步骤 8:创建聊天组件
<template> <div> <div class="flex flex-col justify-end h-80"> <div ref="messagesContainer" class="p-4 overflow-y-auto max-h-fit"> <div v-for="message in messages" :key="message.id" class="flex items-center mb-2" > <div v-if="message.sender_id === currentUser.id" class="p-2 ml-auto text-white bg-blue-500 rounded-lg" > {{ message.text }} </div> <div v-else class="p-2 mr-auto bg-gray-200 rounded-lg"> {{ message.text }} </div> </div> </div> </div> <div class="flex items-center"> <input type="text" v-model="newMessage" @keydown="sendTypingEvent" @keyup.enter="sendMessage" placeholder="Type a message..." class="flex-1 px-2 py-1 border rounded-lg" /> <button @click="sendMessage" class="px-4 py-1 ml-2 text-white bg-blue-500 rounded-lg" > Send </button> </div> <small v-if="isFriendTyping" class="text-gray-700"> {{ friend.name }} is typing... </small> </div></template> <script setup>import axios from "axios";import { nextTick, onMounted, ref, watch } from "vue"; const props = defineProps({ friend: { type: Object, required: true, }, currentUser: { type: Object, required: true, },}); const messages = ref([]);const newMessage = ref("");const messagesContainer = ref(null);const isFriendTyping = ref(false);const isFriendTypingTimer = ref(null); watch( messages, () => { nextTick(() => { messagesContainer.value.scrollTo({ top: messagesContainer.value.scrollHeight, behavior: "smooth", }); }); }, { deep: true }); const sendMessage = () => { if (newMessage.value.trim() !== "") { axios .post(`/messages/${props.friend.id}`, { message: newMessage.value, }) .then((response) => { messages.value.push(response.data); newMessage.value = ""; }); }}; const sendTypingEvent = () => { Echo.private(`chat.${props.friend.id}`).whisper("typing", { userID: props.currentUser.id, });}; onMounted(() => { axios.get(`/messages/${props.friend.id}`).then((response) => { console.log(response.data); messages.value = response.data; }); Echo.private(`chat.${props.currentUser.id}`) .listen("MessageSent", (response) => { messages.value.push(response.message); }) .listenForWhisper("typing", (response) => { isFriendTyping.value = response.userID === props.friend.id; if (isFriendTypingTimer.value) { clearTimeout(isFriendTypingTimer.value); } isFriendTypingTimer.value = setTimeout(() => { isFriendTyping.value = false; }, 1000); });});</script>
这个 Vue.js 组件管理聊天界面的动态行为。它显示一个可滚动的消息列表,根据发送者(当前用户或好友)以不同的方式进行样式设置。它提供一个用于撰写新消息的输入字段和一个用于发送消息的按钮。它利用 axios
进行 HTTP 请求,以获取初始消息和发送新消息。
实时功能是通过 Laravel Echo 实现的
-
它监听广播的
MessageSent
事件,以便在收到新消息时更新消息列表。 -
它利用私有频道上的 whispers 来通知聊天伙伴用户的打字活动,并接收来自好友的类似通知。
步骤 9:运行项目
要运行 Laravel 项目,我们需要执行以下命令
php artisan serve
为了启动前端
npm run dev
运行 reverb
php artisan reverb:start
源代码
你可以在以下 GitHub 仓库中找到这个 Laravel Reverb 聊天实现的源代码: https://github.com/qirolab/laravel-reverb-chat
通过利用 Laravel Reverb,开发人员可以确保他们的应用程序保持响应和动态,提供现代用户所期望的实时更新和交互。祝您编码愉快!