186 lines
5.7 KiB
Go
186 lines
5.7 KiB
Go
package chat
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
"hfb_sys/backend/internal/model"
|
|
"hfb_sys/backend/internal/modules/chathub"
|
|
"time"
|
|
)
|
|
|
|
func (r *Repository) Messages(ctx context.Context, principal Principal, conversationID uint64, page, pageSize int) (*PaginatedResult, error) {
|
|
page, pageSize = normalizePagination(page, pageSize)
|
|
db := r.db.WithContext(ctx)
|
|
|
|
// 管理员可以查看任意会话的消息,普通用户需要是 participant
|
|
if principal.Type != "admin" {
|
|
if _, err := r.findParticipant(db, principal, conversationID, false); err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
// 管理员需要验证会话存在
|
|
var count int64
|
|
if err := db.Model(&model.ChatConversation{}).Where("id = ?", conversationID).Count(&count).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
if count == 0 {
|
|
return nil, ErrConversationNotFound
|
|
}
|
|
}
|
|
|
|
var total int64
|
|
if err := db.Model(&model.ChatMessage{}).Where("conversation_id = ?", conversationID).Count(&total).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
offset := (page - 1) * pageSize
|
|
var rows []model.ChatMessage
|
|
if err := db.Where("conversation_id = ?", conversationID).
|
|
Order("id ASC").
|
|
Offset(offset).
|
|
Limit(pageSize).
|
|
Find(&rows).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
items, err := r.toMessageDTOs(ctx, principal, rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &PaginatedResult{Items: items, Total: total, Page: page, PageSize: pageSize}, nil
|
|
}
|
|
func (r *Repository) SendMessage(ctx context.Context, principal Principal, conversationID uint64, req SendMessageRequest) (*MessageDTO, error) {
|
|
var messageID uint64
|
|
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
var conversation model.ChatConversation
|
|
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&conversation, conversationID).Error; err != nil {
|
|
return err
|
|
}
|
|
if conversation.Status != "active" {
|
|
return ErrPermissionDenied
|
|
}
|
|
|
|
var senderRole string
|
|
var participant *model.ChatParticipant
|
|
|
|
// 管理员可以在任意会话发送消息,无需是 participant
|
|
if principal.Type == "admin" {
|
|
// 尝试查找管理员的 participant 记录
|
|
var p model.ChatParticipant
|
|
err := tx.Where("conversation_id = ? AND participant_type = ? AND participant_id = ?",
|
|
conversationID, principal.Type, principal.ID).First(&p).Error
|
|
if err == nil {
|
|
// 管理员是 participant,使用其角色
|
|
participant = &p
|
|
senderRole = p.Role
|
|
} else if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
// 管理员不是 participant,使用特殊角色 "admin"
|
|
senderRole = "admin"
|
|
} else {
|
|
return err
|
|
}
|
|
} else {
|
|
// 普通用户必须是 participant
|
|
p, err := r.findParticipant(tx, principal, conversationID, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
participant = p
|
|
senderRole = p.Role
|
|
}
|
|
|
|
message := model.ChatMessage{
|
|
ConversationID: conversation.ID,
|
|
SenderType: principal.Type,
|
|
SenderID: principal.ID,
|
|
SenderRole: senderRole,
|
|
ContentType: "text",
|
|
Content: req.Content,
|
|
AttachmentURLS: encodeStringList(req.AttachmentURLS),
|
|
}
|
|
if err := tx.Create(&message).Error; err != nil {
|
|
return err
|
|
}
|
|
conversation.LastMessageID = &message.ID
|
|
conversation.LastMessagePreview = messagePreview(message.Content, req.AttachmentURLS)
|
|
conversation.LastMessageAt = &message.CreatedAt
|
|
if err := tx.Save(&conversation).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
// 更新 participant 的已读时间(仅当是 participant 时)
|
|
if participant != nil {
|
|
now := time.Now()
|
|
if err := tx.Model(participant).Update("last_read_at", now).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
messageID = message.ID
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var message model.ChatMessage
|
|
if err := r.db.WithContext(ctx).First(&message, messageID).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
items, err := r.toMessageDTOs(ctx, principal, []model.ChatMessage{message})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(items) == 0 {
|
|
return nil, ErrConversationNotFound
|
|
}
|
|
// 推送新消息事件给会话中的在线参与者
|
|
if r.hub != nil {
|
|
msg := items[0]
|
|
r.hub.NotifyConversation(conversationID, &chathub.ChatEvent{
|
|
Type: "new_message",
|
|
ConversationID: conversationID,
|
|
Message: &chathub.MessageData{
|
|
ID: msg.ID,
|
|
ConversationID: msg.ConversationID,
|
|
SenderType: msg.SenderType,
|
|
SenderID: msg.SenderID,
|
|
SenderRole: msg.SenderRole,
|
|
SenderName: msg.SenderName,
|
|
ContentType: msg.ContentType,
|
|
Content: msg.Content,
|
|
AttachmentURLS: msg.AttachmentURLS,
|
|
CreatedAt: msg.CreatedAt.Format(time.RFC3339),
|
|
},
|
|
})
|
|
}
|
|
return &items[0], nil
|
|
}
|
|
func (r *Repository) MarkRead(ctx context.Context, principal Principal, conversationID uint64) error {
|
|
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
// 管理员可以不是 participant,直接返回成功
|
|
if principal.Type == "admin" {
|
|
// 尝试查找 participant 记录,如果有就更新
|
|
var participant model.ChatParticipant
|
|
err := tx.Where("conversation_id = ? AND participant_type = ? AND participant_id = ?",
|
|
conversationID, principal.Type, principal.ID).First(&participant).Error
|
|
if err == nil {
|
|
// 有 participant 记录,更新已读时间
|
|
now := time.Now()
|
|
return tx.Model(&participant).Update("last_read_at", now).Error
|
|
} else if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
// 没有 participant 记录,直接返回成功(管理员无需记录已读)
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
// 普通用户必须是 participant
|
|
participant, err := r.findParticipant(tx, principal, conversationID, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
now := time.Now()
|
|
return tx.Model(participant).Update("last_read_at", now).Error
|
|
})
|
|
}
|