站內信消息未讀 - 已讀數據表設計 如何用 Golang 實現

今天我們來聊一個經常在後臺開發中遇到的話題——站內信消息的未讀 / 已讀狀態數據表設計,以及如何用 Golang 來實現這個功能。

對於很多做過後臺開發的程序員來說,站內信系統幾乎是每個系統都會涉及到的功能之一。無論是用戶通知、系統提醒,還是訂單更新,站內信的使用都非常廣泛。

不過,在實際的開發過程中,關於站內信消息的存儲、管理以及查詢等,往往會讓我們花上一些時間去設計和優化。特別是當我們需要處理未讀已讀這兩個狀態時,設計一張合理的數據庫表結構和高效的業務邏輯就顯得尤爲重要了。

今天就來跟大家分享一下,如何通過 Golang 來實現一個高效的站內信消息管理系統,確保每個用戶都能方便地查看和管理自己的消息。

一、站內信系統設計思路

首先,我們需要明確站內信系統的核心需求:每個用戶都能接收不同發送者的消息,消息有未讀和已讀的狀態,甚至可能有刪除的需求。那麼我們要如何設計數據表,才能滿足這些需求呢?

1.1 消息表(messages)

消息表用來存儲每一條站內信的基本內容。一般來說,消息表會包含以下幾個重要字段:

消息表的設計可以簡單明瞭地存儲每一條消息的核心內容,這樣我們就能對消息的內容進行管理,比如刪除、修改等操作。

1.2 用戶 - 消息關聯表(user_message_status)

爲了追蹤每個用戶對每條消息的閱讀狀態,我們需要設計一個關聯表,存儲每個用戶與消息的關係。常見的字段包括:

通過這個關聯表,我們可以清晰地知道每個用戶對每條消息的處理狀態。對於查詢未讀消息和已讀消息的操作非常高效。

二、站內信基本流程

接下來,我們來看看站內信系統的基本流程是怎樣的:

2.1 發送消息

當一個用戶發送消息時,我們會向消息表中插入一條新的記錄,表示這條消息已經被創建了。同時,我們還需要在用戶 - 消息關聯表中爲每個接收者創建記錄,默認狀態爲未讀。代碼實現大概如下:

// 消息結構體
type Message struct {
    ID        uint   `gorm:"primaryKey"`
    SenderID  uint   `gorm:"not null"`
    ReceiverID uint  `gorm:"not null"`
    Title     string `gorm:"not null"`
    Content   string `gorm:"not null"`
    Type      string
    CreatedAt time.Time
}

// 用戶-消息關聯表結構體
type UserMessageStatus struct {
    ID        uint      `gorm:"primaryKey"`
    UserID    uint      `gorm:"not null"`
    MessageID uint      `gorm:"not null"`
    Status    string    `gorm:"default:'unread'"`
    ReadAt    *time.Time
    DeletedAt *time.Time
}

func sendMessage(db *gorm.DB, senderID, receiverID uint, title, content string) error {
    message := Message{
        SenderID:  senderID,
        ReceiverID: receiverID,
        Title:     title,
        Content:   content,
        Type:      "notification", // 假設這是一個通知類型的消息
        CreatedAt: time.Now(),
    }

    if err := db.Create(&message).Error; err != nil {
        return err
    }

    // 爲每個接收者創建用戶消息關聯記錄,默認未讀
    userMessageStatus := UserMessageStatus{
        UserID:    receiverID,
        MessageID: message.ID,
        Status:    "unread",
    }

    if err := db.Create(&userMessageStatus).Error; err != nil {
        return err
    }

    return nil
}

2.2 讀取消息

當用戶查看一條消息時,我們需要更新用戶 - 消息關聯表中的狀態爲已讀,並記錄閱讀時間。代碼大致如下:

func markMessageAsRead(db *gorm.DB, userID, messageID uint) error {
    var userMessageStatus UserMessageStatus
    if err := db.Where("user_id = ? AND message_id = ?", userID, messageID).First(&userMessageStatus).Error; err != nil {
        return err
    }

    userMessageStatus.Status = "read"
    userMessageStatus.ReadAt = time.Now()

    if err := db.Save(&userMessageStatus).Error; err != nil {
        return err
    }

    return nil
}

2.3 查詢未讀消息

我們常常需要查詢用戶的未讀消息。這個操作通過查詢用戶 - 消息關聯表中狀態爲 “未讀” 的記錄來實現:

func getUnreadMessages(db *gorm.DB, userID uint) ([]Message, error) {
    var messages []Message
    err := db.Joins("JOIN user_message_status ums ON ums.message_id = messages.id").
        Where("ums.user_id = ? AND ums.status = ?", userID, "unread").
        Find(&messages).Error

    return messages, err
}

2.4 刪除消息

刪除消息其實可以有兩種處理方式:

  1. 物理刪除:直接從表中刪除消息記錄。

  2. 邏輯刪除:更新記錄狀態爲 “已刪除”,這樣用戶還是能查看到自己刪除的消息,只是狀態變成“已刪除” 而已。

如果是邏輯刪除,代碼可能是這樣:

func deleteMessage(db *gorm.DB, userID, messageID uint) error {
    var userMessageStatus UserMessageStatus
    if err := db.Where("user_id = ? AND message_id = ?", userID, messageID).First(&userMessageStatus).Error; err != nil {
        return err
    }

    userMessageStatus.Status = "deleted"
    userMessageStatus.DeletedAt = time.Now()

    if err := db.Save(&userMessageStatus).Error; err != nil {
        return err
    }

    return nil
}

通過設計合理的數據庫結構,我們能清晰地分離消息內容用戶狀態,提高系統的可維護性和擴展性。藉助 Golang 強大的併發能力和 ORM 框架 GORM 的支持,我們可以輕鬆實現消息發送、閱讀、查詢等業務邏輯。

這套方案適用於大多數站內信系統,如果需要支持更多複雜功能(例如消息分組、提醒通知、消息推送等),我們也可以在此基礎上進行擴展。

每當用戶發送一條消息,系統會自動爲每個接收者生成記錄,確保消息能夠順利送達並被處理。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/IH46SmxSUGKObuel5CJU1g