使用 Reverb 和 Vue 为 Laravel 添加实时聊天功能

上次更新于 作者:

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=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_HOSTREVERB_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 立即广播,无需排队。请按照以下步骤创建和设置事件

  1. App\Events 目录中创建一个新的 PHP 文件,并将其命名为 MessageSent.php

  2. 打开新创建的文件,并添加以下代码

<?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 实现的

  1. 它监听广播的 MessageSent 事件,以便在收到新消息时更新消息列表。

  2. 它利用私有频道上的 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,开发人员可以确保他们的应用程序保持响应和动态,提供现代用户所期望的实时更新和交互。祝您编码愉快!

Harish Kumar photo

嗨,我是哈里什,一名全栈 Web 开发者。我在 Qirolab YouTube 频道上教授带有实际屏幕录制的 Web 开发教程。

归档于
Cube

Laravel 时事通讯

加入 40,000 多名其他开发者,不要错过新的技巧、教程等等。

Laravel Forge logo

Laravel Forge

轻松创建和管理您的服务器,并在几秒钟内部署您的 Laravel 应用程序。

Laravel Forge
Tinkerwell logo

Tinkerwell

Laravel 开发人员必备的代码运行器。使用 AI、自动完成和对本地和生产环境的即时反馈进行微调。

Tinkerwell
No Compromises logo

无妥协

Joel 和 Aaron,来自 No Compromises 播客的两名经验丰富的开发者,现在可以为您的 Laravel 项目聘用。 ⬧ 固定价格 7500 美元/月。 ⬧ 无漫长的销售流程。 ⬧ 无合同。 ⬧ 100% 返款保证。

无妥协
Kirschbaum logo

Kirschbaum

提供创新和稳定性,以确保您的 Web 应用程序成功。

Kirschbaum
Shift logo

Shift

运行旧版本的 Laravel?即时、自动的 Laravel 升级和代码现代化,使您的应用程序保持新鲜。

Shift
Bacancy logo

Bacancy

使用经验丰富的 Laravel 开发人员(拥有 4-6 年的经验)为您的项目增光添彩,每月仅需 2500 美元。获得 160 小时的专业知识和无风险的 15 天试用。立即安排电话!

Bacancy
Lucky Media logo

Lucky Media

立即获得 Lucky - Laravel 开发的理想选择,拥有超过十年的经验!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel 电子商务

Laravel 的电子商务。一个开源包,将现代无头电子商务功能的强大功能带入 Laravel。

Lunar: Laravel 电子商务
LaraJobs logo

LaraJobs

官方 Laravel 招聘板

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS 启动工具包

SaaSykit 是一个 Laravel SaaS 启动工具包,包含运行现代 SaaS 所需的所有功能。付款、漂亮的结账、管理面板、用户仪表板、身份验证、就绪组件、统计数据、博客、文档等等。

SaaSykit: Laravel SaaS 启动工具包
Rector logo

Rector

您无缝 Laravel 升级、降低成本和加速创新的合作伙伴,助力企业成功

Rector
MongoDB logo

MongoDB

通过 MongoDB 和 Laravel 的强大集成来增强您的 PHP 应用程序,使开发人员能够轻松高效地构建应用程序。支持事务性、搜索、分析和移动用例,同时使用熟悉的 Eloquent API。了解 MongoDB 的灵活、现代数据库如何改变您的 Laravel 应用程序。

MongoDB
Maska is a Simple Zero-dependency Input Mask Library image

Maska 是一个简单的无依赖输入掩码库

阅读文章
Add Swagger UI to Your Laravel Application image

将 Swagger UI 添加到您的 Laravel 应用程序

阅读文章
Assert the Exact JSON Structure of a Response in Laravel 11.19 image

在 Laravel 11.19 中断言响应的精确 JSON 结构

阅读文章
Build SSH Apps with PHP and Laravel Prompts image

使用 PHP 和 Laravel 提示构建 SSH 应用程序

阅读文章
Building fast, fuzzy site search with Laravel and Typesense image

使用 Laravel 和 Typesense 构建快速、模糊的网站搜索

阅读文章
Add Comments to your Laravel Application with the Commenter Package image

使用 Commenter 包向您的 Laravel 应用程序添加评论

阅读文章