576 lines
14 KiB
Go
576 lines
14 KiB
Go
/**
|
||
* 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
|
||
}
|