diff --git a/internal/api/room_handler.go b/internal/api/room_handler.go index b663604..ffcd6ed 100644 --- a/internal/api/room_handler.go +++ b/internal/api/room_handler.go @@ -5,6 +5,8 @@ package api import ( + "encoding/json" + "fmt" "log" "strconv" "xk-websocket-v2/internal/model" @@ -114,6 +116,63 @@ func CreateChatGroupHandler(c *gin.Context) { return } + // 为所有成员创建会话记录 + if service.ConversationSvc != nil { + if err := service.ConversationSvc.EnsureGroupConversations(room.RoomID); err != nil { + log.Printf("⚠️ 创建群聊会话失败 (roomID: %s): %v", room.RoomID, err) + } + } + + // 发送系统消息通知所有成员 + if service.ChatSvc != nil && service.UserSvc != nil { + // 获取创建者信息 + creator, err := service.UserSvc.GetUserByID(creatorID) + if err == nil { + // 构建系统消息内容 + content := fmt.Sprintf("%s 邀请你加入了群聊", creator.Name) + extraData := map[string]interface{}{ + "type": "group_created", + "room_id": room.RoomID, + "creator": creatorID, + } + extraJSON, _ := json.Marshal(extraData) + + // 为每个成员发送系统消息 + for _, memberID := range members { + if memberID != creatorID { + // 创建系统消息 + systemMsg := model.ChatMessage{ + RoomID: room.RoomID, + SenderUserID: creatorID, + ReceiverUserID: room.RoomID, // 群聊时使用 roomID + MessageType: model.MessageTypeSystem, + Content: content, + Extra: string(extraJSON), + } + + // 持久化消息 + if err := service.ChatSvc.DB.Create(&systemMsg).Error; err != nil { + log.Printf("❌ 发送群聊系统消息失败 (memberID: %s): %v", memberID, err) + continue + } + + // 更新会话 + if service.ConversationSvc != nil { + _ = service.ConversationSvc.UpsertConversationOnMessage(memberID, room.RoomID, room.RoomID, systemMsg, false) + } + + // 通过 WebSocket 推送消息 + pushMsg := model.WsPayload{ + RequestType: "receive_message", + Data: systemMsg, + } + msgBytes, _ := json.Marshal(pushMsg) + service.ChatSvc.DispatchMessage(memberID, msgBytes) + } + } + } + } + utils.SuccessWithData(c, room, "创建群聊成功") } @@ -249,8 +308,19 @@ func GetGroupInfoHandler(c *gin.Context) { */ func ListGroupMembersHandler(c *gin.Context) { roomID := c.Param("room_id") + keyword := c.Query("keyword") + + var members []model.RoomMember + var err error + + if keyword != "" { + // 如果有搜索关键词,使用搜索方法 + members, err = service.RoomSvc.SearchGroupMembers(roomID, keyword) + } else { + // 否则返回所有成员 + members, err = service.RoomSvc.ListGroupMembers(roomID) + } - members, err := service.RoomSvc.ListGroupMembers(roomID) if err != nil { utils.InternalError(c, "查询失败") return @@ -310,6 +380,97 @@ func RemoveGroupMemberHandler(c *gin.Context) { return } + // 发送群通知和系统消息 + if service.ChatSvc != nil && service.UserSvc != nil { + // 获取操作者和被移除成员信息 + operator, opErr := service.UserSvc.GetUserByID(operatorID.(string)) + member, memErr := service.UserSvc.GetUserByID(memberID) + + if opErr == nil && memErr == nil { + // 获取群成员列表 + memberIDs, mErr := service.RoomSvc.GetRoomMembers(roomID) + if mErr == nil { + // 构建群通知消息 + notifContent := fmt.Sprintf("%s 将 %s 移出了群聊", operator.Name, member.Name) + notifExtra := map[string]interface{}{ + "type": "member_remove", + "room_id": roomID, + "operator": operatorID.(string), + "target": memberID, + "operator_name": operator.Name, + "target_name": member.Name, + } + notifExtraJSON, _ := json.Marshal(notifExtra) + + // 构建系统消息 + systemContent := fmt.Sprintf("你被 %s 移出了群聊", operator.Name) + systemExtra := map[string]interface{}{ + "type": "member_removed", + "room_id": roomID, + "operator": operatorID.(string), + } + systemExtraJSON, _ := json.Marshal(systemExtra) + + // 为所有成员发送群通知 + for _, uid := range memberIDs { + if uid == memberID { + continue // 被移除的成员不接收群通知 + } + + // 群通知消息 + notifMsg := model.ChatMessage{ + RoomID: roomID, + SenderUserID: operatorID.(string), + ReceiverUserID: roomID, + MessageType: model.MessageTypeGroupNotif, + Content: notifContent, + Extra: string(notifExtraJSON), + } + + if err := service.ChatSvc.DB.Create(¬ifMsg).Error; err == nil { + // 更新会话 + if service.ConversationSvc != nil { + _ = service.ConversationSvc.UpsertConversationOnMessage(uid, roomID, roomID, notifMsg, false) + } + + // 推送消息 + pushMsg := model.WsPayload{ + RequestType: "receive_message", + Data: notifMsg, + } + msgBytes, _ := json.Marshal(pushMsg) + service.ChatSvc.DispatchMessage(uid, msgBytes) + } + } + + // 为被移除的成员发送系统消息 + systemMsg := model.ChatMessage{ + RoomID: roomID, + SenderUserID: operatorID.(string), + ReceiverUserID: roomID, + MessageType: model.MessageTypeSystem, + Content: systemContent, + Extra: string(systemExtraJSON), + } + + if err := service.ChatSvc.DB.Create(&systemMsg).Error; err == nil { + // 更新会话 + if service.ConversationSvc != nil { + _ = service.ConversationSvc.UpsertConversationOnMessage(memberID, roomID, roomID, systemMsg, false) + } + + // 推送消息 + pushMsg := model.WsPayload{ + RequestType: "receive_message", + Data: systemMsg, + } + msgBytes, _ := json.Marshal(pushMsg) + service.ChatSvc.DispatchMessage(memberID, msgBytes) + } + } + } + } + utils.Success(c, "已移除成员") } @@ -427,6 +588,55 @@ func QuitGroupHandler(c *gin.Context) { return } + // 发送群通知 + if service.ChatSvc != nil && service.UserSvc != nil { + // 获取退出成员信息 + member, memErr := service.UserSvc.GetUserByID(userID.(string)) + + if memErr == nil { + // 获取群成员列表(退出后剩余的成员) + memberIDs, mErr := service.RoomSvc.GetRoomMembers(roomID) + if mErr == nil { + // 构建群通知消息 + notifContent := fmt.Sprintf("%s 退出了群聊", member.Name) + notifExtra := map[string]interface{}{ + "type": "member_leave", + "room_id": roomID, + "target": userID.(string), + "target_name": member.Name, + } + notifExtraJSON, _ := json.Marshal(notifExtra) + + // 为所有剩余成员发送群通知 + for _, uid := range memberIDs { + notifMsg := model.ChatMessage{ + RoomID: roomID, + SenderUserID: userID.(string), + ReceiverUserID: roomID, + MessageType: model.MessageTypeGroupNotif, + Content: notifContent, + Extra: string(notifExtraJSON), + } + + if err := service.ChatSvc.DB.Create(¬ifMsg).Error; err == nil { + // 更新会话 + if service.ConversationSvc != nil { + _ = service.ConversationSvc.UpsertConversationOnMessage(uid, roomID, roomID, notifMsg, false) + } + + // 推送消息 + pushMsg := model.WsPayload{ + RequestType: "receive_message", + Data: notifMsg, + } + msgBytes, _ := json.Marshal(pushMsg) + service.ChatSvc.DispatchMessage(uid, msgBytes) + } + } + } + } + } + utils.Success(c, "已退出群聊") } diff --git a/internal/service/chat_service.go b/internal/service/chat_service.go index 45cd138..11c06ce 100644 --- a/internal/service/chat_service.go +++ b/internal/service/chat_service.go @@ -132,6 +132,36 @@ func (s *ChatService) HandleUserMessage(senderClient *manager.Client, req *model room, err := RoomSvc.GetRoom(req.RoomID) if err == nil && room.RoomType == "group" { isGroupMessage = true + + // 双重防护:验证发送者是否为群成员 + memberIDs, mErr := RoomSvc.GetRoomMembers(req.RoomID) + if mErr != nil { + log.Printf("❌ 获取群成员失败: %v", mErr) + return + } + + isMember := false + for _, memberID := range memberIDs { + if memberID == senderClient.UserID { + isMember = true + break + } + } + + if !isMember { + log.Printf("❌ 用户 %s 不是群 %s 的成员,拒绝发送消息", senderClient.UserID, req.RoomID) + // 发送错误消息给发送者 + errorMsg := model.WsPayload{ + RequestType: "error", + Data: map[string]interface{}{ + "message": "您已被移出群聊,无法发送消息", + "code": "FORBIDDEN", + }, + } + errorBytes, _ := json.Marshal(errorMsg) + s.DispatchMessage(senderClient.UserID, errorBytes) + return + } } } diff --git a/internal/service/room_service.go b/internal/service/room_service.go index 32596b0..ab3aecf 100644 --- a/internal/service/room_service.go +++ b/internal/service/room_service.go @@ -6,6 +6,7 @@ package service import ( "fmt" + "strings" "time" "xk-websocket-v2/internal/model" "xk-websocket-v2/internal/utils" @@ -296,6 +297,38 @@ func (s *RoomService) ListGroupMembers(roomID string) ([]model.RoomMember, error 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 * 功能:查询用户所在的所有群聊房间