精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

構(gòu)建一個(gè)即時(shí)消息應(yīng)用(四):消息

開發(fā) 后端
在這篇文章中,我們將對端點(diǎn)進(jìn)行編碼,以創(chuàng)建一條消息并列出它們,同時(shí)還將編寫一個(gè)端點(diǎn)以更新參與者上次閱讀消息的時(shí)間。

[[345179]]

本文是該系列的第四篇。

在這篇文章中,我們將對端點(diǎn)進(jìn)行編碼,以創(chuàng)建一條消息并列出它們,同時(shí)還將編寫一個(gè)端點(diǎn)以更新參與者上次閱讀消息的時(shí)間。 首先在 main() 函數(shù)中添加這些路由。

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

消息會進(jìn)入對話,因此端點(diǎn)包含對話 ID。

創(chuàng)建消息

該端點(diǎn)處理對 /api/conversations/{conversationID}/messages 的 POST 請求,其 JSON 主體僅包含消息內(nèi)容,并返回新創(chuàng)建的消息。它有兩個(gè)副作用:更新對話 last_message_id 以及更新參與者 messages_read_at

  1. func createMessage(w http.ResponseWriter, r *http.Request) {
  2. var input struct {
  3. Content string `json:"content"`
  4. }
  5. defer r.Body.Close()
  6. if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
  7. http.Error(w, err.Error(), http.StatusBadRequest)
  8. return
  9. }
  10.  
  11. errs := make(map[string]string)
  12. input.Content = removeSpaces(input.Content)
  13. if input.Content == "" {
  14. errs["content"] = "Message content required"
  15. } else if len([]rune(input.Content)) > 480 {
  16. errs["content"] = "Message too long. 480 max"
  17. }
  18. if len(errs) != 0 {
  19. respond(w, Errors{errs}, http.StatusUnprocessableEntity)
  20. return
  21. }
  22.  
  23. ctx := r.Context()
  24. authUserID := ctx.Value(keyAuthUserID).(string)
  25. conversationID := way.Param(ctx, "conversationID")
  26.  
  27. tx, err := db.BeginTx(ctx, nil)
  28. if err != nil {
  29. respondError(w, fmt.Errorf("could not begin tx: %v", err))
  30. return
  31. }
  32. defer tx.Rollback()
  33.  
  34. isParticipant, err := queryParticipantExistance(ctx, tx, authUserID, conversationID)
  35. if err != nil {
  36. respondError(w, fmt.Errorf("could not query participant existance: %v", err))
  37. return
  38. }
  39.  
  40. if !isParticipant {
  41. http.Error(w, "Conversation not found", http.StatusNotFound)
  42. return
  43. }
  44.  
  45. var message Message
  46. if err := tx.QueryRowContext(ctx, `
  47. INSERT INTO messages (content, user_id, conversation_id) VALUES
  48. ($1, $2, $3)
  49. RETURNING id, created_at
  50. `, input.Content, authUserID, conversationID).Scan(
  51. &message.ID,
  52. &message.CreatedAt,
  53. ); err != nil {
  54. respondError(w, fmt.Errorf("could not insert message: %v", err))
  55. return
  56. }
  57.  
  58. if _, err := tx.ExecContext(ctx, `
  59. UPDATE conversations SET last_message_id = $1
  60. WHERE id = $2
  61. `, message.ID, conversationID); err != nil {
  62. respondError(w, fmt.Errorf("could not update conversation last message ID: %v", err))
  63. return
  64. }
  65.  
  66. if err = tx.Commit(); err != nil {
  67. respondError(w, fmt.Errorf("could not commit tx to create a message: %v", err))
  68. return
  69. }
  70.  
  71. go func() {
  72. if err = updateMessagesReadAt(nil, authUserID, conversationID); err != nil {
  73. log.Printf("could not update messages read at: %v\n", err)
  74. }
  75. }()
  76.  
  77. message.Content = input.Content
  78. message.UserID = authUserID
  79. message.ConversationID = conversationID
  80. // TODO: notify about new message.
  81. message.Mine = true
  82.  
  83. respond(w, message, http.StatusCreated)
  84. }

首先,它將請求正文解碼為包含消息內(nèi)容的結(jié)構(gòu)。然后,它驗(yàn)證內(nèi)容不為空并且少于 480 個(gè)字符。

  1. var rxSpaces = regexp.MustCompile("\\s+")
  2.  
  3. func removeSpaces(s string) string {
  4. if s == "" {
  5. return s
  6. }
  7.  
  8. lines := make([]string, 0)
  9. for _, line := range strings.Split(s, "\n") {
  10. line = rxSpaces.ReplaceAllLiteralString(line, " ")
  11. line = strings.TrimSpace(line)
  12. if line != "" {
  13. lines = append(lines, line)
  14. }
  15. }
  16. return strings.Join(lines, "\n")
  17. }

這是刪除空格的函數(shù)。它遍歷每一行,刪除兩個(gè)以上的連續(xù)空格,然后回非空行。

驗(yàn)證之后,它將啟動(dòng)一個(gè) SQL 事務(wù)。首先,它查詢對話中的參與者是否存在。

  1. func queryParticipantExistance(ctx context.Context, tx *sql.Tx, userID, conversationID string) (bool, error) {
  2. if ctx == nil {
  3. ctx = context.Background()
  4. }
  5. var exists bool
  6. if err := tx.QueryRowContext(ctx, `SELECT EXISTS (
  7. SELECT 1 FROM participants
  8. WHERE user_id = $1 AND conversation_id = $2
  9. )`, userID, conversationID).Scan(&exists); err != nil {
  10. return false, err
  11. }
  12. return exists, nil
  13. }

我將其提取到一個(gè)函數(shù)中,因?yàn)樯院罂梢灾赜谩?/p>

如果用戶不是對話參與者,我們將返回一個(gè) 404 NOT Found 錯(cuò)誤。

然后,它插入消息并更新對話 last_message_id。從這時(shí)起,由于我們不允許刪除消息,因此 last_message_id 不能為 NULL

接下來提交事務(wù),并在 goroutine 中更新參與者 messages_read_at

  1. func updateMessagesReadAt(ctx context.Context, userID, conversationID string) error {
  2. if ctx == nil {
  3. ctx = context.Background()
  4. }
  5.  
  6. if _, err := db.ExecContext(ctx, `
  7. UPDATE participants SET messages_read_at = now()
  8. WHERE user_id = $1 AND conversation_id = $2
  9. `, userID, conversationID); err != nil {
  10. return err
  11. }
  12. return nil
  13. }

在回復(fù)這條新消息之前,我們必須通知一下。這是我們將要在下一篇文章中編寫的實(shí)時(shí)部分,因此我在那里留一了個(gè)注釋。

獲取消息

這個(gè)端點(diǎn)處理對 /api/conversations/{conversationID}/messages 的 GET 請求。 它用一個(gè)包含會話中所有消息的 JSON 數(shù)組進(jìn)行響應(yīng)。它還具有更新參與者 messages_read_at 的副作用。

  1. func getMessages(w http.ResponseWriter, r *http.Request) {
  2. ctx := r.Context()
  3. authUserID := ctx.Value(keyAuthUserID).(string)
  4. conversationID := way.Param(ctx, "conversationID")
  5.  
  6. tx, err := db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
  7. if err != nil {
  8. respondError(w, fmt.Errorf("could not begin tx: %v", err))
  9. return
  10. }
  11. defer tx.Rollback()
  12.  
  13. isParticipant, err := queryParticipantExistance(ctx, tx, authUserID, conversationID)
  14. if err != nil {
  15. respondError(w, fmt.Errorf("could not query participant existance: %v", err))
  16. return
  17. }
  18.  
  19. if !isParticipant {
  20. http.Error(w, "Conversation not found", http.StatusNotFound)
  21. return
  22. }
  23.  
  24. rows, err := tx.QueryContext(ctx, `
  25. SELECT
  26. id,
  27. content,
  28. created_at,
  29. user_id = $1 AS mine
  30. FROM messages
  31. WHERE messages.conversation_id = $2
  32. ORDER BY messages.created_at DESC
  33. `, authUserID, conversationID)
  34. if err != nil {
  35. respondError(w, fmt.Errorf("could not query messages: %v", err))
  36. return
  37. }
  38. defer rows.Close()
  39.  
  40. messages := make([]Message, 0)
  41. for rows.Next() {
  42. var message Message
  43. if err = rows.Scan(
  44. &message.ID,
  45. &message.Content,
  46. &message.CreatedAt,
  47. &message.Mine,
  48. ); err != nil {
  49. respondError(w, fmt.Errorf("could not scan message: %v", err))
  50. return
  51. }
  52.  
  53. messages = append(messages, message)
  54. }
  55.  
  56. if err = rows.Err(); err != nil {
  57. respondError(w, fmt.Errorf("could not iterate over messages: %v", err))
  58. return
  59. }
  60.  
  61. if err = tx.Commit(); err != nil {
  62. respondError(w, fmt.Errorf("could not commit tx to get messages: %v", err))
  63. return
  64. }
  65.  
  66. go func() {
  67. if err = updateMessagesReadAt(nil, authUserID, conversationID); err != nil {
  68. log.Printf("could not update messages read at: %v\n", err)
  69. }
  70. }()
  71.  
  72. respond(w, messages, http.StatusOK)
  73. }

首先,它以只讀模式開始一個(gè) SQL 事務(wù)。檢查參與者是否存在,并查詢所有消息。在每條消息中,我們使用當(dāng)前經(jīng)過身份驗(yàn)證的用戶 ID 來了解用戶是否擁有該消息(mine)。 然后,它提交事務(wù),在 goroutine 中更新參與者 messages_read_at 并以消息響應(yīng)。

讀取消息

該端點(diǎn)處理對 /api/conversations/{conversationID}/read_messages 的 POST 請求。 沒有任何請求或響應(yīng)主體。 在前端,每次有新消息到達(dá)實(shí)時(shí)流時(shí),我們都會發(fā)出此請求。

  1. func readMessages(w http.ResponseWriter, r *http.Request) {
  2. ctx := r.Context()
  3. authUserID := ctx.Value(keyAuthUserID).(string)
  4. conversationID := way.Param(ctx, "conversationID")
  5.  
  6. if err := updateMessagesReadAt(ctx, authUserID, conversationID); err != nil {
  7. respondError(w, fmt.Errorf("could not update messages read at: %v", err))
  8. return
  9. }
  10.  
  11. w.WriteHeader(http.StatusNoContent)
  12. }

它使用了與更新參與者 messages_read_at 相同的函數(shù)。


到此為止。實(shí)時(shí)消息是后臺僅剩的部分了。請等待下一篇文章。

 

責(zé)任編輯:龐桂玉 來源: 51CTO
相關(guān)推薦

2020-10-09 15:00:56

實(shí)時(shí)消息編程語言

2019-09-29 15:25:13

CockroachDBGoJavaScript

2019-10-28 20:12:40

OAuthGuard中間件編程語言

2020-03-31 12:21:20

JSON即時(shí)消息編程語言

2020-10-12 09:20:13

即時(shí)消息Access頁面編程語言

2020-10-19 16:20:38

即時(shí)消息Conversatio編程語言

2020-10-16 14:40:20

即時(shí)消息Home頁面編程語言

2020-10-10 20:51:10

即時(shí)消息編程語言

2021-03-25 08:29:33

SpringBootWebSocket即時(shí)消息

2023-08-14 08:01:12

websocket8g用戶

2025-06-30 01:45:00

Netty輪詢HTTP 協(xié)議

2015-03-18 15:37:19

社交APP場景

2011-10-19 09:30:23

jQuery

2021-12-03 00:02:01

通訊工具即時(shí)

2023-03-27 08:33:32

2010-05-24 09:51:37

System Cent

2022-08-30 11:41:53

網(wǎng)絡(luò)攻擊木馬

2021-05-10 15:05:18

消息通信本地網(wǎng)絡(luò)

2024-04-24 11:42:21

Redis延遲消息數(shù)據(jù)庫

2009-06-29 09:06:42

微軟Web版MSN
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

国产成人精品在线视频| 欧美中文字幕一区二区三区| 91精品入口蜜桃| 日本一级黄色大片| 欧美欧美黄在线二区| 欧美日韩在线综合| www插插插无码免费视频网站| 四虎影视在线播放| 精品一区二区三区在线播放视频| 欧美精品国产精品日韩精品| 波多野结衣av在线观看| 99久久久成人国产精品| 精品久久久久久电影| 相泽南亚洲一区二区在线播放| 亚洲精品字幕在线| 蜜臀久久久久久久| 97精品视频在线播放| www成人啪啪18软件| 高清精品视频| 欧美精品粉嫩高潮一区二区| 国产免费一区二区三区视频| 菠萝蜜视频国产在线播放| www日韩大片| 2022国产精品| 亚洲专区在线播放| 美女被久久久| 久久人人爽人人爽人人片av高清| 貂蝉被到爽流白浆在线观看 | 五月激情综合婷婷| 色中文字幕在线观看| 免费在线观看一级毛片| 丁香婷婷综合色啪| 成人自拍性视频| 看黄色一级大片| 一本一道久久综合狠狠老精东影业| xxxxxxxxx欧美| 久久久久久国产免费a片| 狼人精品一区二区三区在线| 日韩一区二区精品| 在线看免费毛片| 成人看片毛片免费播放器| 激情av一区二区| 国产一二三区在线播放| 国产黄色在线免费观看| 中文字幕在线观看不卡视频| 亚洲欧洲一区二区| 超碰免费在线| 国产日韩高清在线| 欧美一级二级三级| 国产私人尤物无码不卡| 国产亚洲欧美在线| 日本精品一区二区三区视频 | 欧美大片在线看| www.超碰在线观看| 一本一本久久a久久综合精品| 日韩在线免费视频观看| 激情五月深爱五月| 国产精品成人av| 久久综合电影一区| 美女的奶胸大爽爽大片| 欧美三级不卡| 97久久精品人人澡人人爽缅北| 久久精品久久国产| 亚洲一区亚洲| 国产精品久久久久久久久粉嫩av| 中文字幕av资源| 麻豆精品新av中文字幕| 成人亚洲激情网| 亚洲av无码国产精品久久不卡| 懂色av一区二区在线播放| 国产精品一区二区三区在线| 同心难改在线观看| 国产欧美精品一区二区色综合朱莉| 视频一区视频二区视频三区视频四区国产 | 精品在线视频免费| 91丨九色丨丰满| 久久亚洲道色| 亚洲精品国精品久久99热 | 久久草在线视频| 亚洲精品一区中文| 中文字幕在线观看二区| 一二三区不卡| 韩国日本不卡在线| 中文字幕码精品视频网站| 国产一区二区在线视频| 国产精品视频一区二区三区经| 欧美女子与性| 中文字幕日韩精品一区| 一二三四视频社区在线| 国产成人精品一区二三区在线观看 | 91女厕偷拍女厕偷拍高清| 亚洲精品日韩在线观看| 欧美家庭影院| 91官网在线免费观看| 日韩va在线观看| 久久综合另类图片小说| 国产亚洲一级高清| 欧美色图一区二区| 性欧美videos另类喷潮| 91久久嫩草影院一区二区| 国产91绿帽单男绿奴| 欧美激情中文字幕| 97在线国产视频| 国产精品久久久久久久久久齐齐| 精品剧情v国产在线观看在线| 欧美大波大乳巨大乳| 国模一区二区三区| 国产精品久久久久久久app | 国产一区二区三区在线看 | 中文字幕制服丝袜| 欧美亚洲精品在线| 97国产在线视频| 国产又粗又猛又黄| 99re成人在线| 亚洲爆乳无码精品aaa片蜜桃| 成人私拍视频| 精品国产亚洲在线| 免费在线观看h片| 久久久青草婷婷精品综合日韩| 91九色蝌蚪成人| 生活片a∨在线观看| 天天综合色天天综合色h| 极品粉嫩美女露脸啪啪| 精品产国自在拍| 78色国产精品| 亚洲精品久久久狠狠狠爱| 国产精品免费视频观看| avav在线看| 极品束缚调教一区二区网站 | www色aa色aawww| а√在线中文在线新版| 欧美午夜精品久久久久久孕妇| 欧美夫妇交换xxx| 午夜性色一区二区三区免费视频 | 成品人视频ww入口| 日韩一区二区三区高清在线观看| 日韩在线观看视频免费| 久久久久久亚洲av无码专区| 99久久伊人久久99| 国产免费黄色小视频| 91蝌蚪精品视频| 欧美成人高清视频| 国产免费的av| 亚洲蜜臀av乱码久久精品蜜桃| 午夜免费福利在线| 成人情趣视频| 国产精品第一第二| 99青草视频在线播放视| 欧美午夜不卡在线观看免费| 特级西西www444人体聚色| 日韩一区欧美二区| 手机看片福利永久国产日韩| 成人毛片免费| 日韩中文字幕精品| 国产三级漂亮女教师| 国产精品第一页第二页第三页 | 国产91丝袜在线观看| 日本美女爱爱视频| 97人人澡人人爽91综合色| 久久免费在线观看| 天堂av中文在线资源库| 日韩欧美在线视频免费观看| a级大片在线观看| 日韩黄色片在线观看| 亚洲精品二区| 在线播放av网址| 国产欧美一区二区三区米奇| 97在线视频一区| 巨骚激情综合| 欧美欧美欧美欧美首页| 亚洲精品卡一卡二| 大陆成人av片| 男人天堂网视频| 久久看人人摘| 91精品国产91久久久久青草| 91制片在线观看| 亚洲色图综合网| 91好色先生tv| 亚洲二区在线视频| 波多野结衣一本| 精品亚洲国产成人av制服丝袜| 成人污网站在线观看| 亚洲福利天堂| 成人在线视频网站| 黄色在线网站噜噜噜| 国产一区二区三区丝袜| 精品国产无码一区二区| 黑人巨大精品欧美一区二区免费| 极品尤物一区二区| 福利一区福利二区| 99sesese| 亚洲精品人人| 综合一区中文字幕| 色婷婷精品视频| 成人国产精品一区| 日本а中文在线天堂| xxx一区二区| 国产精品国产高清国产| 4438x成人网最大色成网站| 日韩精品国产一区二区| 中文幕一区二区三区久久蜜桃| 18禁一区二区三区| 毛片av中文字幕一区二区| 日本一本中文字幕| 97人人精品| 欧美日韩另类综合| ady日本映画久久精品一区二区| 国产精品2018| 国产高清自产拍av在线| 久久夜色精品国产| a中文在线播放| 亚洲国产欧美在线成人app| 国产女人18毛片18精品| 色8久久人人97超碰香蕉987| 久久久久无码国产精品| 亚洲欧洲日韩在线| 免费在线观看a视频| 91丨九色丨尤物| 成人欧美精品一区二区| 久久99精品久久久| 黄色三级视频片| 国产精品综合| 少妇大叫太大太粗太爽了a片小说| 日韩久久综合| 日韩免费av电影| 偷窥自拍亚洲色图精选| 国产区一区二区| 一区二区三区在线免费看 | 久久久五月婷婷| 成年人的黄色片| 丁香婷婷综合色啪| 亚洲美女精品视频| 国产精品18久久久久久久久久久久| 日日干夜夜操s8| 日韩1区2区3区| 成人性做爰aaa片免费看不忠| 中文日韩欧美| 欧美 日韩 国产在线观看| 亚洲日本激情| 男人添女人下面高潮视频| 亚洲婷婷在线| 黄色激情在线视频| 亚洲三级毛片| 亚洲熟妇无码一区二区三区导航| 国产一区激情| 久久国产精品网| 亚洲青涩在线| 91视频最新入口| 国产视频一区三区| 青青视频在线播放| 首页欧美精品中文字幕| 青青在线免费观看视频| 日本在线播放一区二区三区| 欧美自拍小视频| 久久精品国产99国产| 五月天丁香花婷婷| 国产毛片精品视频| 亚洲成年人在线观看| 91在线你懂得| 国产又粗又黄又猛| 亚洲日本韩国一区| 国产精品99re| 欧美日韩亚洲激情| 国产九色91回来了| 制服丝袜av成人在线看| 亚洲欧美另类视频| 精品美女久久| 国产欧洲精品视频| 免费精品一区二区三区在线观看| 成人18视频| 亚洲专区视频| 一区二区av| 国产精品videossex久久发布| 18岁网站在线观看| 美女看a上一区| 日本少妇一级片| 99国产精品一区| 精品一区二区在线观看视频| 一区二区日韩av| 免费看日批视频| 欧美精品色综合| 天堂在线视频免费观看| 国产午夜精品美女视频明星a级| 日本韩国在线视频爽| 久久久久久com| 成人国产在线| 国产成人看片| 成人一区而且| 日韩小视频网站| 欧美aa在线视频| 国产高潮视频在线观看| 国产精品久久久久久久久果冻传媒 | 精品国产乱码久久久久久1区2匹| 日本高清视频免费在线观看| 麻豆精品网站| 精品伦一区二区三区| 国产精品美日韩| 国产精品suv一区二区三区| 欧美人狂配大交3d怪物一区 | 91在线视频免费91| 无码黑人精品一区二区| 一本到不卡免费一区二区| 国产偷人妻精品一区二区在线| 亚洲精品综合久久中文字幕| 在线中文字幕电影| 国产精品情侣自拍| 午夜a一级毛片亚洲欧洲| 国产一区一区三区| 日韩精品电影在线观看| 亚洲av成人无码一二三在线观看| 中文字幕亚洲精品在线观看| 欧美精品一二三四区| 亚洲精品一区二区三区影院 | 五月婷婷久久久| 欧美成人午夜激情| h1515四虎成人| 欧美午夜精品久久久久久蜜| 激情五月***国产精品| 欧美成人乱码一二三四区免费| 91老司机福利 在线| 欧美人妻一区二区| 911国产精品| 大地资源中文在线观看免费版| 91精品国产精品| 懂色av一区二区| www.激情网| 国产精品一区二区在线播放| 成年人看的免费视频| 在线观看网站黄不卡| 欧美日韩伦理片| 欧美在线精品免播放器视频| 国产一区二区三区亚洲| 国产精品国三级国产av| 国产综合久久久久久鬼色 | 一区二区三区高清| 国产精品污视频| 精品激情国产视频| 日韩三级成人| 五月天男人天堂| 精品一区二区三区在线观看国产| 91大神福利视频| 欧美久久久影院| 黄色网页在线免费看| 国产噜噜噜噜噜久久久久久久久 | 牛牛精品成人免费视频| 青青草精品视频在线| av成人老司机| 日韩精品成人一区| 亚洲欧美在线一区| 欧美色网在线| 在线视频不卡国产| 国产综合色在线| 久久免费视频精品| 欧美精品一区在线观看| 成人免费网站观看| 你懂的网址一区二区三区| 日韩精品视频网站| 亚洲最大成人综合网| 欧美片在线播放| 亚洲羞羞网站| 国产一区二区高清不卡| 美女日韩在线中文字幕| 国产精品久久久久久久av| 欧美日韩不卡一区二区| av在线导航| 精品国产免费久久久久久尖叫| 鲁大师成人一区二区三区| 欧美日韩生活片| 欧美大胆人体bbbb| 黄色污网站在线观看| 亚洲精品8mav| 国产高清无密码一区二区三区| 色播视频在线播放| 国产亚洲成av人片在线观看桃| 日韩精品一页| 91免费黄视频| 日本一区二区视频在线观看| 国产精品女同一区二区| 久久久欧美一区二区| 国产日产精品一区二区三区四区的观看方式| 浓精h攵女乱爱av| 亚洲午夜羞羞片| 国产精品免费播放| 91精品久久香蕉国产线看观看| 欧美亚洲一级| 丝袜美腿小色网| 亚洲欧洲成视频免费观看| 亚洲青青久久| 97国产在线播放| 国产精品久久久久一区| 日日夜夜精品免费| 国产精品爽黄69天堂a| 精品成人国产| 四虎影视一区二区| 日韩精品亚洲元码| 国产午夜精品一区在线观看| 国产欧美在线一区| 亚洲综合一二区| 91社区在线观看| 久久精品国产99精品国产亚洲性色|