Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to construct an instant message conversation in json

2025-01-14 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article focuses on "how to build an instant messaging conversation in json". Interested friends may wish to take a look. The method introduced in this paper is simple, fast and practical. Let's let the editor take you to learn "how to build an instant message conversation in json".

In our instant messaging application, messages are represented as a stack of conversations between two participants. If you want to start a conversation, you should provide the app with the user you want to talk to, and when the conversation is created (if it doesn't already exist), you can send a message to the conversation.

On the front end, we may want to display a list of recent conversations. The last message of the conversation and the name and avatar of another participant are displayed here.

In this post, we will write some endpoint endpoint to accomplish tasks such as "create a conversation", "get a conversation list", and "find a single conversation".

First, add the following route to the main function main ().

Router.HandleFunc ("POST", "/ api/conversations", requireJSON (guard (createConversation) router.HandleFunc ("GET", "/ api/conversations", guard (getConversations)) router.HandleFunc ("GET", "/ api/conversations/:conversationID", guard (getConversation))

All three endpoints need to be authenticated, so we will use guard () middleware. We will also build a new middleware to check whether the request content is in JSON format.

JSON request check middleware func requireJSON (handler http.HandlerFunc) http.HandlerFunc {return func (w http.ResponseWriter, r * http.Request) {if ct: = r.Header.Get ("Content-Type");! strings.HasPrefix (ct, "application/json") {http.Error (w, "Content type of application/json required", http.StatusUnsupportedMediaType) return} handler (w, r)}}

If the request request is not in JSON format, it returns a 415 Unsupported Media Type (unsupported media type) error.

Create a dialogue type Conversation struct {ID string `json: "id" `OtherParticipant * User `json: "otherParticipant" `LastMessage * Message `json: "lastMessage" `HasUnreadMessages bool `json: "hasUnreadMessages" `}

As in the code above, the conversation maintains a reference to another participant and the last message, as well as a field of type bool to tell if there is an unread message.

Type Message struct {ID string `json: "id" `Content string `json: "content" `UserID string `json: "-" `ConversationID string `json: "conversationID,omitempty" `CreatedAt time.Time `json: "createdAt" `Mine bool `json: "mine" `ReceiverID string `json: "-" `}

We will cover the content related to messages in the next article, but since we also need to use it here, we first define the Message structure. Most of these fields are consistent with the database table. We need to use Mine to determine whether the message belongs to the currently authenticated user. Once real-time capabilities are added, ReceiverID can help us filter messages.

Next, let's write a HTTP handler. Although it is a little long, there is nothing to be afraid of.

Func createConversation (w http.ResponseWriter, r * http.Request) {var input struct {Username string `json: "username" `} defer r.Body.Close () if err: = json.NewDecoder (r.Body) .Decode (& input) Err! = nil {http.Error (w, err.Error (), http.StatusBadRequest) return} input.Username = strings.TrimSpace (input.Username) if input.Username = "" {respond (w, Errors {map [string] string {"username": "Username required",}}) Http.StatusUnprocessableEntity) return} ctx: = r.Context () authUserID: = ctx.Value (keyAuthUserID). (string) tx, err: = db.BeginTx (ctx, nil) if err! = nil {respondError (w, fmt.Errorf ("could not begin tx:% v", err)) return} defer tx.Rollback () var otherParticipant User if err: = tx.QueryRowContext (ctx, `SELECT id) Avatar_url FROM users WHERE username = $1`, input.Username) .Scan (& otherParticipant.ID, & otherParticipant.AvatarURL,) Err = = sql.ErrNoRows {http.Error (w, "User not found", http.StatusNotFound) return} else if err! = nil {respondError (w, fmt.Errorf ("could not query other participant:% v", err)) return} otherParticipant.Username = input.Username if otherParticipant.ID = = authUserID {http.Error (w, "Try start a conversation with someone else") Http.StatusForbidden) return} var conversationID string if err: = tx.QueryRowContext (ctx, `SELECT conversation_id FROM participants WHERE user_id = $1 INTERSECT SELECT conversation_id FROM participants WHERE user_id = $2`, authUserID, otherParticipant.ID) .Scan (& conversationID) Err! = nil & & err! = sql.ErrNoRows {respondError (w, fmt.Errorf ("could not query common conversation id:% v", err)) return} else if err = = nil {http.Redirect (w, r, "/ api/conversations/" + conversationID, http.StatusFound) return} var conversation Conversation if err = tx.QueryRowContext (ctx, `INSERT INTO conversations DEFAULT VALUES RETURNING id `). Scan (& conversation.ID) Err! = nil {respondError (w, fmt.Errorf ("could not insert conversation:% v", err) return} if _, err = tx.ExecContext (ctx, `INSERT INTO participants (user_id, conversation_id) VALUES ($1, $2), ($3, $2) `, authUserID, conversation.ID, otherParticipant.ID) Err! = nil {respondError (w, fmt.Errorf ("could not insert participants:% v", err)) return} if err = tx.Commit (); err! = nil {respondError (w, fmt.Errorf ("could not commit tx to create conversation:% v", err)) return} conversation.OtherParticipant = & otherParticipant respond (w, conversation, http.StatusCreated)}

At this endpoint, you send a POST request to / api/conversations, and the JSON body of the request contains the user name of the user you want to talk to.

Therefore, you first need to parse the request subject into a structure that contains the user name. Then, verify that the user name cannot be empty.

Type Errors struct {Errors map [string] string `json: "errors" `}

This is the structure of the error message, Errors, which is just a mapping. If you enter an empty user name, you will get a JSON with a 422 Unprocessable Entity error message.

{"errors": {"username": "Username required"}}

Then we begin to execute the SQL transaction. All you receive is the user name, but in fact, we need to know the actual user ID. Therefore, the first item of the transaction is to query the ID and avatar of another participant. If the user is not found, we will return a 404 Not Found error. In addition, if the user found happens to be the same as the currently authenticated user, we should return a 403 Forbidden (deny processing) error. This is because the conversation should only be initiated between two different users, not the same one.

Then we try to find the conversation shared by the two users, so we need to use the INTERSECT statement. If it exists, simply redirect to the conversation and return it via / api/conversations/ {conversationID}.

If a shared conversation is not found, we need to create a new conversation and add the specified two participants. Finally, we COMMIT the transaction and respond with the newly created conversation.

Get conversation list

The endpoint / api/conversations will get all conversations for the currently authenticated user.

Func getConversations (w http.ResponseWriter, r * http.Request) {ctx: = r.Context () authUserID: = ctx.Value (keyAuthUserID). (string) rows, err: = db.QueryContext (ctx, `SELECT conversations.id, auth_user.messages_read_at < messages.created_at AS has_unread_messages, messages.id, messages.content, messages.created_at Messages.user_id = $1 AS mine, other_users.id, other_users.username Other_users.avatar_url FROM conversations INNER JOIN messages ON conversations.last_message_id = messages.id INNER JOIN participants other_participants ON other_participants.conversation_id = conversations.id AND other_participants.user_id! = $1 INNER JOIN users other_users ON other_participants.user_id = other_users.id INNER JOIN participants auth_user ON auth_ User.conversation_id = conversations.id AND auth_user.user_id = $1 ORDER BY messages.created_at DESC ` AuthUserID) if err! = nil {respondError (w, fmt.Errorf ("could not query conversations:% v", err)) return} defer rows.Close () conversations: = make ([] Conversation, 0) for rows.Next () {var conversation Conversation var lastMessage Message var otherParticipant User if err = rows.Scan (& conversation.ID, & conversation.HasUnreadMessages) & lastMessage.ID, & lastMessage.Content, & lastMessage.CreatedAt, & lastMessage.Mine, & otherParticipant.ID, & otherParticipant.Username, & otherParticipant.AvatarURL,) Err! = nil {respondError (w, fmt.Errorf ("could not scan conversation:% v", err)) return} conversation.LastMessage = & lastMessage conversation.OtherParticipant = & otherParticipant conversations = append (conversations, conversation)} if err = rows.Err () Err! = nil {respondError (w, fmt.Errorf ("could not iterate over conversations:% v", err)) return} respond (w, conversations, http.StatusOK)}

The handler only queries the database. It queries the conversation table through some joins. First, get the last message from the message table. Then, according to the condition that the ID is different from the currently authenticated user, another participant in the conversation is found from the participant table. Then join to the user table to get the user name and avatar of the user. Finally, join the participant table again and find another user participating in the conversation from the table on the opposite condition, which is actually the current authenticated user. We compare the messages_read_at and created_at fields in the message to determine if there is an unread message in the conversation. We then use the user_id field to determine whether the message belongs to "I" (which refers to the currently authenticated user).

Note that this query process assumes that only two users are involved in the conversation, and it applies only in this case. In addition, the design is not very suitable for situations where you need to display the number of unread messages. If you need to show the number of unread messages, I think you can add a unread_messages_count INT field to the participants table, increment it each time you create a new message, and reset it if the user has read it.

The next step is to iterate through each record, create a conversation slice slice of conversations by scanning each existing conversation and respond at the end.

Find a single conversation

The endpoint / api/conversations/ {conversationID} responds to a single conversation based on the ID.

Func getConversation (w http.ResponseWriter, r * http.Request) {ctx: = r.Context () authUserID: = ctx.Value (keyAuthUserID). (string) conversationID: = way.Param (ctx, "conversationID") var conversation Conversation var otherParticipant User if err: = db.QueryRowContext (ctx, `SELECT IFNULL (auth_user.messages_read_at < messages.created_at, false) AS has_unread_messages, other_users.id Other_users.username Other_users.avatar_url FROM conversations LEFT JOIN messages ON conversations.last_message_id = messages.id INNER JOIN participants other_participants ON other_participants.conversation_id = conversations.id AND other_participants.user_id! = $1 INNER JOIN users other_users ON other_participants.user_id = other_users.id INNER JOIN participants auth_user ON auth_ User.conversation_id = conversations.id AND auth_user.user_id = $1 WHERE conversations.id = $2` AuthUserID, conversationID) .Scan (& conversation.HasUnreadMessages, & otherParticipant.ID, & otherParticipant.Username, & otherParticipant.AvatarURL,) Err = = sql.ErrNoRows {http.Error (w, "Conversation not found", http.StatusNotFound) return} else if err! = nil {respondError (w, fmt.Errorf ("could not query conversation:% v", err)) return} conversation.ID = conversationID conversation.OtherParticipant = & otherParticipant respond (w, conversation, http.StatusOK)}

The query here is a bit similar to the previous one. Although we don't care about the display of the last message and therefore ignore some fields related to it, we need to use this message to determine whether there is an unread message in the conversation. At this point, we use LEFT JOIN instead of INNER JOIN because the last_message_id field is NULLABLE (which can be empty); in other cases, we can't get any records. For the same reason, we used the IFNULL statement in the has_unread_messages comparison. Finally, we filter by ID.

If the query does not return any records, our response will return a 404 Not Found error, otherwise the response will return 200 OK and the conversation found.

At this point, I believe you have a deeper understanding of "how to build an instant messaging conversation in json". You might as well do it in practice. Here is the website, more related content can enter the relevant channels to inquire, follow us, continue to learn!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report