Дар ин дастур, мо бо истифода аз Laravel, Nuxt 3, Sanctum ва Laravel Reverb як барномаи воқеии сӯҳбатро барои коркарди паёмнависии бехатар ва зинда байни корбарон месозем. Мо аутентификатсияи корбарро насб мекунем, ба API-и бехатар пайваст мешавем ва кафолат медиҳем, ки чат барои таҷрибаи ҳамвор ва ҷавобгӯ фавран нав мешавад.
Мо модулро барои идоракунии аутентификатсияи SPA истифода хоҳем кард, ки он ҳам аризаи ягонаи саҳифа (SPA) ва ҳам аутентификатсияи API-ро самаранок идора мекунад. Барои гирифтани маълумоти бештар дар бораи истифодаи ин модул, ба мақола дар бораи аутентификатсияи Nuxt 3 SPA нигаред.
Дар ин лоиҳа, мо Laravel Reverb-ро барои пахши воқеии рӯйдодҳо танзим мекунем, аутентификатсияро бо Sanctum амалӣ месозем ва фронти Nuxt 3-ро месозем, ки паёмҳои чатро ба таври динамикӣ намоиш ва идора мекунад. Биёед оғоз кунем!
Аввалан, боварӣ ҳосил кунед, ки Laravel Sanctum насб ва танзим карда шудааст. Sanctum имкон медиҳад, ки аутентификатсия дар асоси нишонаҳо барои замимаҳои як саҳифа (SPA). Сипас, Laravel Reverb-ро барои қобилиятҳои вақти воқеӣ насб ва танзим кунед.
Ба файли .env
и худ тағирёбандаҳои муҳити Reverb-и зеринро илова кунед:
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 барои навсозӣ ва чопкунии нишондиҳандаҳои вақти воқеӣ.
Ин аст шарҳи он, ки мо ин функсияи чатро чӣ гуна сохтор кардаем:
Аввалан, мо userID
аз URL бо useRoute
composable мегирем. Ин 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' }); }
Истифодабарандагон метавонанд бо майдони вуруд паём фиристанд. Ҳангоми пешниҳод, дархости POST ба пуштибонии Laravel фиристода мешавад.
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 муроҷиат кунед.