369 lines
10 KiB
Go
369 lines
10 KiB
Go
/**
|
||
* package service
|
||
* 作用:会话列表(最近聊天)管理服务
|
||
*/
|
||
package service
|
||
|
||
import (
|
||
"log"
|
||
"time"
|
||
|
||
"xk-websocket-v2/internal/model"
|
||
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// ConversationService 会话服务结构体
|
||
type ConversationService struct {
|
||
DB *gorm.DB
|
||
}
|
||
|
||
// ConversationSvc 全局单例
|
||
var ConversationSvc *ConversationService
|
||
|
||
// InitConversationService 初始化会话服务
|
||
func InitConversationService(db *gorm.DB) {
|
||
ConversationSvc = &ConversationService{DB: db}
|
||
}
|
||
|
||
/**
|
||
* GetConversations
|
||
* 功能:获取用户的最近会话列表
|
||
*/
|
||
func (s *ConversationService) GetConversations(userID string) ([]model.ChatConversation, error) {
|
||
var list []model.ChatConversation
|
||
err := s.DB.Where("user_id = ?", userID).
|
||
Order("is_top DESC, last_time DESC, id DESC").
|
||
Find(&list).Error
|
||
if err != nil {
|
||
return list, err
|
||
}
|
||
|
||
// 手动加载关联信息
|
||
for i := range list {
|
||
if list[i].Type == 1 { // 私聊:加载目标用户信息
|
||
var user model.User
|
||
if err := s.DB.Where("id = ?", list[i].TargetID).First(&user).Error; err == nil {
|
||
list[i].TargetUser = &user
|
||
}
|
||
} else if list[i].Type == 2 { // 群聊:加载群信息
|
||
var room model.ChatRoom
|
||
if err := s.DB.Where("room_id = ?", list[i].RoomID).First(&room).Error; err == nil {
|
||
list[i].Room = &room
|
||
} else {
|
||
// 记录警告:群聊信息查询失败
|
||
log.Printf("⚠️ 群聊信息查询失败 (room_id: %s): %v", list[i].RoomID, err)
|
||
}
|
||
}
|
||
}
|
||
|
||
return list, nil
|
||
}
|
||
|
||
/**
|
||
* UpsertConversationOnMessage
|
||
* 功能:在发送/接收消息时更新会话记录
|
||
* @param userID 会话所属用户
|
||
* @param targetID 好友ID或群ID(群聊时targetID等于roomID)
|
||
* @param roomID 房间ID
|
||
* @param msg 已持久化的消息
|
||
* @param isSender 是否为发送方(发送方通常不增加未读数)
|
||
*/
|
||
func (s *ConversationService) UpsertConversationOnMessage(userID, targetID, roomID string, msg model.ChatMessage, isSender bool) error {
|
||
if s == nil {
|
||
return nil
|
||
}
|
||
|
||
// 判断是否为群聊:优先通过查询数据库确认房间类型,其次通过 roomID 格式判断
|
||
conversationType := 1 // 默认私聊
|
||
if roomID != "" {
|
||
// 情况1:targetID == roomID,说明是群聊(群聊时 targetID 就是 roomID)
|
||
if targetID == roomID {
|
||
conversationType = 2 // 群聊
|
||
} else {
|
||
// 情况2:查询数据库确认房间类型(用于兼容旧数据或特殊情况)
|
||
var room model.ChatRoom
|
||
if err := s.DB.Where("room_id = ?", roomID).First(&room).Error; err == nil {
|
||
if room.RoomType == "group" {
|
||
conversationType = 2 // 群聊
|
||
}
|
||
} else if len(roomID) > 6 && roomID[:6] == "group_" {
|
||
// 如果查询失败,回退到格式判断
|
||
conversationType = 2 // 群聊
|
||
}
|
||
}
|
||
}
|
||
|
||
var conv model.ChatConversation
|
||
tx := s.DB.Where("user_id = ? AND target_id = ? AND type = ?", userID, targetID, conversationType).First(&conv)
|
||
now := time.Now()
|
||
|
||
// 计算摘要:群聊时包含发送者信息
|
||
summary := buildMessageSummary(msg, conversationType == 2, msg.SenderUserID)
|
||
|
||
if tx.Error != nil {
|
||
if tx.Error == gorm.ErrRecordNotFound {
|
||
// 新建会话
|
||
conv = model.ChatConversation{
|
||
UserID: userID,
|
||
TargetID: targetID,
|
||
RoomID: roomID,
|
||
Type: conversationType, // 支持私聊(1)和群聊(2)
|
||
LastMessage: summary,
|
||
LastTime: now,
|
||
}
|
||
// 接收方增加未读
|
||
if !isSender {
|
||
conv.UnreadCount = 1
|
||
}
|
||
|
||
// 群聊时,target_id 是群ID,不是用户ID,需要临时禁用外键检查
|
||
if conversationType == 2 {
|
||
s.DB.Exec("SET FOREIGN_KEY_CHECKS = 0")
|
||
err := s.DB.Create(&conv).Error
|
||
s.DB.Exec("SET FOREIGN_KEY_CHECKS = 1")
|
||
return err
|
||
}
|
||
|
||
return s.DB.Create(&conv).Error
|
||
}
|
||
return tx.Error
|
||
}
|
||
|
||
// 已存在会话则更新
|
||
updates := map[string]interface{}{
|
||
"room_id": roomID,
|
||
"last_message": summary,
|
||
"last_time": now,
|
||
}
|
||
if !isSender {
|
||
updates["unread_count"] = conv.UnreadCount + 1
|
||
}
|
||
|
||
return s.DB.Model(&model.ChatConversation{}).
|
||
Where("id = ?", conv.ID).
|
||
Updates(updates).Error
|
||
}
|
||
|
||
/**
|
||
* ResetUnread
|
||
* 功能:清空某个目标的未读消息数
|
||
*/
|
||
func (s *ConversationService) ResetUnread(userID, targetID string) error {
|
||
return s.DB.Model(&model.ChatConversation{}).
|
||
Where("user_id = ? AND target_id = ?", userID, targetID).
|
||
Update("unread_count", 0).Error
|
||
}
|
||
|
||
/**
|
||
* UpdateConversationFlags
|
||
* 功能:更新会话标记(置顶、免打扰、特别关心等)
|
||
*/
|
||
func (s *ConversationService) UpdateConversationFlags(userID, targetID string, updates map[string]interface{}) error {
|
||
return s.DB.Model(&model.ChatConversation{}).
|
||
Where("user_id = ? AND target_id = ?", userID, targetID).
|
||
Updates(updates).Error
|
||
}
|
||
|
||
/**
|
||
* DeleteConversation
|
||
* 功能:删除单个会话记录(不删除聊天记录)
|
||
*/
|
||
func (s *ConversationService) DeleteConversation(userID, targetID string) error {
|
||
return s.DB.Where("user_id = ? AND target_id = ?", userID, targetID).
|
||
Delete(&model.ChatConversation{}).Error
|
||
}
|
||
|
||
/**
|
||
* EnsureGroupConversations
|
||
* 功能:确保群的所有成员都有该群的会话记录
|
||
* @param roomID 群ID
|
||
*/
|
||
func (s *ConversationService) EnsureGroupConversations(roomID string) error {
|
||
if s == nil {
|
||
return nil
|
||
}
|
||
|
||
// 获取群的所有成员
|
||
memberIDs, err := RoomSvc.GetRoomMembers(roomID)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 获取群信息,确认是群聊
|
||
room, err := RoomSvc.GetRoom(roomID)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if room.RoomType != "group" {
|
||
return nil // 不是群聊,不需要创建会话
|
||
}
|
||
|
||
// 为每个成员创建会话(如果不存在)
|
||
now := time.Now()
|
||
for _, memberID := range memberIDs {
|
||
// 检查是否已有会话
|
||
var existingConv model.ChatConversation
|
||
err := s.DB.Where("user_id = ? AND target_id = ? AND type = ?", memberID, roomID, 2).First(&existingConv).Error
|
||
|
||
if err != nil {
|
||
if err == gorm.ErrRecordNotFound {
|
||
// 不存在,创建新会话
|
||
// 临时禁用外键检查,因为 target_id 是群ID,不是用户ID
|
||
s.DB.Exec("SET FOREIGN_KEY_CHECKS = 0")
|
||
insertSQL := `INSERT INTO chat_conversations (user_id, target_id, room_id, type, is_top, is_muted, is_special_care, unread_count, last_message, last_time, created_at, updated_at)
|
||
VALUES (?, ?, ?, 2, false, false, false, 0, '', ?, ?, ?)`
|
||
createErr := s.DB.Exec(insertSQL, memberID, roomID, roomID, now, now, now).Error
|
||
s.DB.Exec("SET FOREIGN_KEY_CHECKS = 1")
|
||
if createErr != nil {
|
||
log.Printf("❌ 创建群聊会话失败 (userID: %s, roomID: %s): %v", memberID, roomID, createErr)
|
||
}
|
||
} else {
|
||
log.Printf("❌ 查询群聊会话失败 (userID: %s, roomID: %s): %v", memberID, roomID, err)
|
||
}
|
||
}
|
||
// 如果已存在,不做任何操作
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// buildMessageSummary 根据消息类型构建会话摘要
|
||
// isGroupChat: 是否为群聊
|
||
// senderUserID: 发送者用户ID(群聊时需要)
|
||
func buildMessageSummary(msg model.ChatMessage, isGroupChat bool, senderUserID string) string {
|
||
var content string
|
||
switch msg.MessageType {
|
||
case 1:
|
||
content = "[图片]"
|
||
case 2:
|
||
content = "[语音]"
|
||
case 3:
|
||
content = "[视频]"
|
||
case 6, 7:
|
||
content = "[通话]"
|
||
case 8:
|
||
content = "[文件]"
|
||
default:
|
||
if msg.Content == "" {
|
||
content = "[空消息]"
|
||
} else {
|
||
if len([]rune(msg.Content)) > 50 {
|
||
content = string([]rune(msg.Content)[:50]) + "..."
|
||
} else {
|
||
content = msg.Content
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果是群聊,需要包含发送者信息
|
||
if isGroupChat && senderUserID != "" {
|
||
var sender model.User
|
||
if err := ConversationSvc.DB.Where("id = ?", senderUserID).First(&sender).Error; err == nil {
|
||
// 格式:用户名:消息内容
|
||
return sender.Name + ":" + content
|
||
}
|
||
// 如果查询失败,仍然返回内容(不包含用户名)
|
||
}
|
||
|
||
return content
|
||
}
|
||
|
||
/**
|
||
* GetOrCreateConversationByRoom
|
||
* 功能:根据 room_id 获取或创建会话
|
||
* @param userID 用户ID
|
||
* @param roomID 房间ID
|
||
* @returns 会话对象和错误
|
||
*/
|
||
func (s *ConversationService) GetOrCreateConversationByRoom(userID, roomID string) (*model.ChatConversation, error) {
|
||
if s == nil {
|
||
return nil, gorm.ErrRecordNotFound
|
||
}
|
||
|
||
// 先查询房间信息(必须存在才能创建会话)
|
||
var room model.ChatRoom
|
||
roomErr := s.DB.Where("room_id = ?", roomID).First(&room).Error
|
||
|
||
if roomErr != nil {
|
||
// 房间不存在,返回错误
|
||
return nil, gorm.ErrRecordNotFound
|
||
}
|
||
|
||
var conversationType int
|
||
var targetID string
|
||
|
||
// 根据房间类型判断会话类型
|
||
if room.RoomType == "group" {
|
||
conversationType = 2 // 群聊
|
||
targetID = roomID // 群聊时 targetID = roomID
|
||
} else {
|
||
conversationType = 1 // 私聊
|
||
// 私聊需要确定 targetID(对方用户ID)
|
||
// 从 room_members 中查找另一个成员
|
||
var members []model.RoomMember
|
||
if err := s.DB.Where("room_id = ?", roomID).Find(&members).Error; err == nil {
|
||
for _, member := range members {
|
||
if member.UserID != userID {
|
||
targetID = member.UserID
|
||
break
|
||
}
|
||
}
|
||
}
|
||
if targetID == "" {
|
||
return nil, gorm.ErrRecordNotFound
|
||
}
|
||
}
|
||
|
||
// 查询会话
|
||
var conv model.ChatConversation
|
||
tx := s.DB.Where("user_id = ? AND target_id = ? AND type = ?", userID, targetID, conversationType).First(&conv)
|
||
|
||
if tx.Error != nil {
|
||
if tx.Error == gorm.ErrRecordNotFound {
|
||
// 会话不存在,根据房间信息创建新会话
|
||
conv = model.ChatConversation{
|
||
UserID: userID,
|
||
TargetID: targetID,
|
||
RoomID: roomID,
|
||
Type: conversationType,
|
||
LastMessage: "",
|
||
LastTime: time.Now(),
|
||
UnreadCount: 0,
|
||
}
|
||
|
||
// 群聊时,target_id 是群ID,不是用户ID,需要临时禁用外键检查
|
||
if conversationType == 2 {
|
||
s.DB.Exec("SET FOREIGN_KEY_CHECKS = 0")
|
||
err := s.DB.Create(&conv).Error
|
||
s.DB.Exec("SET FOREIGN_KEY_CHECKS = 1")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
} else {
|
||
if err := s.DB.Create(&conv).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
} else {
|
||
return nil, tx.Error
|
||
}
|
||
}
|
||
|
||
// 加载关联信息
|
||
if conv.Type == 1 { // 私聊:加载目标用户信息
|
||
var user model.User
|
||
if err := s.DB.Where("id = ?", conv.TargetID).First(&user).Error; err == nil {
|
||
conv.TargetUser = &user
|
||
} else {
|
||
log.Printf("⚠️ 用户信息查询失败 (user_id: %s): %v", conv.TargetID, err)
|
||
}
|
||
} else if conv.Type == 2 { // 群聊:加载群信息
|
||
conv.Room = &room
|
||
}
|
||
|
||
return &conv, nil
|
||
}
|
||
|
||
|