Files
nl-im-service/internal/service/room_service.go
2025-12-05 15:23:04 +08:00

576 lines
14 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 service
* 作用:房间管理服务
*/
package service
import (
"fmt"
"strings"
"time"
"xk-websocket-v2/internal/model"
"xk-websocket-v2/internal/utils"
"gorm.io/gorm"
)
// RoomService 房间服务结构体
type RoomService struct {
DB *gorm.DB
}
// RoomSvc 全局单例
var RoomSvc *RoomService
/**
* InitRoomService
* 功能:初始化房间服务
*/
func InitRoomService(db *gorm.DB) {
RoomSvc = &RoomService{DB: db}
}
/**
* GenerateP2PRoomID
* 功能:生成点对点聊天房间ID
* 规则:将两个用户ID按字母序排序后,用下划线连接
*/
func GenerateP2PRoomID(userID1, userID2 string) string {
if userID1 < userID2 {
return fmt.Sprintf("%s_%s", userID1, userID2)
}
return fmt.Sprintf("%s_%s", userID2, userID1)
}
/**
* GenerateGroupRoomID
* 功能:生成群聊房间ID
* 规则:使用雪花ID生成全局唯一ID
*/
func GenerateGroupRoomID() string {
id, err := utils.NextID()
if err != nil {
// 如果雪花ID生成失败,使用备用方案
return fmt.Sprintf("group_%d", time.Now().UnixNano())
}
return fmt.Sprintf("group_%d", id)
}
/**
* CreateRoom
* 功能:创建房间
*/
func (s *RoomService) CreateRoom(roomType string, members []string, creatorID string) (*model.ChatRoom, error) {
var roomID string
var ownerID string = "0" // 单聊默认为0
if roomType == "p2p" {
if len(members) != 2 {
return nil, fmt.Errorf("点对点房间需要2个成员")
}
roomID = GenerateP2PRoomID(members[0], members[1])
} else {
roomID = GenerateGroupRoomID()
// 群聊时,创建者就是群主
ownerID = creatorID
}
// 检查房间是否已存在
var existingRoom model.ChatRoom
if err := s.DB.Where("room_id = ?", roomID).First(&existingRoom).Error; err == nil {
return &existingRoom, nil
}
// 开始事务
tx := s.DB.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
// 创建房间
room := model.ChatRoom{
RoomID: roomID,
RoomType: roomType,
OwnerID: ownerID,
CreatorID: creatorID,
}
if roomType == "group" {
room.RoomName = "群聊"
}
if err := tx.Create(&room).Error; err != nil {
tx.Rollback()
return nil, err
}
// 创建房间成员记录
for _, userID := range members {
member := model.RoomMember{
RoomID: roomID,
UserID: userID,
}
if err := tx.Create(&member).Error; err != nil {
tx.Rollback()
return nil, err
}
}
if err := tx.Commit().Error; err != nil {
return nil, err
}
return &room, nil
}
/**
* GetRoom
* 功能:获取房间信息
*/
func (s *RoomService) GetRoom(roomID string) (*model.ChatRoom, error) {
var room model.ChatRoom
result := s.DB.Where("room_id = ?", roomID).First(&room)
if result.Error != nil {
return nil, result.Error
}
return &room, nil
}
/**
* GetOrCreateP2PRoom
* 功能:获取或创建点对点房间
*/
func (s *RoomService) GetOrCreateP2PRoom(userID1, userID2 string) (*model.ChatRoom, error) {
roomID := GenerateP2PRoomID(userID1, userID2)
// 尝试获取现有房间
var room model.ChatRoom
if err := s.DB.Where("room_id = ?", roomID).First(&room).Error; err == nil {
return &room, nil
}
// 创建新房间
members := []string{userID1, userID2}
return s.CreateRoom("p2p", members, userID1)
}
/**
* UpdateRoomLastMessage
* 功能:更新房间最后消息
*/
func (s *RoomService) UpdateRoomLastMessage(roomID, message string) error {
now := time.Now()
return s.DB.Model(&model.ChatRoom{}).
Where("room_id = ?", roomID).
Updates(map[string]interface{}{
"last_message": message,
"last_message_time": now,
}).Error
}
/**
* GetRoomMembers
* 功能:获取房间成员列表
*/
func (s *RoomService) GetRoomMembers(roomID string) ([]string, error) {
var members []model.RoomMember
if err := s.DB.Where("room_id = ?", roomID).Find(&members).Error; err != nil {
return nil, err
}
userIDs := make([]string, len(members))
for i, member := range members {
userIDs[i] = member.UserID
}
return userIDs, nil
}
/**
* GetRoomMember
* 功能:获取房间中指定成员的信息
*/
func (s *RoomService) GetRoomMember(roomID, userID string) (*model.RoomMember, error) {
var member model.RoomMember
err := s.DB.Where("room_id = ? AND user_id = ?", roomID, userID).First(&member).Error
if err != nil {
return nil, err
}
return &member, nil
}
/**
* AddRoomMember
* 功能:添加房间成员
*/
func (s *RoomService) AddRoomMember(roomID, userID string) error {
member := model.RoomMember{
RoomID: roomID,
UserID: userID,
}
return s.DB.Create(&member).Error
}
/**
* RemoveRoomMember
* 功能:移除房间成员
*/
func (s *RoomService) RemoveRoomMember(roomID, userID string) error {
return s.DB.Where("room_id = ? AND user_id = ?", roomID, userID).
Delete(&model.RoomMember{}).Error
}
/**
* CreateGroupRoom
* 功能:创建群聊房间,并为成员写入角色(群主/管理员/成员)
*/
func (s *RoomService) CreateGroupRoom(creatorID string, memberIDs []string, name string, avatar string, adminIDs []string) (*model.ChatRoom, error) {
if len(memberIDs) == 0 {
return nil, fmt.Errorf("成员列表不能为空")
}
// 1. 先通过通用的 CreateRoom 创建群聊和基础成员关系(角色默认都是 0)
room, err := s.CreateRoom("group", memberIDs, creatorID)
if err != nil {
return nil, err
}
// 2. 更新群名称和头像(如果提供)
updates := map[string]interface{}{}
if name != "" {
updates["room_name"] = name
}
if avatar != "" {
updates["room_avatar"] = avatar
}
if len(updates) > 0 {
if err := s.DB.Model(room).Updates(updates).Error; err != nil {
return nil, err
}
}
// 3. 为成员设置角色:群主=2 管理员=1 成员=0
roomID := room.RoomID
// 创建者作为群主
if err := s.DB.Model(&model.RoomMember{}).
Where("room_id = ? AND user_id = ?", roomID, creatorID).
Update("role", 2).Error; err != nil {
return nil, err
}
// 管理员列表去重并排除群主
adminSet := map[string]struct{}{}
for _, id := range adminIDs {
if id == "" || id == creatorID {
continue
}
adminSet[id] = struct{}{}
}
if len(adminSet) > 0 {
for adminID := range adminSet {
if err := s.DB.Model(&model.RoomMember{}).
Where("room_id = ? AND user_id = ?", roomID, adminID).
Update("role", 1).Error; err != nil {
return nil, err
}
}
}
return room, nil
}
/**
* ListGroupMembers
* 功能:返回群成员及其角色信息(包含用户信息)
*/
func (s *RoomService) ListGroupMembers(roomID string) ([]model.RoomMember, error) {
var members []model.RoomMember
if err := s.DB.Where("room_id = ?", roomID).
Preload("User").
Order("role DESC, joined_at ASC").
Find(&members).Error; err != nil {
return nil, err
}
return members, nil
}
/**
* SearchGroupMembers
* 功能:搜索群成员(根据用户名或昵称)
*/
func (s *RoomService) SearchGroupMembers(roomID, keyword string) ([]model.RoomMember, error) {
var members []model.RoomMember
keywordPattern := "%" + keyword + "%"
if err := s.DB.Where("room_id = ?", roomID).
Preload("User", "name LIKE ? OR email LIKE ? OR phone LIKE ?", keywordPattern, keywordPattern, keywordPattern).
Order("role DESC, joined_at ASC").
Find(&members).Error; err != nil {
return nil, err
}
// 过滤掉 User 为 nil 或不符合搜索条件的成员
filtered := make([]model.RoomMember, 0)
for _, m := range members {
if m.User != nil {
// 检查用户名、邮箱、手机号是否匹配
if strings.Contains(strings.ToLower(m.User.Name), strings.ToLower(keyword)) ||
strings.Contains(strings.ToLower(m.User.Email), strings.ToLower(keyword)) ||
strings.Contains(strings.ToLower(m.User.Phone), strings.ToLower(keyword)) ||
(m.Nickname != "" && strings.Contains(strings.ToLower(m.Nickname), strings.ToLower(keyword))) {
filtered = append(filtered, m)
}
}
}
return filtered, nil
}
/**
* ListUserGroups
* 功能:查询用户所在的所有群聊房间
* 返回:群聊列表和用户在每个群中的角色信息
*/
func (s *RoomService) ListUserGroups(userID string) ([]model.ChatRoom, []model.RoomMember, error) {
var rooms []model.ChatRoom
var members []model.RoomMember
// 查询用户所在的所有群聊
err := s.DB.
Joins("JOIN room_members ON room_members.room_id = chat_rooms.room_id").
Where("room_members.user_id = ? AND chat_rooms.room_type = ?", userID, "group").
Find(&rooms).Error
if err != nil {
return nil, nil, err
}
// 获取用户在这些群中的角色信息
if len(rooms) > 0 {
roomIDs := make([]string, len(rooms))
for i, room := range rooms {
roomIDs[i] = room.RoomID
}
err = s.DB.Where("room_id IN ? AND user_id = ?", roomIDs, userID).Find(&members).Error
if err != nil {
return nil, nil, err
}
}
return rooms, members, nil
}
/**
* AddGroupMembers
* 功能:批量添加群成员(仅群主或管理员可操作)
*/
func (s *RoomService) AddGroupMembers(roomID, operatorID string, memberIDs []string) error {
if len(memberIDs) == 0 {
return nil
}
// 权限校验:必须是群主或管理员
isAdmin, err := s.isGroupAdminOrOwner(roomID, operatorID)
if err != nil {
return err
}
if !isAdmin {
return fmt.Errorf("无权限添加成员")
}
for _, uid := range memberIDs {
if uid == "" {
continue
}
member := model.RoomMember{
RoomID: roomID,
UserID: uid,
Role: 0, // 默认成员
}
if err := s.DB.Create(&member).Error; err != nil {
return err
}
}
return nil
}
/**
* RemoveGroupMember
* 功能:踢出群成员(群主或管理员),或作为封装调用 RemoveRoomMember
*/
func (s *RoomService) RemoveGroupMember(roomID, operatorID, memberID string) error {
// 群主或管理员才能踢人
isAdmin, err := s.isGroupAdminOrOwner(roomID, operatorID)
if err != nil {
return err
}
if !isAdmin {
return fmt.Errorf("无权限移除成员")
}
// 不允许移除群主
room, err := s.GetRoom(roomID)
if err != nil {
return err
}
if memberID == room.OwnerID {
return fmt.Errorf("不能移除群主")
}
return s.RemoveRoomMember(roomID, memberID)
}
/**
* QuitGroup
* 功能:成员主动退出群聊
*/
func (s *RoomService) QuitGroup(roomID, userID string) error {
room, err := s.GetRoom(roomID)
if err != nil {
return err
}
// 群主不能直接退群
if userID == room.OwnerID {
return fmt.Errorf("群主不能直接退出群聊,请先转让群主或解散群")
}
return s.RemoveRoomMember(roomID, userID)
}
/**
* DissolveGroup
* 功能:群主解散群聊
*/
func (s *RoomService) DissolveGroup(roomID, ownerID string) error {
room, err := s.GetRoom(roomID)
if err != nil {
return err
}
// 只有群主可以解散群
if ownerID != room.OwnerID {
return fmt.Errorf("只有群主可以解散群聊")
}
// 删除所有成员记录
if err := s.DB.Where("room_id = ?", roomID).Delete(&model.RoomMember{}).Error; err != nil {
return fmt.Errorf("删除成员记录失败: %v", err)
}
// 可选:标记房间为已解散(或直接删除房间)
// 这里选择删除房间记录
if err := s.DB.Where("room_id = ?", roomID).Delete(&model.ChatRoom{}).Error; err != nil {
return fmt.Errorf("删除房间记录失败: %v", err)
}
return nil
}
/**
* ChangeMemberRole
* 功能:修改成员角色(仅群主可设置/取消管理员)
*/
func (s *RoomService) ChangeMemberRole(roomID, operatorID, memberID string, role int8) error {
// 仅允许 0/1/2
if role < 0 || role > 2 {
return fmt.Errorf("非法角色值")
}
room, err := s.GetRoom(roomID)
if err != nil {
return err
}
// 只有群主可以调整角色
if operatorID != room.OwnerID {
return fmt.Errorf("只有群主可以调整成员角色")
}
// 防止将群主本人的角色修改为其他值
if memberID == room.OwnerID && role != 2 {
return fmt.Errorf("不能修改群主自身角色")
}
return s.DB.Model(&model.RoomMember{}).
Where("room_id = ? AND user_id = ?", roomID, memberID).
Update("role", role).Error
}
/**
* IsGroupAdminOrOwner
* 功能:对外暴露的权限判断:是否为群主或管理员
*/
func (s *RoomService) IsGroupAdminOrOwner(roomID, userID string) (bool, error) {
return s.isGroupAdminOrOwner(roomID, userID)
}
/**
* IsGroupOwner
* 功能:判断用户是否为群主
*/
func (s *RoomService) IsGroupOwner(roomID, userID string) (bool, error) {
room, err := s.GetRoom(roomID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return false, nil
}
return false, err
}
return room.OwnerID == userID, nil
}
/**
* isGroupAdminOrOwner
* 功能:判断用户是否是群主或管理员
*/
func (s *RoomService) isGroupAdminOrOwner(roomID, userID string) (bool, error) {
if userID == "" {
return false, nil
}
var member model.RoomMember
err := s.DB.Where("room_id = ? AND user_id = ?", roomID, userID).First(&member).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return false, nil
}
return false, err
}
return member.Role == 1 || member.Role == 2, nil
}
/**
* GetGroupAnnouncement
* 功能:获取群公告
*/
func (s *RoomService) GetGroupAnnouncement(roomID string) (string, error) {
var room model.ChatRoom
err := s.DB.Where("room_id = ? AND room_type = ?", roomID, "group").First(&room).Error
if err != nil {
return "", err
}
return room.Announcement, nil
}
/**
* UpdateGroupAnnouncement
* 功能:更新群公告(仅群主或管理员可操作)
*/
func (s *RoomService) UpdateGroupAnnouncement(roomID, userID, announcement string) error {
// 权限校验:必须是群主或管理员
isAdmin, err := s.isGroupAdminOrOwner(roomID, userID)
if err != nil {
return err
}
if !isAdmin {
return fmt.Errorf("无权限更新群公告")
}
// 更新群公告
return s.DB.Model(&model.ChatRoom{}).
Where("room_id = ? AND room_type = ?", roomID, "group").
Update("announcement", announcement).Error
}