精品欧美一区二区三区在线观看 _久久久久国色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久久国产综合久久91| 日本黄色动态图| 久cao在线| 成人一级黄色片| 欧美精品成人在线| 丰满少妇高潮一区二区| 亚洲国产伊人| 亚洲成av人影院| 欧美三日本三级少妇三99| 中文字幕男人天堂| 亚洲天堂成人| 中文在线不卡视频| 国产免费a级片| 免费高清视频在线一区| 一区二区三区在线免费| 牛人盗摄一区二区三区视频| 国产一区二区小视频| 亚洲国产高清一区二区三区| 最近2019年中文视频免费在线观看| 国产乱淫av片| 国产精品伊人| 欧美日韩亚洲视频| 男人日女人的bb| 国产天堂在线| 99久久精品免费精品国产| 国产精品色婷婷视频| 日韩免费不卡视频| 国产精品久久久久久久久妇女| 亚洲精品动漫100p| 师生出轨h灌满了1v1| 日韩色淫视频| 黑人巨大精品欧美一区免费视频| 在线观看污视频| 欧美成人性生活视频| 久久夜色精品国产噜噜av| 99www免费人成精品| 五月天中文字幕| 模特精品在线| 久久久噜噜噜久久| 亚洲欧美一区二区三区四区五区| 日韩欧美中字| 亚洲色图校园春色| 变态另类丨国产精品| 亚洲一区 二区| 欧美一区二区三区日韩视频| 日韩一区二区三区不卡视频| 午夜精品成人av| 欧美天天综合色影久久精品| 国产免费黄色小视频| 性xxxxfjsxxxxx欧美| 一区二区视频在线| 日韩一二区视频| av在线官网| 伊人色综合久久天天人手人婷| 99亚洲精品视频| 国产高清一区二区三区视频| 中文字幕亚洲精品在线观看| 亚洲精品久久久久久一区二区| 成人欧美一区| 国产精品视频在线看| 日韩欧美一区二区三区四区| 番号集在线观看| 中文字幕乱码一区二区免费| 亚洲电影免费| 毛片av在线| 亚洲黄一区二区三区| 91精品一区二区三区四区| 黄色在线播放网站| 一区二区三区日韩欧美精品| 欧美中文字幕在线观看视频| 国产精选在线| 一本久久a久久免费精品不卡| 哪个网站能看毛片| 欧美色片在线观看| 91麻豆精品久久久久蜜臀| 亚洲制服在线观看| 大桥未久女教师av一区二区| 日韩精品高清在线| 亚洲最大成人综合网| 午夜精品无码一区二区三区| 在线免费观看黄色网址| 91在线一区二区三区| 欧美高清性xxxxhd| 欧美成人高清在线| 亚洲成人免费视| 韩国一区二区av| 四虎永久精品在线| 精品成人在线观看| 欧美18—19性高清hd4k| 在线成人超碰| 欧美一级免费看| 怡春院在线视频| 成人一区二区三区中文字幕| 欧美精品在线一区| 成人video亚洲精品| 精品久久久久久久久久久久| 毛葺葺老太做受视频| 精品中文字幕一区二区三区| 国产丝袜高跟一区| 一区二区三区在线播放视频| 激情综合电影网| 国产精品视频色| 刘亦菲久久免费一区二区| 国产三级精品在线| 日本免费a视频| 黄色欧美视频| 国产丝袜视频一区| 九九热国产精品视频| 日韩精品电影在线| 国产一区二区三区色淫影院| 麻豆传媒在线观看| 欧美日韩免费在线观看| 久久久精品高清| 一个色免费成人影院| 欧美巨乳美女视频| 中文字幕理论片| thepron国产精品| 熟妇熟女乱妇乱女网站| 卡通欧美亚洲| 亚洲激情 国产| 中文字幕在线2021| 欧美一区免费观看| 波多野结衣的一区二区三区| 高清欧美性猛交xxxx黑人猛交| 中文字幕黄色av| 久久久精品欧美丰满| 妺妺窝人体色www看人体| 久久精品嫩草影院| 亚洲午夜精品久久久久久性色| 国产在线观看成人| 韩国一区二区视频| 午夜欧美性电影| 二吊插入一穴一区二区| 欧美v亚洲v综合ⅴ国产v| 国产午夜精品理论片| 免费av成人在线| 日韩av在线电影观看| 中文不卡1区2区3区| 亚洲国产毛片完整版| 久久精品欧美一区二区| 国产成人在线视频网址| 国产av不卡一区二区| 欧美爱爱视频| 色偷偷av一区二区三区| 亚洲手机在线观看| 中文一区二区在线观看| 久久久国产欧美| 欧美丝袜一区| 国产日韩视频在线观看| 999国产在线视频| 欧美日韩在线综合| 毛片aaaaaa| 精品一区二区三区在线观看| 一区精品视频| 日韩精品成人在线观看| 精品中文字幕乱| 蜜臀av免费在线观看| 亚洲成人一区在线| 亚洲综合自拍网| 视频一区二区不卡| 欧美日韩综合久久| 新片速递亚洲合集欧美合集| 亚洲网站在线观看| 在线观看一二三区| 亚洲免费电影在线| 丰满少妇xbxb毛片日本| 亚洲国产91| 欧美h视频在线| 国产精品传媒麻豆hd| 美日韩丰满少妇在线观看| www.av在线.com| 欧美日韩国产限制| 内射毛片内射国产夫妻| 国产在线不卡视频| 亚洲中文字幕无码av永久| 亚洲欧美日本伦理| 国产欧美日韩专区发布| 尤物视频在线看| 精品一区二区亚洲| 国产乱码精品一区二三区蜜臂| 亚洲国产欧美一区二区三区丁香婷| yy6080午夜| 蜜桃传媒麻豆第一区在线观看| 激情综合色综合久久| 国新精品乱码一区二区三区18| 欧美久久天堂| 日韩中文字幕免费视频| 欧美自拍第一页| 欧美性猛片xxxx免费看久爱| 日本妇女毛茸茸| 26uuu国产日韩综合| 性刺激的欧美三级视频| 国内自拍一区| 午夜视频久久久| 伊人久久大香线蕉av超碰| 日韩av电影免费观看高清| 女女色综合影院| 亚洲第一综合天堂另类专| 日韩国产成人在线| 一区二区三区免费看视频| 成人免费无遮挡无码黄漫视频| 国产乱色国产精品免费视频| 日本精品www| 欧美三级小说| 色之综合天天综合色天天棕色| 亚洲国产欧美国产第一区| 国产成人综合一区二区三区| 成全电影大全在线观看| www.欧美免费| 国产在线自天天| 亚洲高清久久网| 国产免费叼嘿网站免费| 一本色道久久综合亚洲91 | 99久久久成人国产精品| 98精品国产自产在线观看| www视频在线看| 亚洲一区第一页| 五月婷婷综合久久| 欧美一区二区美女| 中文字幕+乱码+中文乱码www| 精品久久久久久亚洲精品| 青娱乐91视频| 亚洲视频在线一区观看| 伊人影院综合网| 久久精品夜色噜噜亚洲a∨| 又黄又爽的网站| 国产suv精品一区二区三区| 国产福利精品一区二区三区| 日本不卡免费在线视频| 国产99久久九九精品无码| 亚洲国产第一| 亚洲精品蜜桃久久久久久| 欧美日韩精选| 成人在线免费高清视频| 国产精品伦理久久久久久| 一区二区三区高清国产| 国产 欧美 日韩 一区| 四虎影视精品| 精品国产一区二区三区日日嗨| 精品久久国产一区| 成人亚洲激情网| 自拍偷拍欧美日韩| 成人国产亚洲精品a区天堂华泰| 精品69视频一区二区三区| 国产精品国语对白| 国产91亚洲精品久久久| 国产精品亚发布| 精品亚洲a∨| 国产欧美日韩专区发布| 一级欧美视频| 成人网在线免费观看| 国产一区二区三区黄网站| 91在线视频一区| 精品国产麻豆| 北条麻妃高清一区| 精品久久对白| 久久久99国产精品免费| 天天躁日日躁成人字幕aⅴ| 免费久久久一本精品久久区| 亚洲精品白浆高清| 欧美日韩成人一区二区三区| 国产99久久精品一区二区300| 日韩福利视频| 天天综合精品| 日韩在线观看a| 国产午夜久久| 亚洲性生活网站| 激情图区综合网| 苍井空张开腿实干12次| 99精品热视频| 国产精品av久久久久久无| 中文字幕一区三区| 久久亚洲AV无码| 精品福利一区二区| 亚洲午夜在线播放| 日韩一区二区麻豆国产| 无码精品人妻一区二区三区影院| 亚洲欧美在线一区| 国产三区视频在线观看| 国语自产在线不卡| 电影一区二区| 91av一区二区三区| 日韩精品导航| 久久久一二三四| 亚洲每日在线| 日韩av.com| 成年人国产精品| 国产视频123区| 亚洲一区二区高清| 欧美精品日韩www.p站| 蜜桃视频网站在线观看| 久久久久久午夜| 69堂精品视频在线播放| dy888夜精品国产专区| 一区三区在线欧| 国内外成人激情免费视频| 另类图片国产| 992tv人人草| 久久精品一区八戒影视| 欧美三级免费看| 在线亚洲精品福利网址导航| 国产黄色美女视频| 国产一区二区三区欧美| 超碰在线最新网址| 国产精品丝袜久久久久久不卡| 精品成人自拍视频| 天天爱天天做天天操| 国产精品亚洲欧美| 一卡二卡三卡四卡五卡| 国产人成一区二区三区影院| 日韩高清精品免费观看| 欧美日韩高清一区二区| 天堂资源最新在线| 欧美黄色性视频| 免费成人黄色网| 欧美高清性xxxxhd| 91久久久久| 国产精品igao网网址不卡| 国产视频一区在线播放| 日韩免费一级片| 欧美精品一区二区高清在线观看| 欧美日韩欧美| 国产精品日韩一区| 九色成人国产蝌蚪91| 男人添女荫道口图片| 日韩av影院| 欧美日韩精品一区二区| 中文字幕一区二区久久人妻| 日韩黄色高清视频| 678在线观看视频| 99久久99久久精品国产片| 亚洲精品tv久久久久久久久久| 久久婷婷国产91天堂综合精品| 久久综合给合久久狠狠狠97色69| 国产乡下妇女做爰| 日韩欧美综合一区| bt在线麻豆视频| 91精品久久久久久久久中文字幕| 欧美日韩在线观看视频小说| 成人在线看视频| 2022国产精品视频| 少妇一级淫片免费放中国| 亚洲激情视频在线播放| missav|免费高清av在线看| 丁香五月网久久综合| 欧美日韩中文| 最新国产精品自拍| 亚洲一区在线看| 欧美熟妇交换久久久久久分类| 欧美肥臀大乳一区二区免费视频| 午夜日韩影院| 欧美亚洲黄色片| 99久久精品免费看国产| 精品国产免费观看| 亚洲精品一区二三区不卡| videos性欧美另类高清| 日韩中文字幕一区二区| 蜜桃视频在线一区| 少妇高潮一区二区三区喷水| 在线成人高清不卡| 中文在线字幕免费观看| 成人免费在线一区二区三区| 亚洲精品资源| 亚洲精品成人无码| 欧美色精品在线视频| 久草中文在线| 国产伦视频一区二区三区| 国产精品久久久亚洲一区| 精品人妻互换一区二区三区| 在线观看一区二区视频| 精精国产xxxx视频在线| 国产一区二区调教| 少妇aaaaa| 亚洲国产古装精品网站| 欧美va在线观看| 韩国黄色一级大片| 成人毛片在线观看| 午夜精品免费观看| 美女福利视频一区| 香蕉视频一区| 亚洲精品第三页| 午夜久久福利影院| 成人av一区| 国产精品视频在线免费观看| 久久不射网站| 东方av正在进入| 亚洲老头同性xxxxx| www.久久| 夜夜添无码一区二区三区| 国产欧美一区二区精品秋霞影院| 97精品人妻一区二区三区在线| 欧美激情精品久久久久久大尺度 | 日韩情涩欧美日韩视频| 中文在线8资源库| 最新av在线免费观看| 久久综合久久99| 亚洲综合一区中| 欧美孕妇毛茸茸xxxx| 亚洲精品午夜av福利久久蜜桃| 99久久人妻无码中文字幕系列|