返回顶部
首页 > 资讯 > 精选 >json中怎么构建一个即时消息对话
  • 497
分享到

json中怎么构建一个即时消息对话

2023-06-16 14:06:48 497人浏览 薄情痞子
摘要

本篇内容主要讲解“JSON中怎么构建一个即时消息对话”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“json中怎么构建一个即时消息对话”吧!在我们的即时消息应用中,消息表现为两个参与者对话的堆叠。

本篇内容主要讲解“JSON中怎么构建一个即时消息对话”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习json中怎么构建一个即时消息对话”吧!

在我们的即时消息应用中,消息表现为两个参与者对话的堆叠。如果你想要开始一场对话,就应该向应用提供你想要交谈的用户,而当对话创建后(如果该对话此前并不存在),就可以向该对话发送消息。

前端而言,我们可能想要显示一份近期对话列表。并在此处显示对话的最后一条消息以及另一个参与者的姓名和头像。

在这篇帖子中,我们将会编写一些端点endpoint来完成像“创建对话”、“获取对话列表”以及“找到单个对话”这样的任务。

首先,要在主函数 main() 中添加下面的路由。

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

这三个端点都需要进行身份验证,所以我们将会使用 guard() 中间件。我们也会构建一个新的中间件,用于检查请求内容是否为 JSON 格式。

JSON 请求检查中间件

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)    }}

如果请求request不是 JSON 格式,那么它会返回 415 Unsupported Media Type(不支持的媒体类型)错误。

创建对话

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

就像上面的代码那样,对话中保持对另一个参与者和最后一条消息的引用,还有一个 bool 类型的字段,用来告知是否有未读消息。

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:"-"`}

我们会在下一篇文章介绍与消息相关的内容,但由于我们这里也需要用到它,所以先定义了 Message 结构体。其中大多数字段与数据库表一致。我们需要使用 Mine 来断定消息是否属于当前已验证用户所有。一旦加入实时功能,ReceiverID 可以帮助我们过滤消息。

接下来让我们编写 HTTP 处理程序。尽管它有些长,但也没什么好怕的。

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)}

在此端点,你会向 /api/conversations 发送 POST 请求,请求的 JSON 主体中包含要对话的用户的用户名。

因此,首先需要将请求主体解析成包含用户名的结构。然后,校验用户名不能为空。

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

这是错误消息的结构体 Errors,它仅仅是一个映射。如果输入空用户名,你就会得到一段带有 422 Unprocessable Entity(无法处理的实体)错误消息的 JSON 。

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

然后,我们开始执行 SQL 事务。收到的仅仅是用户名,但事实上,我们需要知道实际的用户 ID 。因此,事务的第一项内容是查询另一个参与者的 ID 和头像。如果找不到该用户,我们将会返回 404 Not Found(未找到) 错误。另外,如果找到的用户恰好和“当前已验证用户”相同,我们应该返回 403 Forbidden(拒绝处理)错误。这是由于对话只应当在两个不同的用户之间发起,而不能是同一个。

然后,我们试图找到这两个用户所共有的对话,所以需要使用 INTERSECT 语句。如果存在,只需要通过 /api/conversations/{conversationID} 重定向到该对话并将其返回。

如果未找到共有的对话,我们需要创建一个新的对话并添加指定的两个参与者。最后,我们 COMMIT 该事务并使用新创建的对话进行响应。

获取对话列表

端点 /api/conversations 将获取当前已验证用户的所有对话。

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)}

该处理程序仅对数据库进行查询。它通过一些联接来查询对话表&hellip;&hellip;首先,从消息表中获取最后一条消息。然后依据“ID  与当前已验证用户不同”的条件,从参与者表找到对话的另一个参与者。然后联接到用户表以获取该用户的用户名和头像。最后,再次联接参与者表,并以相反的条件从该表中找出参与对话的另一个用户,其实就是当前已验证用户。我们会对比消息中的  messages_read_atcreated_at 两个字段,以确定对话中是否存在未读消息。然后,我们通过 user_id 字段来判定该消息是否属于“我”(指当前已验证用户)。

注意,此查询过程假定对话中只有两个用户参与,它也仅仅适用于这种情况。另外,该设计也不很适用于需要显示未读消息数量的情况。如果需要显示未读消息的数量,我认为可以在 participants 表上添加一个unread_messages_count INT 字段,并在每次创建新消息的时候递增它,如果用户已读则重置该字段。

接下来需要遍历每一条记录,通过扫描每一个存在的对话来建立一个对话切片slice of conversations并在最后进行响应。

找到单个对话

端点 /api/conversations/{conversationID} 会根据 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)}

这里的查询与之前有点类似。尽管我们并不关心最后一条消息的显示问题,并因此忽略了与之相关的一些字段,但是我们需要根据这条消息来判断对话中是否存在未读消息。此时,我们使用 LEFT JOIN 来代替 INNER JOIN,因为 last_message_id 字段是 NULLABLE(可以为空)的;而其他情况下,我们无法得到任何记录。基于同样的理由,我们在 has_unread_messages 的比较中使用了 IFNULL 语句。最后,我们按 ID 进行过滤。

如果查询没有返回任何记录,我们的响应会返回 404 Not Found 错误,否则响应将会返回 200 OK 以及找到的对话。

到此,相信大家对“json中怎么构建一个即时消息对话”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

--结束END--

本文标题: json中怎么构建一个即时消息对话

本文链接: https://lsjlt.com/news/283837.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • json中怎么构建一个即时消息对话
    本篇内容主要讲解“json中怎么构建一个即时消息对话”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“json中怎么构建一个即时消息对话”吧!在我们的即时消息应用中,消息表现为两个参与者对话的堆叠。...
    99+
    2023-06-16
  • 构建一个即时消息应用之什么是实时消息
    这篇文章主要讲解了“构建一个即时消息应用之什么是实时消息”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“构建一个即时消息应用之什么是实时消息”吧!消息户端在 HTTP 部分之前,让我们先编写一...
    99+
    2023-06-16
  • 构建一个即时消息应用之如何创建消息
    这篇文章主要介绍“构建一个即时消息应用之如何创建消息”,在日常操作中,相信很多人在构建一个即时消息应用之如何创建消息问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”构建一个即时消息应用之如何创建消息”的疑惑有所...
    99+
    2023-06-16
  • go和javascript怎么构建一个即时消息应用
    这篇“go和javascript怎么构建一个即时消息应用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“go和javascri...
    99+
    2023-06-16
  • OAuth如何构建一个即时消息应用
    这篇“OAuth如何构建一个即时消息应用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“OAuth如何构建一个即时消息应用”文...
    99+
    2023-06-16
  • 构建一个即时消息应用之实现Conversation页面
    这篇文章主要讲解了“构建一个即时消息应用之实现Conversation页面”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“构建一个即时消息应用之实现Conversation页面”吧!聊天标题让...
    99+
    2023-06-16
  • 构建一个即时消息应用之实现Home页面
    这篇文章主要介绍“构建一个即时消息应用之实现Home页面”,在日常操作中,相信很多人在构建一个即时消息应用之实现Home页面问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”构建一个即时消息应用之实现Home页面...
    99+
    2023-06-16
  • ASP.NET中怎么创建一个对话框
    这篇文章给大家介绍ASP.NET中怎么创建一个对话框,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。 在解决方案资源管理器中选择“Test Installer”项目。在“视图”菜单上指向“编辑器”,然后选择“用户界面”。...
    99+
    2023-06-18
  • jquery中怎么创建一个确认对话框
    这篇文章将为大家详细讲解有关jquery中怎么创建一个确认对话框,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。$(document).ready(funct...
    99+
    2024-04-02
  • PyQt5中怎么创建一个文件对话框
    在PyQt5中,可以使用QFileDialog类来创建一个文件对话框。下面是一个简单的示例代码,演示如何创建一个文件对话桳: imp...
    99+
    2024-03-12
    PyQt5
  • 怎么在PHP中创建一个删除对话框
    本文小编为大家详细介绍“怎么在PHP中创建一个删除对话框”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么在PHP中创建一个删除对话框”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。步骤一:创建删除按钮首先,让我...
    99+
    2023-07-05
  • JavaScript中怎么构建一个倒数计时器
    JavaScript中怎么构建一个倒数计时器,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。首先,你需要设置一个有效的结束日期。这应该是Java...
    99+
    2024-04-02
  • 怎么在Android中实现一个对话框
    怎么在Android中实现一个对话框?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。2个按钮public class MainActivity ...
    99+
    2023-05-30
    android
  • ORACLE中alter system kill session怎么实现立即结束一个会话
    这篇文章主要为大家展示了“ORACLE中alter system kill session怎么实现立即结束一个会话”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“...
    99+
    2024-04-02
  • 怎么在PHP中实现一个长轮询消息实时推送功能
    今天就跟大家聊聊有关怎么在PHP中实现一个长轮询消息实时推送功能,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。入口文件index.html<!DOCTYPE HTML...
    99+
    2023-06-06
  • Python中怎么构建一个决策树
    本篇文章给大家分享的是有关Python中怎么构建一个决策树,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。决策树决策树是当今最强大的监督学习方法的组成部分。决策树基本上是一个二叉...
    99+
    2023-06-16
  • C++中怎么构建一个 main()函数
    本篇文章为大家展示了C++中怎么构建一个 main()函数,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。C++ Builder使用户无需考虑Windows程序的低级细节,而可以集中考虑程序用户界面和...
    99+
    2023-06-17
  • linux中怎么即时设置一个静态文件服务器
    这篇文章主要为大家展示了“linux中怎么即时设置一个静态文件服务器”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“linux中怎么即时设置一个静态文件服务器”这篇文章吧。曾经想通过网络共享你的文...
    99+
    2023-06-16
  • 怎么在Android中自定义一个扁平化对话框
    这篇文章给大家介绍怎么在Android中自定义一个扁平化对话框,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。Shamoo想到在Android平台上弄一个扁平化的对话框。参考过一篇帖子,然后改了一下。这个Demo比较简单...
    99+
    2023-05-31
    android
  • Ajax中怎么创建一个XMLHttp对象
    Ajax中怎么创建一个XMLHttp对象,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。代码如下:function creataj...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作