千家信息网

构建一个即时消息应用之如何创建消息

发表于:2025-11-16 作者:千家信息网编辑
千家信息网最后更新 2025年11月16日,这篇文章主要介绍"构建一个即时消息应用之如何创建消息",在日常操作中,相信很多人在构建一个即时消息应用之如何创建消息问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"构建一
千家信息网最后更新 2025年11月16日构建一个即时消息应用之如何创建消息

这篇文章主要介绍"构建一个即时消息应用之如何创建消息",在日常操作中,相信很多人在构建一个即时消息应用之如何创建消息问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"构建一个即时消息应用之如何创建消息"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

首先在 main() 函数中添加这些路由。

router.HandleFunc("POST", "/api/conversations/:conversationID/messages", requireJSON(guard(createMessage)))router.HandleFunc("GET", "/api/conversations/:conversationID/messages", guard(getMessages))router.HandleFunc("POST", "/api/conversations/:conversationID/read_messages", guard(readMessages))

消息会进入对话,因此端点包含对话 ID。

创建消息

该端点处理对 /api/conversations/{conversationID}/messages 的 POST 请求,其 JSON 主体仅包含消息内容,并返回新创建的消息。它有两个副作用:更新对话 last_message_id 以及更新参与者 messages_read_at

func createMessage(w http.ResponseWriter, r *http.Request) {var input struct {Content string `json:"content"`}defer r.Body.Close()if err := json.NewDecoder(r.Body).Decode(&input); err != nil {http.Error(w, err.Error(), http.StatusBadRequest)return} errs := make(map[string]string)input.Content = removeSpaces(input.Content)if input.Content == "" {errs["content"] = "Message content required"} else if len([]rune(input.Content)) > 480 {errs["content"] = "Message too long. 480 max"}if len(errs) != 0 {respond(w, Errors{errs}, http.StatusUnprocessableEntity)return} ctx := r.Context()authUserID := ctx.Value(keyAuthUserID).(string)conversationID := way.Param(ctx, "conversationID") tx, err := db.BeginTx(ctx, nil)if err != nil {respondError(w, fmt.Errorf("could not begin tx: %v", err))return}defer tx.Rollback() isParticipant, err := queryParticipantExistance(ctx, tx, authUserID, conversationID)if err != nil {respondError(w, fmt.Errorf("could not query participant existance: %v", err))return} if !isParticipant {http.Error(w, "Conversation not found", http.StatusNotFound)return} var message Messageif err := tx.QueryRowContext(ctx, `INSERT INTO messages (content, user_id, conversation_id) VALUES($1, $2, $3)RETURNING id, created_at`, input.Content, authUserID, conversationID).Scan(&message.ID,&message.CreatedAt,); err != nil {respondError(w, fmt.Errorf("could not insert message: %v", err))return} if _, err := tx.ExecContext(ctx, `UPDATE conversations SET last_message_id = $1WHERE id = $2`, message.ID, conversationID); err != nil {respondError(w, fmt.Errorf("could not update conversation last message ID: %v", err))return} if err = tx.Commit(); err != nil {respondError(w, fmt.Errorf("could not commit tx to create a message: %v", err))return} go func() {if err = updateMessagesReadAt(nil, authUserID, conversationID); err != nil {log.Printf("could not update messages read at: %v\n", err)}}() message.Content = input.Contentmessage.UserID = authUserIDmessage.ConversationID = conversationID// TODO: notify about new message.message.Mine = true respond(w, message, http.StatusCreated)}

首先,它将请求正文解码为包含消息内容的结构。然后,它验证内容不为空并且少于 480 个字符。

var rxSpaces = regexp.MustCompile("\\s+") func removeSpaces(s string) string {if s == "" {return s} lines := make([]string, 0)for _, line := range strings.Split(s, "\n") {line = rxSpaces.ReplaceAllLiteralString(line, " ")line = strings.TrimSpace(line)if line != "" {lines = append(lines, line)}}return strings.Join(lines, "\n")}

这是删除空格的函数。它遍历每一行,删除两个以上的连续空格,然后回非空行。

验证之后,它将启动一个 SQL 事务。首先,它查询对话中的参与者是否存在。

func queryParticipantExistance(ctx context.Context, tx *sql.Tx, userID, conversationID string) (bool, error) {if ctx == nil {ctx = context.Background()}var exists boolif err := tx.QueryRowContext(ctx, `SELECT EXISTS (SELECT 1 FROM participantsWHERE user_id = $1 AND conversation_id = $2)`, userID, conversationID).Scan(&exists); err != nil {return false, err}return exists, nil}

我将其提取到一个函数中,因为稍后可以重用。

如果用户不是对话参与者,我们将返回一个 404 NOT Found 错误。

然后,它插入消息并更新对话 last_message_id。从这时起,由于我们不允许删除消息,因此 last_message_id 不能为 NULL

接下来提交事务,并在 goroutine 中更新参与者 messages_read_at

func updateMessagesReadAt(ctx context.Context, userID, conversationID string) error {if ctx == nil {ctx = context.Background()} if _, err := db.ExecContext(ctx, `UPDATE participants SET messages_read_at = now()WHERE user_id = $1 AND conversation_id = $2`, userID, conversationID); err != nil {return err}return nil}

在回复这条新消息之前,我们必须通知一下。这是我们将要在下一篇文章中编写的实时部分,因此我在那里留一了个注释。

获取消息

这个端点处理对 /api/conversations/{conversationID}/messages 的 GET 请求。 它用一个包含会话中所有消息的 JSON 数组进行响应。它还具有更新参与者 messages_read_at 的副作用。

func getMessages(w http.ResponseWriter, r *http.Request) {ctx := r.Context()authUserID := ctx.Value(keyAuthUserID).(string)conversationID := way.Param(ctx, "conversationID") tx, err := db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})if err != nil {respondError(w, fmt.Errorf("could not begin tx: %v", err))return}defer tx.Rollback() isParticipant, err := queryParticipantExistance(ctx, tx, authUserID, conversationID)if err != nil {respondError(w, fmt.Errorf("could not query participant existance: %v", err))return} if !isParticipant {http.Error(w, "Conversation not found", http.StatusNotFound)return} rows, err := tx.QueryContext(ctx, `SELECTid,content,created_at,user_id = $1 AS mineFROM messagesWHERE messages.conversation_id = $2ORDER BY messages.created_at DESC`, authUserID, conversationID)if err != nil {respondError(w, fmt.Errorf("could not query messages: %v", err))return}defer rows.Close() messages := make([]Message, 0)for rows.Next() {var message Messageif err = rows.Scan(&message.ID,&message.Content,&message.CreatedAt,&message.Mine,); err != nil {respondError(w, fmt.Errorf("could not scan message: %v", err))return} messages = append(messages, message)} if err = rows.Err(); err != nil {respondError(w, fmt.Errorf("could not iterate over messages: %v", err))return} if err = tx.Commit(); err != nil {respondError(w, fmt.Errorf("could not commit tx to get messages: %v", err))return} go func() {if err = updateMessagesReadAt(nil, authUserID, conversationID); err != nil {log.Printf("could not update messages read at: %v\n", err)}}() respond(w, messages, http.StatusOK)}

首先,它以只读模式开始一个 SQL 事务。检查参与者是否存在,并查询所有消息。在每条消息中,我们使用当前经过身份验证的用户 ID 来了解用户是否拥有该消息(mine)。 然后,它提交事务,在 goroutine 中更新参与者 messages_read_at 并以消息响应。

读取消息

该端点处理对 /api/conversations/{conversationID}/read_messages 的 POST 请求。 没有任何请求或响应主体。 在前端,每次有新消息到达实时流时,我们都会发出此请求。

func readMessages(w http.ResponseWriter, r *http.Request) {ctx := r.Context()authUserID := ctx.Value(keyAuthUserID).(string)conversationID := way.Param(ctx, "conversationID") if err := updateMessagesReadAt(ctx, authUserID, conversationID); err != nil {respondError(w, fmt.Errorf("could not update messages read at: %v", err))return} w.WriteHeader(http.StatusNoContent)}

它使用了与更新参与者 messages_read_at 相同的函数。

到此,关于"构建一个即时消息应用之如何创建消息"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

消息 参与者 更新 对话 应用 事务 函数 端点 学习 内容 用户 处理 验证 接下来 两个 主体 副作用 实时 更多 空格 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 哪里可以问到数据库问题 北大转录因子数据库 漳州市网络技术有限公司地址 论大学生网络安全意识 培训网络安全员是带薪培新吗 对数据库进行安全设置的方法 网络安全问题指什么 服务器硬盘告警 深圳互联网科技公司排名前十 虚拟机配置ie代理服务器 票房数据库技术文案 厦门思维软件开发 辽宁费用管理软件开发 栖霞市网络安全办公室电话 山东开辰网络技术服务有限公司 工程软件开发分期付款科目 it网络安全怎么自学 计算机网络安全最新论文题目 数据库事务非正常结束后果 php获取数据库数据 杨浦区优势网络技术诚信服务 上海煦淼互联网科技有限公司 网络安全大赛登陆 上海服务器机柜定做厂家 怎么远程连接主机数据库 云服务器首单秒杀 山东航天信息服务器地址 暗河小说软件开发 云服务器的网速跟什么有关 游戏数据库怎么做的
0