I am a full-stack developer, and I make video tutorials on my youtube channel, Qirolab.
The code in this story is for educational purposes. The readers are solely responsible for whatever they build with it.
在本教程中,我们将使用 Laravel、Nuxt 3、Sanctum 和 Laravel Reverb 构建一个实时聊天应用程序,以处理用户之间的安全实时消息传递。我们将设置用户身份验证、连接到安全 API,并确保聊天即时更新,以获得流畅且响应迅速的体验。
我们将使用该模块来管理 SPA 身份验证,该模块可高效处理单页应用程序 (SPA) 和 API 身份验证。要了解有关使用此模块的更多信息,请参阅有关Nuxt 3 SPA 身份验证的文章。
在这个项目中,我们将配置 Laravel Reverb 进行实时事件广播,使用 Sanctum 实现身份验证,并构建一个动态显示和管理聊天消息的 Nuxt 3 前端。让我们开始吧!
首先,确保已安装并配置 Laravel Sanctum。Sanctum 允许对单页应用程序 (SPA) 进行基于令牌的身份验证。然后,安装并配置 Laravel Reverb 以实现实时功能。
将以下 Reverb 环境变量添加到您的.env
文件中:
REVERB_APP_ID=my-app-id REVERB_APP_KEY=my-app-key REVERB_APP_SECRET=my-app-secret REVERB_HOST="localhost" REVERB_PORT=8080 REVERB_SCHEME=http
要存储聊天消息,请为chat_messages
表创建迁移。运行:
php artisan make:migration create_chat_messages_table
更新迁移文件如下:
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
为了实时广播消息,请创建一个MessageSent
事件类:
php artisan make:event MessageSent
更新事件类:
<?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, InteractsWithSockets, SerializesModels; public function __construct(public ChatMessage $message) { // } public function broadcastOn(): array { return [new PrivateChannel("chat.{$this->message->receiver_id}")]; } }
在channels.php
中,定义广播频道:
Broadcast::channel('chat.{id}', function ($user, $id) { return (int) $user->id === (int) $id; });
添加这些路由来管理发送和检索聊天消息:
Route::get('/messages/{user}', function (User $user, Request $request) { return ChatMessage::query() ->where(function ($query) use ($user, $request) { $query->where('sender_id', $request->user()->id) ->where('receiver_id', $user->id); }) ->orWhere(function ($query) use ($user, $request) { $query->where('sender_id', $user->id) ->where('receiver_id', $request->user()->id); }) ->with(['sender', 'receiver']) ->orderBy('id', 'asc') ->get(); })->middleware('auth:sanctum'); Route::post('/messages/{user}', function (User $user, Request $request) { $request->validate(['message' => 'required|string']); $message = ChatMessage::create([ 'sender_id' => $request->user()->id, 'receiver_id' => $user->id, 'text' => $request->message ]); broadcast(new MessageSent($message)); return $message; });
为了允许 Nuxt 3 与 Reverb 连接,请将这些变量添加到.env
文件中:
NUXT_PUBLIC_REVERB_APP_ID=my-app-id NUXT_PUBLIC_REVERB_APP_KEY=my-app-key NUXT_PUBLIC_REVERB_APP_SECRET=my-app-secret NUXT_PUBLIC_REVERB_HOST="localhost" NUXT_PUBLIC_REVERB_PORT=8080 NUXT_PUBLIC_REVERB_SCHEME=http
然后,在nuxt.config.ts
中加载它们:
export default defineNuxtConfig({ runtimeConfig: { public: { REVERB_APP_ID: process.env.NUXT_PUBLIC_REVERB_APP_ID, REVERB_APP_KEY: process.env.NUXT_PUBLIC_REVERB_APP_KEY, REVERB_APP_SECRET: process.env.NUXT_PUBLIC_REVERB_APP_SECRET, REVERB_HOST: process.env.NUXT_PUBLIC_REVERB_HOST, REVERB_PORT: process.env.NUXT_PUBLIC_REVERB_PORT, REVERB_SCHEME: process.env.NUXT_PUBLIC_REVERB_SCHEME, }, }, });
要管理实时更新,请创建一个laravel-echo.client.ts
插件:
import Echo from "laravel-echo"; import Pusher, { type ChannelAuthorizationCallback } from "pusher-js"; declare global { interface Window { Echo: Echo; Pusher: typeof Pusher; } } export default defineNuxtPlugin(() => { window.Pusher = Pusher; const config = useRuntimeConfig(); const echo = new Echo({ broadcaster: "reverb", key: config.public.REVERB_APP_KEY, wsHost: config.public.REVERB_HOST, wsPort: config.public.REVERB_PORT ?? 80, wssPort: config.public.REVERB_PORT ?? 443, forceTLS: (config.public.REVERB_SCHEME ?? "https") === "https", enabledTransports: ["ws", "wss"], authorizer: (channel, options) => ({ authorize: (socketId, callback) => { useSanctumFetch("api/broadcasting/auth", { method: "post", body: { socket_id: socketId, channel_name: channel.name }, }) .then(response => callback(null, response)) .catch(error => callback(error, null)); }, }), }); return { provide: { echo } }; });
为了在我们的 Nuxt 3 应用中启用实时聊天功能,我创建了一个新页面, chats > [id].vue
,用户可以在其中选择其他用户并与他们聊天。此页面连接到 Laravel 后端以管理聊天消息和用户数据,并使用 Laravel Echo 和 WebSockets 进行实时更新和输入指示器。
以下是我们如何构建此聊天功能的详细说明:
首先,我们使用useRoute
可组合项从 URL 中检索userID
。此userID
可识别我们正在聊天的用户,并加载必要的用户和聊天消息。
const route = useRoute(); const userID = route.params.id; const { user: currentUser } = useSanctum<User>(); const { data: user } = await useAsyncData( `user-${userID}`, () => useSanctumFetch<User>(`/api/users/${userID}`) ); const { data: messages } = useAsyncData( `messages-${userID}`, () => useSanctumFetch<ChatMessage[]>(`/api/messages/${userID}`), { default: (): ChatMessage[] => [] } );
此代码片段使用自定义可组合useSanctumFetch
在页面挂载时异步加载消息。
我们动态地呈现每条消息,并根据它们来自当前用户还是聊天参与者来设置它们的样式。
<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>
聊天窗口使用nextTick()
自动滚动到最新消息。
watch( messages, () => { nextTick(() => messageContainerScrollToBottom()); }, { deep: true } ); function messageContainerScrollToBottom() { if (!messagesContainer.value) return; messagesContainer.value.scrollTo({ top: messagesContainer.value.scrollHeight, behavior: 'smooth' }); }
用户可以使用输入字段发送消息。提交后,会向 Laravel 后端发送 POST 请求。
const newMessage = ref(""); const sendMessage = async () => { if (!newMessage.value.trim()) return; const messageResponse = await useSanctumFetch<ChatMessage>(`/api/messages/${userID}`, { method: "post", body: { message: newMessage.value } }); messages.value.push(messageResponse); newMessage.value = ""; };
消息发送后立即更新。
Laravel Echo 监听诸如MessageSent
和typing
类的关键事件。
onMounted(() => { if (currentUser.value) { $echo.private(`chat.${currentUser.value.id}`) .listen('MessageSent', (response: { message: ChatMessage }) => { messages.value.push(response.message); }) .listenForWhisper("typing", (response: { userID: number }) => { isUserTyping.value = response.userID === user.value?.id; if (isUserTypingTimer.value) clearTimeout(isUserTypingTimer.value); isUserTypingTimer.value = setTimeout(() => { isUserTyping.value = false; }, 1000); }); } });
它处理实时消息更新和输入指示器,这些指示器在一秒钟不活动后消失。
为了显示打字指示器,我们用whisper
触发“打字”事件。
const sendTypingEvent = () => { if (!user.value || !currentUser.value) return; $echo.private(`chat.${user.value.id}`).whisper("typing", { userID: currentUser.value.id }); };
每当用户键入时就会发送此事件,并且收件人会看到键入指示。
聊天界面包括一个带有选定用户姓名的标题以及消息部分和输入字段。
<template> <div> <header v-if="user" class="bg-white shadow"> <div class="px-4 py-6 mx-auto max-w-7xl sm:px-6 lg:px-8"> <h1 class="text-2xl font-bold ">{{ user.name }}</h1> </div> </header> <div class="flex flex-col items-center py-5"> <div class="container w-full h-full p-8 space-y-3 bg-white rounded-xl"> <div v-if="currentUser"> <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-shrink-0"> <span v-if="user && isUserTyping" class="text-gray-500"> {{ user.name }} is typing... </span> <div class="flex items-center justify-between w-full p-4 border-t border-gray-200"> <input type="text" v-model="newMessage" @keydown="sendTypingEvent" @keyup.enter="sendMessage" class="w-full p-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Type a message..." /> <button @click.prevent="sendMessage" class="inline-flex items-center justify-center w-12 h-12 ml-4 text-white bg-blue-500 rounded-lg hover:bg-blue-600"> <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" /> </svg> </button> </div> </div> </div> </div> </div> </div> </template>
这完成了使用 Laravel Sanctum 和 Echo 与 Nuxt 3 的实时聊天功能设置。
我们现在使用 Laravel、Sanctum、Reverb 和 Nuxt 3 创建了一个安全的实时聊天应用程序。此设置可以轻松扩展以包含消息反应或多个聊天室等附加功能。
如需完整代码,请访问GitHub 存储库。