Laravel, web 工匠最喜欢的 PHP 框架,在其武器库中新添了一个强大的工具:Reverb。作为 Laravel 的官方包之一,这个 WebSocket 服务器应用程序可以让你轻松地将实时功能集成到你的 Laravel 应用程序中,从而将交互提升到一个全新的水平。
#什么是 Laravel Reverb?
Reverb 充当你的 Laravel 应用程序及其用户之间的中介。它基于 WebSocket 技术建立双向、实时通信,允许网页在不刷新页面的情况下接收服务器上的更新。这意味着你的用户可以更动态、更快速地体验你的应用程序。
#Laravel Reverb 的主要功能
超快速度: 为实时信息提供卓越的性能,没有延迟。
可扩展性: 随着你的应用程序增长,可处理不断增加的用户流量。
无缝集成: 与 Laravel 和 Laravel Echo 中添加的广播功能协同工作,使开发变得简单。
推送更新: 将更新、消息或事件推送到客户端,以便立即分享你的信息。
内置安全: 数据加密和身份验证保证,确保安全通信
#将 Laravel Reverb 添加到你的聊天项目
使用 Laravel Reverb,你可以构建动态的聊天应用程序。消息会立即发布,让用户全面参与。以下是涉及的步骤分解
#步骤 1:设置你的 Laravel 项目
#步骤 2:安装和配置 Reverb
通过运行以下命令安装 Laravel Reverb
php artisan install:broadcasting
安装 Reverb 后,现在可以从 config/reverb.php
文件中修改其配置。为了建立与 Reverb 的连接,需要在客户端和服务器之间交换一组 Reverb “应用程序”凭据。这些凭据在服务器上配置,用于验证来自客户端的请求。可以使用以下环境变量定义这些凭据
BROADCAST_DRIVER=reverb
REVERB_APP_ID=my-app-id
REVERB_APP_KEY=my-app-key
REVERB_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=sqlite
DB_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,开发人员可以确保他们的应用程序保持响应和动态,提供现代用户所期望的实时更新和交互。祝您编码愉快!