Project Templates
Social Network
Chat in Tempo Reale per il Tuo Social Network
54 min
introduzione in questo tutorial, imparerai come implementare un sistema di messaggistica in tempo reale per la tua applicazione di social network utilizzando back4app costruirai una funzionalità di chat completa che consente agli utenti di inviare e ricevere messaggi istantaneamente, vedere gli indicatori di digitazione e gestire le conversazioni caratteristiche essenziali per piattaforme social coinvolgenti back4app è una piattaforma backend as a service (baas) costruita su parse server che fornisce potenti capacità in tempo reale attraverso la sua funzionalità live query con l'infrastruttura in tempo reale di back4app, puoi creare sistemi di messaggistica reattivi senza gestire server websocket complessi o preoccupazioni di scalabilità alla fine di questo tutorial, avrai creato un sistema di messaggistica completamente funzionale simile a quello utilizzato in back4gram, un'applicazione di social network implementerai la creazione di conversazioni, lo scambio di messaggi in tempo reale, gli indicatori di digitazione e la ricerca utenti, offrendo ai tuoi utenti un'esperienza di comunicazione fluida progetto back4gram trova qui il codice completo per un progetto campione di social network costruito con back4app requisiti per completare questo tutorial, avrai bisogno di un account back4app puoi registrarti per un account gratuito su back4app com https //www back4app com un progetto back4app configurato con il parse javascript sdk inizializzato node js installato sulla tua macchina locale conoscenze di base di javascript, react js e back4app/parse server un sistema di autenticazione già implementato se non l'hai ancora configurato, dai un'occhiata al nostro tutorial sul sistema di autenticazione https //www back4app com/docs/react/authentication tutorial familiarità con i react hooks e il ciclo di vita dei componenti passo 1 – comprendere le capacità in tempo reale di back4app prima di iniziare a programmare, comprendiamo come back4app abilita la funzionalità in tempo reale attraverso la sua funzione live query spiegazione della live query la live query è una funzionalità di parse server che consente ai client di iscriversi a query e ricevere aggiornamenti quando gli oggetti che corrispondono a quelle query vengono creati, aggiornati o eliminati questo è perfetto per costruire applicazioni in tempo reale come i sistemi di chat ecco come funziona la live query il client si iscrive a una query specifica (ad esempio, "tutti i messaggi nella conversazione x") quando un oggetto corrispondente viene creato, aggiornato o eliminato sul server la live query notifica automaticamente tutti i client iscritti i client possono quindi aggiornare la loro interfaccia utente in risposta a questi eventi nel contesto di un sistema di messaggistica, questo significa quando viene inviato un nuovo messaggio, tutti gli utenti in quella conversazione lo ricevono istantaneamente quando un utente inizia a digitare, gli altri utenti possono vedere un indicatore di digitazione in tempo reale quando un messaggio viene letto, le ricevute di lettura possono essere aggiornate per tutti i partecipanti impostare live query su back4app per utilizzare live query, è necessario abilitarlo nel tuo dashboard di back4app accedi al tuo dashboard di back4app naviga su impostazioni del server > hosting web e live query abilita live query aggiungi le classi che desideri utilizzare con live query (nel nostro caso, "messaggio" e "stato di digitazione") successivamente, nel tuo codice client, devi inizializzare il client live query // initialize parse with your back4app credentials parse initialize("your app id", "your javascript key"); parse serverurl = "https //parseapi back4app com/"; // initialize live query parse livequeryserverurl = "wss\ //your app id back4app io"; ora passiamo alla progettazione del nostro schema di database per il nostro sistema di messaggistica progetto back4gram trova qui il codice completo per un progetto campione di social network costruito con back4app passo 2 – progettazione dello schema del database per il nostro sistema di messaggistica, avremo bisogno di diverse classi parse conversazione rappresenta una chat tra due o più utenti messaggio rappresenta messaggi individuali all'interno di una conversazione stato di scrittura tiene traccia di quando gli utenti stanno scrivendo in una conversazione creiamo queste classi in back4app classe conversazione la classe conversation avrà i seguenti campi partecipanti (array di puntatori a user) gli utenti coinvolti nella conversazione ultimomessaggio (stringa) il testo dell'ultimo messaggio aggiornatoil (data) aggiornato automaticamente da parse quando il record cambia classe messaggio la classe messaggio avrà conversazione (puntatore a conversazione) la conversazione a cui appartiene questo messaggio mittente (puntatore a user) l'utente che ha inviato il messaggio testo (stringa) il contenuto del messaggio creatoil (data) creato automaticamente da parse quando il messaggio viene inviato classe typingstatus la classe typingstatus traccerà gli indicatori di digitazione utente (puntatore all'utente) l'utente che sta digitando conversazione (puntatore alla conversazione) la conversazione in cui si sta digitando stadigitando (booleano) se l'utente sta attualmente digitando ora, impostiamo la nostra struttura di progetto per implementare questo sistema di messaggistica progetto back4gram trova qui il codice completo per un progetto campione di social network costruito con back4app passo 3 – impostare la struttura del progetto organizziamo il nostro codice per il sistema di messaggistica ci concentreremo sui componenti essenziali src/ ├── components/ │ └── ui/ │ ├── toaster js │ └── avatar js ├── pages/ │ └── messagespage js ├── app js └── parseconfig js creiamo un semplice toaster js per le notifiche // src/components/ui/toaster js export const toaster = { create ({ title, description, type }) => { // in a real app, you would use a proper toast notification component console log(`\[${type touppercase()}] ${title} ${description}`); alert(`${title} ${description}`); } }; e aggiorniamo il nostro app js per includere la rotta dei messaggi // src/app js import react from 'react'; import { browserrouter as router, routes, route } from 'react router dom'; import messagespage from ' /pages/messagespage'; // import other pages function app() { return ( \<router> \<routes> \<route path="/messages" element={\<messagespage />} /> {/ other routes /} \</routes> \</router> ); } export default app; ora, implementiamo la funzionalità principale di messaggistica progetto back4gram trova qui il codice completo per un progetto campione di social network costruito con back4app passo 4 – creare il componente della pagina messaggi il nostro messagespage js componente sarà il nucleo del nostro sistema di messaggistica esso mostra un elenco di conversazioni consenti agli utenti di visualizzare e inviare messaggi mostra indicatori di digitazione abilita la ricerca di utenti per avviare nuove conversazioni costruiamo questo passo dopo passo // src/pages/messagespage js import react, { usestate, useeffect, useref } from 'react'; import { box, flex, input, button, vstack, text, avatar, heading, spinner, center, hstack, divider, } from '@chakra ui/react'; import { usenavigate } from 'react router dom'; import parse from 'parse/dist/parse min js'; import { toaster } from ' /components/ui/toaster'; function messagespage() { const \[message, setmessage] = usestate(''); const \[istyping, setistyping] = usestate(false); const \[isloading, setisloading] = usestate(true); const \[issending, setissending] = usestate(false); const \[currentuser, setcurrentuser] = usestate(null); const \[conversations, setconversations] = usestate(\[]); const \[activeconversation, setactiveconversation] = usestate(null); const \[messages, setmessages] = usestate(\[]); const \[users, setusers] = usestate(\[]); const \[searchquery, setsearchquery] = usestate(''); const \[showusersearch, setshowusersearch] = usestate(false); const \[processedmessageids, setprocessedmessageids] = usestate(new set()); const \[otherusertyping, setotherusertyping] = usestate(false); const messagesendref = useref(null); const livequerysubscription = useref(null); const typingstatussubscription = useref(null); const typingtimerref = useref(null); const navigate = usenavigate(); // check if user is authenticated useeffect(() => { const checkauth = async () => { try { console log('checking authentication '); const user = await parse user current(); if (!user) { console log('no user found, redirecting to login'); navigate('/login'); return; } console log('user authenticated ', user id, user get('username')); setcurrentuser(user); fetchconversations(user); } catch (error) { console error('error checking authentication ', error); navigate('/login'); } }; checkauth(); // clean up subscriptions when component unmounts return () => { if (livequerysubscription current) { livequerysubscription current unsubscribe(); } if (typingstatussubscription current) { typingstatussubscription current unsubscribe(); } if (typingtimerref current) { cleartimeout(typingtimerref current); } }; }, \[navigate]); // we'll implement the following functions next const fetchconversations = async (user) => { // coming up next }; const resetmessagestate = () => { // coming up next }; const fetchmessages = async (conversationid) => { // coming up next }; const setuplivequery = async (conversationid) => { // coming up next }; const setuptypingstatussubscription = async (conversationid) => { // coming up next }; const updatetypingstatus = async (istyping) => { // coming up next }; const sendmessage = async () => { // coming up next }; const searchusers = async (query) => { // coming up next }; const startconversation = async (userid) => { // coming up next }; // format time for messages const formatmessagetime = (date) => { return new date(date) tolocaletimestring(\[], { hour '2 digit', minute '2 digit' }); }; // format date for conversation list const formatconversationdate = (date) => { const now = new date(); const messagedate = new date(date); // if today, show time if (messagedate todatestring() === now\ todatestring()) { return messagedate tolocaletimestring(\[], { hour '2 digit', minute '2 digit' }); } // if this year, show month and day if (messagedate getfullyear() === now\ getfullyear()) { return messagedate tolocaledatestring(\[], { month 'short', day 'numeric' }); } // otherwise show full date return messagedate tolocaledatestring(); }; return ( \<flex h="100vh"> {/ we'll implement the ui next /} \</flex> ); } export default messagespage; ora implementiamo ciascuna delle funzioni necessarie per il nostro sistema di messaggistica progetto back4gram trova qui il codice completo per un progetto campione di social network realizzato con back4app passo 5 – recupero delle conversazioni utente per prima cosa, implementiamo la fetchconversations funzione che carica tutte le conversazioni per l'utente corrente const fetchconversations = async (user) => { setisloading(true); try { console log('fetching conversations for user ', user id); // query conversations where the current user is a participant const query = new parse query('conversation'); query equalto('participants', user); query include('participants'); query descending('updatedat'); const results = await query find(); console log('found conversations ', results length); // format conversations const formattedconversations = results map(conv => { const participants = conv get('participants'); // find the other participant (not the current user) const otherparticipant = participants find(p => p id !== user id); if (!otherparticipant) { console warn('could not find other participant in conversation ', conv id); return null; } return { id conv id, user { id otherparticipant id, username otherparticipant get('username'), avatar otherparticipant get('avatar') ? otherparticipant get('avatar') url() null }, lastmessage conv get('lastmessage') || '', updatedat conv get('updatedat') }; }) filter(boolean); // remove any null entries console log('formatted conversations ', formattedconversations); setconversations(formattedconversations); // if there are conversations, set the first one as active if (formattedconversations length > 0) { setactiveconversation(formattedconversations\[0]); fetchmessages(formattedconversations\[0] id); } } catch (error) { console error('error fetching conversations ', error); toaster create({ title 'error', description 'failed to load conversations', type 'error', }); } finally { setisloading(false); } }; questa funzione interroga la classe conversation per le conversazioni in cui l'utente corrente è un partecipante formatta i risultati per la visualizzazione nell'interfaccia utente imposta la prima conversazione come attiva se disponibile progetto back4gram trova qui il codice completo per un progetto campione di social network costruito con back4app passo 6 – gestire i messaggi e aggiornamenti in tempo reale ora implementiamo le funzioni per recuperare i messaggi e impostare aggiornamenti in tempo reale utilizzando live query const resetmessagestate = () => { console log('resetting message state'); setmessages(\[]); setprocessedmessageids(new set()); setotherusertyping(false); if (livequerysubscription current) { livequerysubscription current unsubscribe(); livequerysubscription current = null; console log('unsubscribed from live query in resetmessagestate'); } if (typingstatussubscription current) { typingstatussubscription current unsubscribe(); typingstatussubscription current = null; console log('unsubscribed from typing status subscription in resetmessagestate'); } }; const fetchmessages = async (conversationid) => { // reset message state to avoid any lingering messages or subscriptions resetmessagestate(); try { // query messages for this conversation const query = new parse query('message'); const conversation = new parse object('conversation'); conversation id = conversationid; query equalto('conversation', conversation); query include('sender'); query ascending('createdat'); const results = await query find(); // format messages const formattedmessages = results map(msg => ({ id msg id, text msg get('text'), sender { id msg get('sender') id, username msg get('sender') get('username') }, createdat msg get('createdat') })); // initialize the set of processed message ids const messageids = new set(formattedmessages map(msg => msg id)); // set state after processing all messages setmessages(formattedmessages); setprocessedmessageids(messageids); // set up live query subscription for new messages setuplivequery(conversationid); // set up typing status subscription setuptypingstatussubscription(conversationid); // scroll to bottom of messages settimeout(() => { if (messagesendref current) { messagesendref current scrollintoview({ behavior 'smooth' }); } }, 100); } catch (error) { console error('error fetching messages ', error); toaster create({ title 'error', description 'failed to load messages', type 'error', }); } }; ora, implementiamo l'iscrizione a live query per aggiornamenti in tempo reale dei messaggi const setuplivequery = async (conversationid) => { // capture the current user in a closure to avoid null reference later const captureduser = currentuser; // unsubscribe from previous subscription if exists if (livequerysubscription current) { livequerysubscription current unsubscribe(); console log('unsubscribed from previous live query'); } try { console log('setting up live query for conversation ', conversationid); // create a query that will be used for the subscription const query = new parse query('message'); const conversation = new parse object('conversation'); conversation id = conversationid; query equalto('conversation', conversation); query include('sender'); console log('created query for message class with conversation id ', conversationid); // subscribe to the query livequerysubscription current = await query subscribe(); console log('successfully subscribed to live query'); // handle connection open livequerysubscription current on('open', () => { console log('live query connection opened for conversation ', conversationid); }); // handle new messages livequerysubscription current on('create', (message) => { console log('new message received via live query ', message id); // check if we've already processed this message if (processedmessageids has(message id)) { console log('skipping duplicate message ', message id); return; } // format the new message const newmessage = { id message id, text message get('text'), sender { id message get('sender') id, username message get('sender') get('username') }, createdat message get('createdat') }; console log('formatted new message ', newmessage); // add the message id to the set of processed ids setprocessedmessageids(previds => { const newids = new set(previds); newids add(message id); return newids; }); // add the new message to the messages state setmessages(prevmessages => { // check if the message is already in the list (additional duplicate check) if (prevmessages some(msg => msg id === message id)) { console log('message already in list, not adding again ', message id); return prevmessages; } return \[ prevmessages, newmessage]; }); // use the captured user to avoid null reference if (captureduser && message get('sender') id !== captureduser id) { setconversations(prevconversations => { return prevconversations map(conv => { if (conv id === conversationid) { return { conv, lastmessage message get('text'), updatedat message get('createdat') }; } return conv; }); }); } else { // if user check fails, update without the check setconversations(prevconversations => { return prevconversations map(conv => { if (conv id === conversationid) { return { conv, lastmessage message get('text'), updatedat message get('createdat') }; } return conv; }); }); } // scroll to bottom of messages settimeout(() => { if (messagesendref current) { messagesendref current scrollintoview({ behavior 'smooth' }); } }, 100); }); // handle errors livequerysubscription current on('error', (error) => { console error('live query error ', error); }); // handle subscription close livequerysubscription current on('close', () => { console log('live query connection closed for conversation ', conversationid); }); } catch (error) { console error('error setting up live query ', error); toaster create({ title 'error', description 'failed to set up real time messaging', type 'error', }); } }; questa configurazione di live query crea un abbonamento ai nuovi messaggi nella conversazione attuale gestisce i messaggi in arrivo in tempo reale aggiorna l'interfaccia utente con i nuovi messaggi man mano che arrivano aggiorna l'elenco delle conversazioni con l'ultimo messaggio e il timestamp gestisce i messaggi duplicati (che possono verificarsi quando si invia e si riceve tramite live query) progetto back4gram trova qui il codice completo per un progetto campione di social network costruito con back4app passo 7 – implementazione degli indicatori di scrittura per migliorare l'esperienza dell'utente, aggiungiamo indicatori di scrittura in tempo reale const setuptypingstatussubscription = async (conversationid) => { // cancel previous subscription, if it exists if (typingstatussubscription current) { typingstatussubscription current unsubscribe(); console log('unsubscribed from previous typing status subscription'); } try { console log('setting up typing status subscription for conversation ', conversationid); // create a query for the typingstatus class const query = new parse query('typingstatus'); const conversation = new parse object('conversation'); conversation id = conversationid; // filter by conversation query equalto('conversation', conversation); // don't include the current user's status query notequalto('user', currentuser); // subscribe to the query typingstatussubscription current = await query subscribe(); console log('successfully subscribed to typing status'); // handle create events typingstatussubscription current on('create', (status) => { console log('typing status created ', status get('istyping')); setotherusertyping(status get('istyping')); }); // handle update events typingstatussubscription current on('update', (status) => { console log('typing status updated ', status get('istyping')); setotherusertyping(status get('istyping')); }); // handle errors typingstatussubscription current on('error', (error) => { console error('typing status subscription error ', error); }); } catch (error) { console error('error setting up typing status subscription ', error); } }; const updatetypingstatus = async (istyping) => { if (!activeconversation || !currentuser) return; try { // check if a typing status already exists for this user and conversation const query = new parse query('typingstatus'); const conversation = new parse object('conversation'); conversation id = activeconversation id; query equalto('user', currentuser); query equalto('conversation', conversation); const existingstatus = await query first(); if (existingstatus) { // update existing status existingstatus set('istyping', istyping); await existingstatus save(); } else { // create a new status const typingstatus = parse object extend('typingstatus'); const newstatus = new typingstatus(); newstatus set('user', currentuser); newstatus set('conversation', conversation); newstatus set('istyping', istyping); await newstatus save(); } } catch (error) { console error('error updating typing status ', error); } }; questa implementazione si iscrive agli aggiornamenti dello stato di digitazione da altri utenti nella conversazione aggiorna l'interfaccia utente quando qualcun altro sta digitando invia aggiornamenti dello stato di digitazione quando l'utente attuale sta digitando progetto back4gram trova qui il codice completo per un progetto campione di social network costruito con back4app passo 8 – inviare messaggi ora implementiamo la funzionalità di invio messaggi const sendmessage = async () => { if (!message trim() || !activeconversation) return; const messagetext = message trim(); // store the message text setissending(true); setmessage(''); // clear input immediately to prevent double sending setistyping(false); // clear typing status locally updatetypingstatus(false); // clear typing status on server // clear typing timer if (typingtimerref current) { cleartimeout(typingtimerref current); } try { // create message const message = parse object extend('message'); const newmessage = new message(); // set conversation pointer const conversation = new parse object('conversation'); conversation id = activeconversation id; newmessage set('conversation', conversation); newmessage set('sender', currentuser); newmessage set('text', messagetext); // save the message const savedmessage = await newmessage save(); console log('message saved ', savedmessage id); // add the message id to the set of processed ids to prevent duplication // when it comes back through live query setprocessedmessageids(previds => { const newids = new set(previds); newids add(savedmessage id); return newids; }); // check if this is a new conversation (no messages yet) // if it is, add the message to the ui immediately if (messages length === 0) { console log('first message in conversation, adding to ui immediately'); const formattedmessage = { id savedmessage id, text messagetext, sender { id currentuser id, username currentuser get('username') }, createdat new date() }; setmessages(\[formattedmessage]); // scroll to bottom of messages settimeout(() => { if (messagesendref current) { messagesendref current scrollintoview({ behavior 'smooth' }); } }, 100); } // update conversation's lastmessage const conversationobj = await new parse query('conversation') get(activeconversation id); conversationobj set('lastmessage', messagetext); await conversationobj save(); // update the conversation in the list setconversations(prevconversations => { return prevconversations map(conv => { if (conv id === activeconversation id) { return { conv, lastmessage messagetext, updatedat new date() }; } return conv; }); }); } catch (error) { console error('error sending message ', error); toaster create({ title 'error', description 'failed to send message', type 'error', }); // if there's an error, put the message back in the input setmessage(messagetext); } finally { setissending(false); } }; questa funzione crea e salva un nuovo messaggio nel database di back4app aggiorna l'ultimo messaggio e il timestamp della conversazione gestisce la prevenzione dei duplicati quando il messaggio torna tramite live query fornisce un feedback immediato al mittente progetto back4gram trova qui il codice completo per un progetto campione di social network costruito con back4app passo 9 – ricerca utente e avvio di nuove conversazioni implementiamo la possibilità di cercare utenti e avviare nuove conversazioni const searchusers = async (query) => { if (!query trim()) { setusers(\[]); return; } try { console log('searching for users with query ', query); // create a query for the user class const userquery = new parse query(parse user); // search for username containing the query string (case insensitive) userquery contains('username', query tolowercase()); // don't include the current user in results userquery notequalto('objectid', currentuser id); // limit to 10 results userquery limit(10); // execute the query const results = await userquery find(); console log('user search results ', results length); // format users const formattedusers = results map(user => ({ id user id, username user get('username'), avatar user get('avatar') ? user get('avatar') url() null })); console log('formatted users ', formattedusers); setusers(formattedusers); } catch (error) { console error('error searching users ', error); toaster create({ title 'error', description 'failed to search users', type 'error', }); } }; const startconversation = async (userid) => { console log('starting conversation with user id ', userid); try { // check if a conversation already exists with this user const query = new parse query('conversation'); // create pointers to both users const currentuserpointer = parse user current(); const otheruserpointer = new parse user(); otheruserpointer id = userid; // find conversations where both users are participants query containsall('participants', \[currentuserpointer, otheruserpointer]); const existingconv = await query first(); if (existingconv) { console log('found existing conversation ', existingconv id); // get the other user object const userquery = new parse query(parse user); const otheruser = await userquery get(userid); // format the conversation const conversation = { id existingconv id, user { id otheruser id, username otheruser get('username'), avatar otheruser get('avatar') ? otheruser get('avatar') url() null }, lastmessage existingconv get('lastmessage') || '', updatedat existingconv get('updatedat') }; // set as active conversation setactiveconversation(conversation); // fetch messages and set up live query await fetchmessages(conversation id); } else { console log('creating new conversation with user ', userid); // get the other user object const userquery = new parse query(parse user); const otheruser = await userquery get(userid); // create a new conversation const conversation = parse object extend('conversation'); const newconversation = new conversation(); // set participants newconversation set('participants', \[currentuserpointer, otheruserpointer]); newconversation set('lastmessage', ''); // save the conversation const savedconv = await newconversation save(); console log('new conversation created ', savedconv id); // format the conversation const conversation = { id savedconv id, user { id otheruser id, username otheruser get('username'), avatar otheruser get('avatar') ? otheruser get('avatar') url() null }, lastmessage '', updatedat savedconv get('updatedat') }; // add to conversations list setconversations(prev => \[conversation, prev]); // set as active conversation and reset message state setactiveconversation(conversation); resetmessagestate(); // set up live query for the new conversation await setuplivequery(savedconv id); // set up typing status subscription await setuptypingstatussubscription(savedconv id); // reset search setshowusersearch(false); setsearchquery(''); setusers(\[]); } } catch (error) { console error('error starting conversation ', error); toaster create({ title 'error', description 'failed to start conversation', type 'error', }); } }; questo codice fornisce due funzioni importanti per il nostro sistema di messaggistica ricerca utente il searchusers funzione consente agli utenti di trovare altri utenti per nome utente interroga la classe utente in back4app, esclude l'utente attuale dai risultati e formatta i dati per la visualizzazione creazione conversazione la startconversation funzione gestisce sia la ricerca di conversazioni esistenti che la creazione di nuove essa controlla se esiste già una conversazione tra gli utenti se esiste, carica quella conversazione e i suoi messaggi se non esiste una conversazione, ne crea una nuova imposta le sottoscrizioni live query per nuovi messaggi e stato di digitazione con queste funzioni implementate, gli utenti possono facilmente trovare altri utenti e avviare conversazioni con loro, il che è essenziale per un sistema di messaggistica di rete sociale progetto back4gram trova qui il codice completo per un progetto campione di rete sociale costruito con back4app passo 10 – costruzione dell'interfaccia utente della chat ora che abbiamo implementato tutte le funzionalità, creiamo l'interfaccia utente per il nostro sistema di messaggistica l'interfaccia consisterà in una barra laterale che elenca tutte le conversazioni un'interfaccia di ricerca per trovare utenti una finestra di chat che mostra i messaggi un campo di input per i messaggi con supporto per l'indicatore di digitazione ecco il jsx per il nostro componente messagespage return ( \<flex h="100vh"> {/ chat list sidebar /} \<box w="300px" borderrightwidth="1px" bordercolor="gray 600" p={4} bg="gray 800"> \<heading size="md" mb={4}> messages \</heading> \<flex mb={4}> \<input placeholder="search users " value={searchquery} onchange={(e) => { const value = e target value; setsearchquery(value); if (value trim() length > 0) { searchusers(value); } else { setusers(\[]); } }} mr={2} /> \<button onclick={() => { setshowusersearch(!showusersearch); if (!showusersearch) { setsearchquery(''); setusers(\[]); } }} \> {showusersearch ? 'cancel' 'new'} \</button> \</flex> {isloading ? ( \<center py={10}> \<spinner size="lg" /> \</center> ) showusersearch ? ( // user search results \<vstack align="stretch" spacing={2}> {users length > 0 ? ( users map(user => ( \<box key={user id} p={3} borderradius="md" hover={{ bg "gray 700" }} cursor="pointer" onclick={() => startconversation(user id)} \> \<hstack> \<avatar size="sm" name={user username} src={user avatar} /> \<text>{user username}\</text> \</hstack> \</box> )) ) searchquery ? ( \<text color="gray 400" textalign="center">no users found\</text> ) ( \<text color="gray 400" textalign="center">search for users to message\</text> )} \</vstack> ) ( // conversations list \<vstack align="stretch" spacing={0}> {conversations length > 0 ? ( conversations map(conv => ( \<box key={conv id} p={3} borderradius="md" bg={activeconversation? id === conv id ? "gray 700" "transparent"} hover={{ bg "gray 700" }} cursor="pointer" onclick={() => { if (activeconversation? id !== conv id) { setactiveconversation(conv); fetchmessages(conv id); } }} \> \<hstack> \<avatar size="sm" name={conv user username} src={conv user avatar} /> \<box flex="1" overflow="hidden"> \<flex justify="space between" align="center"> \<text fontweight="bold" nooflines={1}>{conv user username}\</text> \<text fontsize="xs" color="gray 400"> {formatconversationdate(conv updatedat)} \</text> \</flex> \<text fontsize="sm" color="gray 400" nooflines={1}> {conv lastmessage || 'no messages yet'} \</text> \</box> \</hstack> \</box> )) ) ( \<text color="gray 400" textalign="center" mt={8}> no conversations yet start a new one! \</text> )} \</vstack> )} \</box> {/ chat window /} \<box flex={1} p={0} display="flex" flexdirection="column" bg="gray 900"> {activeconversation ? ( <> {/ chat header /} \<flex align="center" p={4} borderbottomwidth="1px" bordercolor="gray 700"> \<avatar size="sm" mr={2} name={activeconversation user username} src={activeconversation user avatar} /> \<text fontweight="bold">{activeconversation user username}\</text> {otherusertyping && ( \<text ml={2} color="gray 400" fontsize="sm"> is typing \</text> )} \</flex> {/ messages /} \<box flex={1} p={4} overflowy="auto" display="flex" flexdirection="column" \> {messages length > 0 ? ( messages map((msg) => ( \<box key={msg id} alignself={msg sender id === currentuser id ? "flex end" "flex start"} bg={msg sender id === currentuser id ? "blue 500" "gray 700"} color={msg sender id === currentuser id ? "white" "white"} p={3} borderradius="lg" maxw="70%" mb={2} \> \<text>{msg text}\</text> \<text fontsize="xs" color={msg sender id === currentuser id ? "blue 100" "gray 400"} textalign="right" mt={1}> {formatmessagetime(msg createdat)} \</text> \</box> )) ) ( \<center flex={1}> \<text color="gray 400"> no messages yet say hello! \</text> \</center> )} \<div ref={messagesendref} /> \</box> {/ message input /} \<flex p={4} bordertopwidth="1px" bordercolor="gray 700"> \<input placeholder="type a message " value={message} onchange={(e) => { setmessage(e target value); // set typing status to true locally setistyping(true); // update typing status on server updatetypingstatus(true); // clear any existing timer if (typingtimerref current) { cleartimeout(typingtimerref current); } // set a new timer to turn off typing status after 2 seconds of inactivity typingtimerref current = settimeout(() => { setistyping(false); updatetypingstatus(false); }, 2000); }} mr={2} onkeypress={(e) => { if (e key === 'enter' && !e shiftkey) { e preventdefault(); sendmessage(); // also clear typing status when sending a message setistyping(false); updatetypingstatus(false); if (typingtimerref current) { cleartimeout(typingtimerref current); } } }} /> \<button colorscheme="blue" onclick={sendmessage} isloading={issending} disabled={!message trim() || issending} \> send \</button> \</flex> \</> ) ( \<center flex={1}> \<vstack> \<text fontsize="xl" fontweight="bold">welcome to messages\</text> \<text color="gray 400"> select a conversation or start a new one \</text> \</vstack> \</center> )} \</box> \</flex> ); analizziamo l'interfaccia utente nei suoi componenti principali barra laterale delle conversazioni la barra laterale sinistra mostra un elenco di tutte le conversazioni ogni conversazione mostra il nome dell'altro utente, l'avatar e l'ultimo messaggio gli utenti possono cliccare su qualsiasi conversazione per visualizzare e continuare la chat una barra di ricerca in alto consente di trovare altri utenti per avviare nuove conversazioni visualizzazione dei messaggi l'area principale visualizza i messaggi per la conversazione attiva i messaggi dell'utente corrente appaiono a destra con uno sfondo blu i messaggi degli altri utenti appaiono a sinistra con uno sfondo grigio ogni messaggio mostra il testo del messaggio e l'ora in cui è stato inviato l'elenco dei messaggi scorre automaticamente verso il basso quando arrivano nuovi messaggi inserimento messaggi in fondo c'è un'area di input per digitare e inviare messaggi gli utenti possono premere invio per inviare messaggi un pulsante invia consente anche di inviare messaggi digitare nell'area di input attiva automaticamente gli indicatori di digitazione l'input è disabilitato fino a quando non viene selezionata una conversazione indicatori di digitazione quando l'altro utente sta digitando, appare un indicatore "sta digitando " l'indicatore viene aggiornato in tempo reale utilizzando l'abbonamento typingstatus live query questa interfaccia utente fornisce un'interfaccia pulita e intuitiva per la messaggistica, simile a quella che gli utenti si aspetterebbero dalle popolari applicazioni di messaggistica progetto back4gram trova qui il codice completo per un progetto campione di social network realizzato con back4app passo 11 – testare le connessioni live query per garantire che le nostre connessioni live query funzionino correttamente, aggiungiamo una funzione di test che verifica la connessione quando il componente viene montato // add this at the beginning of the component to test live query connection useeffect(() => { // test live query connection const testlivequery = async () => { try { console log('testing live query connection '); console log('live query url ', parse livequeryserverurl); const query = new parse query('message'); console log('created test query for message class'); const subscription = await query subscribe(); console log('live query subscription successful!'); subscription on('open', () => { console log('live query connection opened successfully'); }); subscription on('create', (object) => { console log('live query create event received ', object id); }); subscription on('error', (error) => { console error('live query error ', error); }); // unsubscribe after a few seconds to test settimeout(() => { subscription unsubscribe(); console log('unsubscribed from test live query'); }, 10000); } catch (error) { console error('error testing live query ', error); } }; testlivequery(); }, \[]); questa funzione di test tenta di creare un'iscrizione alla classe message imposta gestori di eventi per eventi di connessione registra i risultati di ogni passaggio annulla automaticamente l'iscrizione dopo 10 secondi osservando i log della console, puoi verificare che l'url della live query è configurato correttamente la connessione può essere stabilita gli eventi vengono ricevuti correttamente se riscontri problemi con la connessione live query, controlla quanto segue back4app live query è abilitato per la tua app l'url della live query è impostato correttamente nella tua inizializzazione di parse la classe message è stata aggiunta all'elenco delle classi live query in back4app la tua rete consente connessioni websocket progetto back4gram trova qui il codice completo per un progetto campione di social network costruito con back4app passo 12 – comprendere come back4app abilita la messaggistica in tempo reale ora che abbiamo costruito un sistema di messaggistica completo, diamo un'occhiata più approfondita a come le funzionalità di back4app rendono possibile la messaggistica in tempo reale infrastruttura di query in tempo reale la funzionalità live query di back4app è costruita su websocket, che forniscono una connessione persistente tra il client e il server questo è fondamentalmente diverso dal tradizionale modello di richiesta/riposta http api rest tradizionale il client effettua una richiesta → il server risponde → la connessione si chiude websocket/live query il client stabilisce una connessione persistente → il server può inviare aggiornamenti in qualsiasi momento questa connessione persistente è ciò che abilita le capacità in tempo reale nel nostro sistema di messaggistica quando viene creato un nuovo messaggio, back4app invia automaticamente quell'aggiornamento a tutti i client iscritti senza che debbano interrogare il server il modello di abbonamento di parse server il modello di abbonamento in parse server è basato su query questo significa ti iscrivi a una query specifica (ad esempio, "tutti i messaggi nella conversazione x") ricevi aggiornamenti solo per gli oggetti che corrispondono a quella query puoi ricevere diversi tipi di eventi ( crea , aggiorna , elimina , ecc ) questo approccio basato su query è estremamente potente perché consente abbonamenti precisi nel nostro sistema di messaggistica, ci stiamo iscrivendo solo ai messaggi per la conversazione attiva, il che è molto più efficiente rispetto all'iscrizione a tutti i messaggi considerazioni sul database quando si utilizza live query con back4app, ci sono alcune importanti considerazioni sul database indicizzazione assicurati che i campi utilizzati nelle query di abbonamento siano indicizzati per migliori prestazioni dimensione dei dati mantieni gli oggetti piccoli per ridurre la dimensione del payload e migliorare le prestazioni scalabilità le connessioni live query utilizzano risorse, quindi considera questo quando scaldi la tua applicazione per prestazioni ottimali, considera creare indici su conversazione campi nella classe messaggio utilizzare puntatori piuttosto che incorporare oggetti di grandi dimensioni gestire il ciclo di vita degli abbonamenti per evitare perdite di memoria considerazioni sulla sicurezza quando si implementa la messaggistica in tempo reale, la sicurezza è cruciale acls e clp usa le liste di controllo degli accessi e i permessi a livello di classe per proteggere i tuoi dati autenticazione della connessione solo gli utenti autenticati dovrebbero essere in grado di stabilire connessioni live query validazione dei dati valida il contenuto dei messaggi sul lato server utilizzando il cloud code limitazione della velocità implementa la limitazione della velocità per prevenire abusi ad esempio, potresti voler utilizzare il cloud code per convalidare e sanificare i messaggi prima che vengano salvati // in cloud code parse cloud beforesave("message", async (request) => { const message = request object; const text = message get("text"); // check if message is empty if (!text || text trim() length === 0) { throw new error("message cannot be empty"); } // sanitize message text message set("text", sanitizehtml(text)); // rate limiting check if user is sending too many messages const sender = message get("sender"); const query = new parse query("message"); query equalto("sender", sender); query greaterthan("createdat", new date(new date() 60000)); // last minute const count = await query count({usemasterkey true}); if (count > 10) { throw new error("you are sending messages too quickly please slow down "); } }); progetto back4gram trova qui il codice completo per un progetto campione di social network costruito con back4app passo 13 – testare e ottimizzare il sistema di messaggistica per garantire che il tuo sistema di messaggistica funzioni correttamente, dovresti testarlo a fondo test di funzionalità di base invio messaggi verifica che i messaggi possano essere inviati e ricevuti creazione di conversazioni testa la creazione di nuove conversazioni ricerca utenti conferma che gli utenti possano essere trovati e che le conversazioni possano essere avviate aggiornamenti ui controlla che gli indicatori di digitazione, le liste dei messaggi e le liste delle conversazioni si aggiornino correttamente test multi utente per testare correttamente un sistema di messaggistica, è necessario testare con più utenti apri due finestre o schede del browser accedi come utenti diversi in ciascuna inizia una conversazione tra di loro invia messaggi avanti e indietro testa gli indicatori di digitazione e gli aggiornamenti in tempo reale ottimizzazione delle prestazioni per applicazioni più grandi, considera queste ottimizzazioni paginazione carica i messaggi a gruppi piuttosto che tutti in una volta gestione delle connessioni stabilire connessioni live query solo per conversazioni attive query efficienti usa query mirate e includi solo i campi necessari caching implementa il caching lato client per i dati frequentemente accessibili ecco un esempio di implementazione della paginazione per il caricamento dei messaggi const fetchmessages = async (conversationid, limit = 20, skip = 0) => { try { const query = new parse query('message'); const conversation = new parse object('conversation'); conversation id = conversationid; query equalto('conversation', conversation); query include('sender'); query descending('createdat'); query limit(limit); query skip(skip); const results = await query find(); // process results return { messages formattedmessages, hasmore results length === limit }; } catch (error) { console error('error fetching messages ', error); throw error; } }; gestione degli errori e recupero una gestione robusta degli errori è cruciale per le applicazioni in tempo reale guasti di connessione implementare la logica di riconnessione per i guasti websocket guasti nei messaggi gestire l'invio di messaggi non riusciti e fornire opzioni di ripetizione recupero dello stato recuperare lo stato dell'applicazione dopo interruzioni della connessione ad esempio, potresti aggiungere il monitoraggio della connessione // monitor live query connection status parse livequery on('open', () => { console log('live query connection established'); // re establish subscriptions if needed }); parse livequery on('close', () => { console log('live query connection closed'); // show connection status to user }); parse livequery on('error', (error) => { console error('live query error ', error); // handle error, potentially reconnect }); conclusione in questo tutorial, hai costruito un sistema di messaggistica in tempo reale completo per il tuo social network utilizzando back4app hai implementato messaggistica tra utenti creato un sistema in cui gli utenti possono trovarsi e messaggiarsi aggiornamenti in tempo reale utilizzato live query di back4app per consegnare messaggi istantaneamente indicatori di digitazione migliorata l'esperienza utente con notifiche di digitazione in tempo reale gestione delle conversazioni costruita un'interfaccia per visualizzare e gestire più conversazioni la funzione live query di back4app ha fornito l'infrastruttura in tempo reale necessaria per rendere tutto ciò possibile senza dover gestire server websocket complessi o preoccupazioni di scalabilità sfruttando il modello di sottoscrizione integrato di parse server, sei stato in grado di creare un sistema di messaggistica reattivo ed efficiente il sistema di messaggistica che hai costruito fornisce una solida base che puoi migliorare con funzionalità aggiuntive prossimi passi ricevute di lettura implementare il tracciamento dello stato di lettura dei messaggi conversazioni di gruppo estendere il sistema per supportare più partecipanti condivisione di media aggiungere supporto per immagini, video e altri allegati reazioni ai messaggi consentire agli utenti di reagire ai messaggi con emoji cancellazione dei messaggi implementare la possibilità di eliminare o modificare i messaggi ricerca avanzata aggiungere capacità di ricerca all'interno delle conversazioni per il codice completo dell'applicazione di social network back4gram, inclusa la sua sistema di messaggistica, puoi controllare il repository github https //github com/templates back4app/back4gram la combinazione di database, api, funzioni cloud, archiviazione file, gestione utenti e capacità in tempo reale di back4app lo rende un'ottima scelta per costruire applicazioni di social network ricche di funzionalità utilizzando back4app, puoi concentrarti sulla creazione di una grande esperienza utente piuttosto che sulla gestione di un'infrastruttura backend complessa