Files
EchoChat/backend/go-service/app/im/dao/message_dao.go

152 lines
4.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package dao
import (
"context"
"github.com/echochat/backend/app/constants"
"github.com/echochat/backend/app/im/model"
"github.com/echochat/backend/pkg/logs"
"go.uber.org/zap"
"gorm.io/gorm"
)
// MessageDAO 消息数据访问对象
type MessageDAO struct {
db *gorm.DB
}
// NewMessageDAO 创建 MessageDAO 实例
func NewMessageDAO(db *gorm.DB) *MessageDAO {
return &MessageDAO{db: db}
}
// Create 写入一条消息
func (d *MessageDAO) Create(ctx context.Context, msg *model.Message) error {
funcName := "dao.message_dao.Create"
logs.Debug(ctx, funcName, "写入消息",
zap.Int64("conversation_id", msg.ConversationID), zap.Int64("sender_id", msg.SenderID))
err := d.db.WithContext(ctx).Create(msg).Error
if err != nil {
logs.Error(ctx, funcName, "写入消息失败", zap.Error(err))
}
return err
}
// GetByID 根据 ID 查询单条消息
func (d *MessageDAO) GetByID(ctx context.Context, id int64) (*model.Message, error) {
var msg model.Message
err := d.db.WithContext(ctx).First(&msg, id).Error
if err != nil {
return nil, err
}
return &msg, nil
}
// GetByConversation 获取会话的历史消息(游标分页,按 ID 降序)
// beforeID > 0 时作为游标,查询 ID 小于 beforeID 的消息
// clearBeforeMsgID > 0 时过滤掉用户已清空的消息(个人视图)
func (d *MessageDAO) GetByConversation(ctx context.Context, conversationID int64, beforeID int64, clearBeforeMsgID int64, limit int) ([]model.Message, error) {
funcName := "dao.message_dao.GetByConversation"
logs.Debug(ctx, funcName, "查询历史消息",
zap.Int64("conversation_id", conversationID),
zap.Int64("before_id", beforeID),
zap.Int64("clear_before_msg_id", clearBeforeMsgID),
zap.Int("limit", limit))
query := d.db.WithContext(ctx).
Where("conversation_id = ? AND status != ?", conversationID, constants.MessageStatusDeleted)
if beforeID > 0 {
query = query.Where("id < ?", beforeID)
}
if clearBeforeMsgID > 0 {
query = query.Where("id > ?", clearBeforeMsgID)
}
var messages []model.Message
err := query.Order("id DESC").Limit(limit).Find(&messages).Error
if err != nil {
logs.Error(ctx, funcName, "查询历史消息失败", zap.Error(err))
}
return messages, err
}
// UpdateStatus 更新消息状态(撤回/删除)
func (d *MessageDAO) UpdateStatus(ctx context.Context, id int64, status int) error {
funcName := "dao.message_dao.UpdateStatus"
logs.Info(ctx, funcName, "更新消息状态",
zap.Int64("message_id", id), zap.Int("status", status))
return d.db.WithContext(ctx).
Model(&model.Message{}).
Where("id = ?", id).
Update("status", status).Error
}
// SearchMessages 全局消息搜索(按关键词匹配,仅搜索用户所在会话的消息)
// 返回匹配的消息列表(已 JOIN 成员表确保权限)
func (d *MessageDAO) SearchMessages(ctx context.Context, userID int64, keyword string, limit int) ([]MessageSearchResult, error) {
funcName := "dao.message_dao.SearchMessages"
logs.Debug(ctx, funcName, "全局消息搜索",
zap.Int64("user_id", userID), zap.String("keyword", keyword))
var results []MessageSearchResult
err := d.db.WithContext(ctx).
Raw(`SELECT m.id, m.conversation_id, m.sender_id, m.content, m.created_at
FROM im_messages m
JOIN im_conversation_members cm ON cm.conversation_id = m.conversation_id
WHERE cm.user_id = ? AND cm.is_deleted = false
AND m.status = ?
AND to_tsvector('simple', m.content) @@ plainto_tsquery('simple', ?)
ORDER BY m.created_at DESC
LIMIT ?`,
userID, constants.MessageStatusNormal, keyword, limit).
Scan(&results).Error
if err != nil {
logs.Error(ctx, funcName, "全局消息搜索失败", zap.Error(err))
}
return results, err
}
// MessageSearchResult 搜索结果单条记录
type MessageSearchResult struct {
ID int64 `json:"id"`
ConversationID int64 `json:"conversation_id"`
SenderID int64 `json:"sender_id"`
Content string `json:"content"`
CreatedAt string `json:"created_at"`
}
// FindByClientMsgID 根据客户端消息 ID 查询(幂等去重用)
func (d *MessageDAO) FindByClientMsgID(ctx context.Context, conversationID int64, clientMsgID string) (*model.Message, error) {
if clientMsgID == "" {
return nil, nil
}
var msg model.Message
err := d.db.WithContext(ctx).
Where("conversation_id = ? AND client_msg_id = ?", conversationID, clientMsgID).
First(&msg).Error
if err == gorm.ErrRecordNotFound {
return nil, nil
}
if err != nil {
return nil, err
}
return &msg, nil
}
// GetLatestMessageID 获取会话中最新一条消息的 ID用于标记已读
func (d *MessageDAO) GetLatestMessageID(ctx context.Context, conversationID int64) (int64, error) {
var id int64
err := d.db.WithContext(ctx).
Model(&model.Message{}).
Select("COALESCE(MAX(id), 0)").
Where("conversation_id = ?", conversationID).
Scan(&id).Error
return id, err
}