diff --git a/internal/api/room_handler.go b/internal/api/room_handler.go index 1230af6..d18f1e8 100644 --- a/internal/api/room_handler.go +++ b/internal/api/room_handler.go @@ -643,14 +643,20 @@ func ChangeMemberRoleHandler(c *gin.Context) { memberID := c.Param("user_id") var req struct { - Role int8 `json:"role" binding:"required"` + Role *int8 `json:"role"` // 使用指针类型,允许 0 值(普通成员) } if err := c.ShouldBindJSON(&req); err != nil { utils.BadRequest(c, "参数错误: "+err.Error()) return } - if err := service.RoomSvc.ChangeMemberRole(roomID, operatorID.(string), memberID, req.Role); err != nil { + // 手动验证 role 是否提供 + if req.Role == nil { + utils.BadRequest(c, "参数错误: role 字段必须提供") + return + } + + if err := service.RoomSvc.ChangeMemberRole(roomID, operatorID.(string), memberID, *req.Role); err != nil { utils.BadRequest(c, err.Error()) return } diff --git a/internal/api/user_handler.go b/internal/api/user_handler.go index df91dba..7ce7ccd 100644 --- a/internal/api/user_handler.go +++ b/internal/api/user_handler.go @@ -5,6 +5,7 @@ package api import ( + "encoding/json" "fmt" "math/rand" "strconv" @@ -105,8 +106,8 @@ func CreateUserHandler(c *gin.Context) { */ func UpdateUserHandler(c *gin.Context) { var req struct { - ID string `json:"id" binding:"required"` - Updates map[string]interface{} `json:"updates" binding:"required"` + ID string `json:"id" binding:"required"` + Updates map[string]interface{} `json:"updates" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { @@ -119,9 +120,70 @@ func UpdateUserHandler(c *gin.Context) { return } + // 如果更新了头像或名称,通知好友 + if service.ContactSvc != nil && service.ChatSvc != nil { + if _, hasName := req.Updates["name"]; hasName { + go notifyFriendsOfProfileUpdate(req.ID, req.Updates) + } else if _, hasAvatar := req.Updates["avatar"]; hasAvatar { + go notifyFriendsOfProfileUpdate(req.ID, req.Updates) + } + } + utils.Success(c, "更新成功") } +/** + * notifyFriendsOfProfileUpdate + * 功能:通知好友用户信息更新 + */ +func notifyFriendsOfProfileUpdate(userID string, updates map[string]interface{}) { + if service.ContactSvc == nil || service.ChatSvc == nil || service.UserSvc == nil { + return + } + + // 获取更新后的用户信息 + user, err := service.UserSvc.GetUserByID(userID) + if err != nil { + return + } + + // 获取好友列表 + friendIDs, err := service.ContactSvc.GetFriendUserIDs(userID) + if err != nil || len(friendIDs) == 0 { + return + } + + // 构建通知消息 + notifExtra := map[string]interface{}{ + "type": "profile_update", + "user_id": userID, + "name": user.Name, + "avatar": user.Avatar, + "updates": updates, + } + notifExtraJSON, _ := json.Marshal(notifExtra) + + // 发送给所有好友 + for _, friendID := range friendIDs { + notifMsg := model.ChatMessage{ + RoomID: userID, + SenderUserID: userID, + ReceiverUserID: friendID, + MessageType: model.MessageTypeFriendNotif, + Content: user.Name + " 更新了个人资料", + Extra: string(notifExtraJSON), + } + + // 不持久化此类通知,直接推送 + pushMsg := model.WsPayload{ + RequestType: "receive_message", + Data: notifMsg, + } + msgBytes, _ := json.Marshal(pushMsg) + service.ChatSvc.DispatchMessage(friendID, msgBytes) + } +} + /** * DeleteUserHandler * 功能:删除用户 @@ -152,4 +214,3 @@ func generateUserID() string { random := rand.Intn(1000000) return fmt.Sprintf("user_%d_%d", timestamp, random) } - diff --git a/internal/service/contact_service.go b/internal/service/contact_service.go index 5eb13a2..7ca0897 100644 --- a/internal/service/contact_service.go +++ b/internal/service/contact_service.go @@ -53,7 +53,7 @@ func (s *ContactService) AddFriend(fromUserID, toUserID, message string) error { // 检查是否已有待处理的申请 var existingRequest model.FriendRequest - result = s.DB.Where("from_user_id = ? AND to_user_id = ? AND status = ?", + result = s.DB.Where("from_user_id = ? AND to_user_id = ? AND status = ?", fromUserID, toUserID, "pending").First(&existingRequest) if result.Error == nil { return errors.New("已发送过好友申请") @@ -79,7 +79,7 @@ func (s *ContactService) AddFriend(fromUserID, toUserID, message string) error { */ func (s *ContactService) GetFriendRequests(userID string) ([]model.FriendRequest, error) { var requests []model.FriendRequest - result := s.DB.Where("(to_user_id = ? AND status = ?) OR (from_user_id = ? AND status = ?)", + result := s.DB.Where("(to_user_id = ? AND status = ?) OR (from_user_id = ? AND status = ?)", userID, "pending", userID, "rejected"). Order("created_at DESC"). Find(&requests) @@ -127,7 +127,7 @@ func (s *ContactService) AcceptFriendRequest(requestID uint, userID string) erro room := model.ChatRoom{ RoomID: roomID, RoomType: "p2p", - OwnerID: "0", // 单聊群主为0 + OwnerID: "0", // 单聊群主为0 CreatorID: request.ToUserID, // 接受者为创建者 } if err := tx.Create(&room).Error; err != nil { @@ -411,22 +411,22 @@ func (s *ContactService) GetContactsWithUserInfo(userID string) ([]map[string]in // 组合数据,包含 user 对象 item := map[string]interface{}{ - "id": contact.ContactID, - "user_id": contact.ContactID, + "id": contact.ContactID, + "user_id": contact.ContactID, "contact_user_id": contact.ContactID, - "user": user, // 包含完整的用户信息对象 - "remark_name": contact.RemarkName, - "room_id": contact.RoomID, - "group_id": contact.GroupID, - "is_top": contact.IsTop, - "is_muted": contact.IsMuted, + "user": user, // 包含完整的用户信息对象 + "remark_name": contact.RemarkName, + "room_id": contact.RoomID, + "group_id": contact.GroupID, + "is_top": contact.IsTop, + "is_muted": contact.IsMuted, "is_special_care": contact.IsSpecialCare, - "is_blocked": contact.IsBlocked, - "last_chat_time": contact.LastChatTime, - "last_message": contact.LastMessage, - "last_msg": contact.LastMessage, - "unread_count": contact.UnreadCount, - "unread": contact.UnreadCount, + "is_blocked": contact.IsBlocked, + "last_chat_time": contact.LastChatTime, + "last_message": contact.LastMessage, + "last_msg": contact.LastMessage, + "unread_count": contact.UnreadCount, + "unread": contact.UnreadCount, } result = append(result, item) } @@ -434,3 +434,20 @@ func (s *ContactService) GetContactsWithUserInfo(userID string) ([]map[string]in return result, nil } +/** + * GetFriendUserIDs + * 功能:获取用户的所有好友ID列表 + */ +func (s *ContactService) GetFriendUserIDs(userID string) ([]string, error) { + var contacts []model.UserContact + if err := s.DB.Where("user_id = ?", userID).Find(&contacts).Error; err != nil { + return nil, err + } + + friendIDs := make([]string, 0, len(contacts)) + for _, contact := range contacts { + friendIDs = append(friendIDs, contact.ContactID) + } + + return friendIDs, nil +} diff --git a/internal/service/room_service.go b/internal/service/room_service.go index fda3fc7..275a822 100644 --- a/internal/service/room_service.go +++ b/internal/service/room_service.go @@ -5,6 +5,7 @@ package service import ( + "encoding/json" "fmt" "strings" "time" @@ -500,9 +501,33 @@ func (s *RoomService) ChangeMemberRole(roomID, operatorID, memberID string, role return fmt.Errorf("不能修改群主自身角色") } - return s.DB.Model(&model.RoomMember{}). + // 获取原角色 + var oldMember model.RoomMember + s.DB.Where("room_id = ? AND user_id = ?", roomID, memberID).First(&oldMember) + oldRole := oldMember.Role + + if err := s.DB.Model(&model.RoomMember{}). Where("room_id = ? AND user_id = ?", roomID, memberID). - Update("role", role).Error + Update("role", role).Error; err != nil { + return err + } + + // 如果是转让群主,需要把原群主降级 + if role == 2 && memberID != room.OwnerID { + // 将原群主降级为管理员 + s.DB.Model(&model.RoomMember{}). + Where("room_id = ? AND user_id = ?", roomID, operatorID). + Update("role", 1) + // 更新房间的 owner_id + s.DB.Model(&model.ChatRoom{}). + Where("room_id = ?", roomID). + Update("owner_id", memberID) + } + + // 发送角色变更通知(异步) + go s.sendRoleChangeNotification(roomID, operatorID, memberID, oldRole, role) + + return nil } /** @@ -636,9 +661,16 @@ func (s *RoomService) MuteGroupMember(roomID, operatorID, memberID string, durat } // 更新禁言状态 - return s.DB.Model(&model.RoomMember{}). + if err := s.DB.Model(&model.RoomMember{}). Where("room_id = ? AND user_id = ?", roomID, memberID). - Update("muted_until", mutedUntil).Error + Update("muted_until", mutedUntil).Error; err != nil { + return err + } + + // 发送群通知(异步) + go s.sendMuteNotification(roomID, operatorID, memberID, duration, mutedUntil) + + return nil } /** @@ -653,3 +685,177 @@ func (s *RoomService) IsGroupMemberMuted(roomID, userID string) (bool, *time.Tim } return member.IsMuted(), member.MutedUntil, nil } + +/** + * sendMuteNotification + * 功能:发送禁言/解除禁言通知给所有群成员 + */ +func (s *RoomService) sendMuteNotification(roomID, operatorID, memberID string, duration int64, mutedUntil *time.Time) { + if ChatSvc == nil || UserSvc == nil { + return + } + + // 获取操作者和被禁言成员信息 + operator, opErr := UserSvc.GetUserByID(operatorID) + member, memErr := UserSvc.GetUserByID(memberID) + if opErr != nil || memErr != nil { + return + } + + // 获取群成员列表 + memberIDs, err := s.GetRoomMembers(roomID) + if err != nil { + return + } + + // 构建通知内容 + var notifContent string + var eventType string + if duration == -1 { + notifContent = fmt.Sprintf("%s 解除了 %s 的禁言", operator.Name, member.Name) + eventType = "member_unmute" + } else if duration == 0 { + notifContent = fmt.Sprintf("%s 将 %s 永久禁言", operator.Name, member.Name) + eventType = "member_mute" + } else { + durationText := formatDuration(duration) + notifContent = fmt.Sprintf("%s 将 %s 禁言 %s", operator.Name, member.Name, durationText) + eventType = "member_mute" + } + + // 构建 extra 数据 + notifExtra := map[string]interface{}{ + "type": eventType, + "room_id": roomID, + "operator": operatorID, + "operator_name": operator.Name, + "target": memberID, + "target_name": member.Name, + "duration": duration, + "muted_until": mutedUntil, + } + notifExtraJSON, _ := json.Marshal(notifExtra) + + // 为所有成员发送群通知 + for _, uid := range memberIDs { + notifMsg := model.ChatMessage{ + RoomID: roomID, + SenderUserID: operatorID, + ReceiverUserID: roomID, + MessageType: model.MessageTypeGroupNotif, + Content: notifContent, + Extra: string(notifExtraJSON), + } + + if err := ChatSvc.DB.Create(¬ifMsg).Error; err == nil { + // 更新会话 + if ConversationSvc != nil { + _ = ConversationSvc.UpsertConversationOnMessage(uid, roomID, roomID, notifMsg, false) + } + + // 推送消息 + pushMsg := model.WsPayload{ + RequestType: "receive_message", + Data: notifMsg, + } + msgBytes, _ := json.Marshal(pushMsg) + ChatSvc.DispatchMessage(uid, msgBytes) + } + } +} + +/** + * formatDuration + * 功能:格式化时长为可读字符串 + */ +func formatDuration(seconds int64) string { + if seconds < 60 { + return fmt.Sprintf("%d秒", seconds) + } else if seconds < 3600 { + return fmt.Sprintf("%d分钟", seconds/60) + } else if seconds < 86400 { + return fmt.Sprintf("%d小时", seconds/3600) + } else { + return fmt.Sprintf("%d天", seconds/86400) + } +} + +/** + * sendRoleChangeNotification + * 功能:发送角色变更通知给所有群成员 + */ +func (s *RoomService) sendRoleChangeNotification(roomID, operatorID, memberID string, oldRole, newRole int8) { + if ChatSvc == nil || UserSvc == nil { + return + } + + // 获取操作者和目标成员信息 + operator, opErr := UserSvc.GetUserByID(operatorID) + member, memErr := UserSvc.GetUserByID(memberID) + if opErr != nil || memErr != nil { + return + } + + // 获取群成员列表 + memberIDs, err := s.GetRoomMembers(roomID) + if err != nil { + return + } + + // 构建通知内容 + var notifContent string + roleNames := map[int8]string{0: "普通成员", 1: "管理员", 2: "群主"} + + if newRole == 2 { + // 转让群主 + notifContent = fmt.Sprintf("%s 将群主转让给了 %s", operator.Name, member.Name) + } else if newRole == 1 && oldRole == 0 { + // 设置管理员 + notifContent = fmt.Sprintf("%s 被设置为管理员", member.Name) + } else if newRole == 0 && oldRole == 1 { + // 取消管理员 + notifContent = fmt.Sprintf("%s 被取消管理员", member.Name) + } else { + notifContent = fmt.Sprintf("%s 的角色变更为 %s", member.Name, roleNames[newRole]) + } + + // 构建 extra 数据 + notifExtra := map[string]interface{}{ + "type": "role_change", + "room_id": roomID, + "operator": operatorID, + "operator_name": operator.Name, + "target": memberID, + "target_name": member.Name, + "old_role": oldRole, + "new_role": newRole, + } + notifExtraJSON, _ := json.Marshal(notifExtra) + + // 为所有成员发送群通知 + for _, uid := range memberIDs { + notifMsg := model.ChatMessage{ + RoomID: roomID, + SenderUserID: operatorID, + ReceiverUserID: roomID, + MessageType: model.MessageTypeGroupNotif, + Content: notifContent, + Extra: string(notifExtraJSON), + } + + if err := ChatSvc.DB.Create(¬ifMsg).Error; err == nil { + // 更新会话 + if ConversationSvc != nil { + _ = ConversationSvc.UpsertConversationOnMessage(uid, roomID, roomID, notifMsg, false) + } + + // 推送消息 + pushMsg := model.WsPayload{ + RequestType: "receive_message", + Data: notifMsg, + } + msgBytes, _ := json.Marshal(pushMsg) + ChatSvc.DispatchMessage(uid, msgBytes) + } + } +}