Σε αυτό το σεμινάριο, θα δημιουργήσουμε μια εφαρμογή συνομιλίας σε πραγματικό χρόνο χρησιμοποιώντας 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 για ενημερώσεις σε πραγματικό χρόνο και δείκτες πληκτρολόγησης.
Ακολουθεί μια ανάλυση του τρόπου δομής αυτής της λειτουργίας συνομιλίας:
Αρχικά, ανακτούμε το 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
, ένα προσαρμοσμένο composable, για τη ασύγχρονη φόρτωση μηνυμάτων κατά την προσάρτηση της σελίδας.
Αποδίδουμε κάθε μήνυμα δυναμικά, διαμορφώνοντάς το με βάση το αν είναι από τον τρέχοντα χρήστη ή από τον συμμετέχοντα στη συνομιλία.
<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 αποστέλλεται στο backend της 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 .