feat(chat): implement sequence-based unread count tracking
Introduce a new mechanism for tracking read positions using message sequences (hasReadSeq), similar to OpenIM, to allow for O(1) unread count calculations. - Implement `UserReadSeq` caching in Redis with Lua scripts to prevent sequence regression (ensuring updates only occur if the new sequence is greater than the current one). - Update `ConversationCache` to support sequence-based unread count computation (`maxSeq - hasReadSeq`). - Refactor `MessageRepository` to use conditional updates for `last_read_seq` in the database. - Update `ChatService` to automatically update the sender's read sequence when sending a message. - Optimize `MarkAsRead` logic to update both database and Redis sequence caches and refine notification targets for private vs group chats. - Update `jpush` client to use pointer types for boolean fields to properly handle optional values in JSON.
This commit is contained in:
@@ -278,27 +278,41 @@ func (r *messageRepository) GetParticipant(conversationID string, userID string)
|
||||
return &participant, nil
|
||||
}
|
||||
|
||||
// UpdateLastReadSeq 更新已读位置
|
||||
// UpdateLastReadSeq 更新已读位置(防回退:仅当新值大于当前值时才更新)
|
||||
// userID 参数为 string 类型(UUID格式),与JWT中user_id保持一致
|
||||
func (r *messageRepository) UpdateLastReadSeq(conversationID string, userID string, lastReadSeq int64) error {
|
||||
// 使用条件更新防止已读位置回退
|
||||
result := r.db.Model(&model.ConversationParticipant{}).
|
||||
Where("conversation_id = ? AND user_id = ?", conversationID, userID).
|
||||
Where("conversation_id = ? AND user_id = ? AND last_read_seq < ?", conversationID, userID, lastReadSeq).
|
||||
Update("last_read_seq", lastReadSeq)
|
||||
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// 如果没有更新任何记录,说明参与者记录不存在,需要插入
|
||||
// 如果没有更新任何记录,可能是参与者记录不存在,尝试插入
|
||||
if result.RowsAffected == 0 {
|
||||
// 尝试插入新记录(跨数据库 upsert)
|
||||
err := r.db.Clauses(clause.OnConflict{
|
||||
// 检查是否已有记录但 seq 不需要更新
|
||||
var existingSeq int64
|
||||
err := r.db.Model(&model.ConversationParticipant{}).
|
||||
Where("conversation_id = ? AND user_id = ?", conversationID, userID).
|
||||
Select("last_read_seq").Scan(&existingSeq).Error
|
||||
if err == nil && existingSeq >= lastReadSeq {
|
||||
// 已有记录且 seq 不需要回退,直接返回成功
|
||||
return nil
|
||||
}
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
|
||||
// 参与者记录不存在,插入新记录
|
||||
err = r.db.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{
|
||||
{Name: "conversation_id"},
|
||||
{Name: "user_id"},
|
||||
},
|
||||
DoUpdates: clause.Assignments(map[string]any{
|
||||
"last_read_seq": lastReadSeq,
|
||||
"last_read_seq": gorm.Expr("CASE WHEN last_read_seq < ? THEN ? ELSE last_read_seq END", lastReadSeq, lastReadSeq),
|
||||
"updated_at": gorm.Expr("CURRENT_TIMESTAMP"),
|
||||
}),
|
||||
}).Create(&model.ConversationParticipant{
|
||||
|
||||
Reference in New Issue
Block a user