package handler import ( "errors" "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" "with_you/internal/dto" "with_you/internal/model" "with_you/internal/pkg/cursor" "with_you/internal/pkg/response" "with_you/internal/service" ) // GroupHandler 群组处理器 type GroupHandler struct { groupService service.GroupService userService service.UserService } // NewGroupHandler 创建群组处理器 func NewGroupHandler(groupService service.GroupService, userService service.UserService) *GroupHandler { return &GroupHandler{ groupService: groupService, userService: userService, } } // parseUserID 从上下文获取用户ID(UUID格式) func parseUserID(c *gin.Context) string { return c.GetString("user_id") } // parseGroupID 从路径参数获取群组ID func parseGroupID(c *gin.Context) string { return c.Param("id") } // parseUserIDFromPath 从路径参数获取用户ID(UUID格式) func parseUserIDFromPath(c *gin.Context) string { return c.Param("user_id") } // parseAnnouncementID 从路径参数获取公告ID func parseAnnouncementID(c *gin.Context) string { return c.Param("announcement_id") } // ==================== 群组管理 ==================== // CreateGroup 创建群组 // POST /api/groups func (h *GroupHandler) CreateGroup(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } var req dto.CreateGroupRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } group, err := h.groupService.CreateGroup(userID, req.Name, req.Description, req.MemberIDs) if err != nil { response.InternalServerError(c, err.Error()) return } response.Success(c, dto.GroupToResponse(group)) } // GetGroup 获取群组详情 // GET /api/groups/:id func (h *GroupHandler) GetGroup(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "invalid group id") return } group, err := h.groupService.GetGroupByID(groupID) if err != nil { if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } // 实时计算群成员数量 memberCount, _ := h.groupService.GetMemberCount(groupID) // 创建响应并设置实时计算的member_count resp := dto.GroupToResponse(group) resp.MemberCount = memberCount response.Success(c, resp) } // UpdateGroup 更新群组信息 // PUT /api/groups/:id func (h *GroupHandler) UpdateGroup(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "invalid group id") return } var req dto.UpdateGroupRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } updates := make(map[string]any) if req.Name != "" { updates["name"] = req.Name } if req.Description != "" { updates["description"] = req.Description } if req.Avatar != "" { updates["avatar"] = req.Avatar } if err := h.groupService.UpdateGroup(userID, groupID, updates); err != nil { if err == service.ErrNotGroupAdmin { response.Forbidden(c, "没有权限修改群组信息") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } // 获取更新后的群组信息 group, _ := h.groupService.GetGroupByID(groupID) response.Success(c, dto.GroupToResponse(group)) } // DissolveGroup 解散群组 // DELETE /api/groups/:id func (h *GroupHandler) DissolveGroup(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "invalid group id") return } if err := h.groupService.DissolveGroup(userID, groupID); err != nil { if err == service.ErrNotGroupOwner { response.Forbidden(c, "只有群主可以解散群组") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } response.SuccessWithMessage(c, "群组已解散", nil) } // TransferOwner 转让群主 // POST /api/groups/:id/transfer func (h *GroupHandler) TransferOwner(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "invalid group id") return } var req dto.TransferOwnerRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } if err := h.groupService.TransferOwner(userID, groupID, req.NewOwnerID); err != nil { if err == service.ErrNotGroupOwner { response.Forbidden(c, "只有群主可以转让群主") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } if err == service.ErrNotGroupMember { response.BadRequest(c, "新群主必须是群成员") return } response.InternalServerError(c, err.Error()) return } response.SuccessWithMessage(c, "群主已转让", nil) } // GetUserGroups 获取用户的群组列表 // GET /api/groups func (h *GroupHandler) GetUserGroups(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) groups, total, err := h.groupService.GetUserGroups(userID, page, pageSize) if err != nil { response.InternalServerError(c, err.Error()) return } response.Paginated(c, dto.GroupsToResponse(groups), total, page, pageSize) } // ==================== 成员管理 ==================== // InviteMembers 邀请成员加入群组 // POST /api/groups/:id/members/invite func (h *GroupHandler) InviteMembers(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "invalid group id") return } var req dto.InviteMembersRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } if err := h.groupService.InviteMembers(userID, groupID, req.MemberIDs); err != nil { if err == service.ErrNotGroupMember { response.Forbidden(c, "只有群成员可以邀请他人") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } if err == service.ErrGroupFull { response.BadRequest(c, "群已满") return } response.InternalServerError(c, err.Error()) return } response.SuccessWithMessage(c, "邀请成功", nil) } // JoinGroup 加入群组 // POST /api/groups/:id/join func (h *GroupHandler) JoinGroup(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "invalid group id") return } if err := h.groupService.JoinGroup(userID, groupID); err != nil { if err == service.ErrCannotJoin { response.Forbidden(c, "该群不允许加入") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } if err == service.ErrAlreadyMember { response.BadRequest(c, "已经是群成员") return } if err == service.ErrGroupFull { response.BadRequest(c, "群已满") return } response.InternalServerError(c, err.Error()) return } response.SuccessWithMessage(c, "加入成功", nil) } // LeaveGroup 退出群组 // POST /api/groups/:id/leave func (h *GroupHandler) LeaveGroup(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "invalid group id") return } if err := h.groupService.LeaveGroup(userID, groupID); err != nil { if err == service.ErrNotGroupMember { response.BadRequest(c, "不是群成员") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } response.SuccessWithMessage(c, "已退出群组", nil) } // RemoveMember 移除群成员 // DELETE /api/groups/:id/members/:userId func (h *GroupHandler) RemoveMember(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "invalid group id") return } targetUserID := parseUserIDFromPath(c) if targetUserID == "" { response.BadRequest(c, "invalid user id") return } if err := h.groupService.RemoveMember(userID, groupID, targetUserID); err != nil { if err == service.ErrNotGroupAdmin { response.Forbidden(c, "只有群主或管理员可以移除成员") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } if err == service.ErrNotGroupMember { response.BadRequest(c, "该用户不是群成员") return } if err == service.ErrCannotRemoveOwner { response.Forbidden(c, "不能移除群主") return } response.InternalServerError(c, err.Error()) return } response.SuccessWithMessage(c, "已移除成员", nil) } // GetMembers 获取群成员列表 // GET /api/groups/:id/members func (h *GroupHandler) GetMembers(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "invalid group id") return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "50")) members, total, err := h.groupService.GetMembers(groupID, page, pageSize) if err != nil { if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } // 转换为响应格式,并预加载用户信息 result := make([]*dto.GroupMemberResponse, 0, len(members)) for _, member := range members { memberResp := dto.GroupMemberToResponse(&member) // 预加载用户信息 user, _ := h.userService.GetUserByID(c.Request.Context(), member.UserID) if user != nil { memberResp.User = dto.ConvertUserToResponse(user) } result = append(result, memberResp) } response.Paginated(c, result, total, page, pageSize) } // ==================== RESTful Action 端点 ==================== // HandleCreateGroup 创建群组 // POST /api/v1/groups func (h *GroupHandler) HandleCreateGroup(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } var params dto.CreateGroupParams if err := c.ShouldBindJSON(¶ms); err != nil { response.BadRequest(c, err.Error()) return } group, err := h.groupService.CreateGroup(userID, params.Name, params.Description, params.MemberIDs) if err != nil { response.InternalServerError(c, err.Error()) return } response.Success(c, dto.GroupToResponse(group)) } // HandleGetUserGroups 获取用户群组列表 // GET /api/v1/groups func (h *GroupHandler) HandleGetUserGroups(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) groups, total, err := h.groupService.GetUserGroups(userID, page, pageSize) if err != nil { response.InternalServerError(c, err.Error()) return } response.Paginated(c, dto.GroupsToResponse(groups), total, page, pageSize) } // HandleGetMyMemberInfo 获取我在群组中的成员信息 // GET /api/v1/groups/:id/me func (h *GroupHandler) HandleGetMyMemberInfo(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "group_id is required") return } // 获取群组信息 group, err := h.groupService.GetGroupByID(groupID) if err != nil { if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } // 获取当前用户的成员信息 member, err := h.groupService.GetMember(groupID, userID) if err != nil { response.NotFound(c, "不是群成员") return } // 构建响应 memberResp := dto.GroupMemberToResponse(member) // 预加载用户信息 user, _ := h.userService.GetUserByID(c.Request.Context(), userID) if user != nil { memberResp.User = dto.ConvertUserToResponse(user) } // 添加群组禁言状态信息 response.Success(c, map[string]any{ "member": memberResp, "mute_all": group.MuteAll, "is_muted": member.Muted || group.MuteAll, "can_speak": !member.Muted && !group.MuteAll, }) } // HandleDissolveGroup 解散群组 // DELETE /api/v1/groups/:id func (h *GroupHandler) HandleDissolveGroup(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "group_id is required") return } if err := h.groupService.DissolveGroup(userID, groupID); err != nil { if err == service.ErrNotGroupOwner { response.Forbidden(c, "只有群主可以解散群组") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } response.SuccessWithMessage(c, "群组已解散", nil) } // HandleTransferOwner 转让群主 // POST /api/v1/groups/:id/transfer func (h *GroupHandler) HandleTransferOwner(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "group_id is required") return } var params dto.TransferOwnerParams if err := c.ShouldBindJSON(¶ms); err != nil { response.BadRequest(c, err.Error()) return } if params.NewOwnerID == "" { response.BadRequest(c, "new_owner_id is required") return } if err := h.groupService.TransferOwner(userID, groupID, params.NewOwnerID); err != nil { if err == service.ErrNotGroupOwner { response.Forbidden(c, "只有群主可以转让群主") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } if err == service.ErrNotGroupMember { response.BadRequest(c, "新群主必须是群成员") return } response.InternalServerError(c, err.Error()) return } response.SuccessWithMessage(c, "群主已转让", nil) } // HandleInviteMembers 邀请成员加入群组 // POST /api/v1/groups/:id/invitations func (h *GroupHandler) HandleInviteMembers(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "group_id is required") return } var params dto.InviteMembersParams if err := c.ShouldBindJSON(¶ms); err != nil { response.BadRequest(c, err.Error()) return } if err := h.groupService.InviteMembers(userID, groupID, params.MemberIDs); err != nil { if err == service.ErrNotGroupMember { response.Forbidden(c, "只有群成员可以邀请他人") return } if err == service.ErrNotGroupAdmin { response.Forbidden(c, "只有群主或管理员可以邀请他人") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } if err == service.ErrNoEligibleInvitee { response.BadRequest(c, "暂无可邀请对象(需互相关注且未在群内)") return } response.InternalServerError(c, err.Error()) return } response.SuccessWithMessage(c, "邀请请求已处理", nil) } // HandleJoinGroup 加入群组 // POST /api/v1/groups/:id/join-requests func (h *GroupHandler) HandleJoinGroup(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "group_id is required") return } if err := h.groupService.JoinGroup(userID, groupID); err != nil { if err == service.ErrJoinRequestPending { response.SuccessWithMessage(c, "申请已提交,等待群主/管理员审批", nil) return } if err == service.ErrCannotJoin { response.Forbidden(c, "该群不允许加入") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } if err == service.ErrAlreadyMember { response.BadRequest(c, "已经是群成员") return } if err == service.ErrGroupFull { response.BadRequest(c, "群已满") return } response.InternalServerError(c, err.Error()) return } response.SuccessWithMessage(c, "加入成功", nil) } // HandleSetNickname 设置群内昵称 // PUT /api/v1/groups/:id/members/me/nickname func (h *GroupHandler) HandleSetNickname(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "group_id is required") return } var params dto.SetNicknameParams if err := c.ShouldBindJSON(¶ms); err != nil { response.BadRequest(c, err.Error()) return } if err := h.groupService.SetMemberNickname(userID, groupID, params.Nickname); err != nil { if err == service.ErrNotGroupMember { response.BadRequest(c, "不是群成员") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } response.SuccessWithMessage(c, "昵称已更新", nil) } // HandleSetJoinType 设置加群方式 // PUT /api/v1/groups/:id/join-type func (h *GroupHandler) HandleSetJoinType(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "group_id is required") return } var params dto.SetJoinTypeParams if err := c.ShouldBindJSON(¶ms); err != nil { response.BadRequest(c, err.Error()) return } if err := h.groupService.SetJoinType(userID, groupID, params.JoinType); err != nil { if err == service.ErrNotGroupOwner { response.Forbidden(c, "只有群主可以设置加群方式") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } joinTypeStr := "允许任何人加入" switch params.JoinType { case int(model.JoinTypeApproval): joinTypeStr = "需要审批" case int(model.JoinTypeForbidden): joinTypeStr = "不允许加入" } response.SuccessWithMessage(c, "加群方式已更新为: "+joinTypeStr, nil) } // HandleCreateAnnouncement 创建群公告 // POST /api/v1/groups/:id/announcements func (h *GroupHandler) HandleCreateAnnouncement(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "group_id is required") return } var params dto.CreateAnnouncementParams if err := c.ShouldBindJSON(¶ms); err != nil { response.BadRequest(c, err.Error()) return } announcement, err := h.groupService.CreateAnnouncement(userID, groupID, params.Content) if err != nil { if err == service.ErrNotGroupAdmin { response.Forbidden(c, "只有群主或管理员可以发布公告") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } response.Success(c, dto.GroupAnnouncementToResponse(announcement)) } // HandleGetAnnouncements 获取群公告列表 // GET /api/v1/groups/:id/announcements // // 可见性策略:仅群成员可见。非成员返回 403 NOT_GROUP_MEMBER。 func (h *GroupHandler) HandleGetAnnouncements(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "group_id is required") return } // 成员校验:公告仅群成员可见。 if !h.groupService.IsGroupMember(userID, groupID) { response.Forbidden(c, "不是群成员") return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) announcements, total, err := h.groupService.GetAnnouncements(groupID, page, pageSize) if err != nil { if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } response.Paginated(c, dto.GroupAnnouncementsToResponse(announcements), total, page, pageSize) } // HandleDeleteAnnouncement 删除群公告 // DELETE /api/v1/groups/:id/announcements/:announcement_id func (h *GroupHandler) HandleDeleteAnnouncement(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "group_id is required") return } announcementID := parseAnnouncementID(c) if announcementID == "" { response.BadRequest(c, "announcement_id is required") return } if err := h.groupService.DeleteAnnouncement(userID, announcementID); err != nil { if err == service.ErrNotGroupAdmin { response.Forbidden(c, "只有群主或管理员可以删除公告") return } response.InternalServerError(c, err.Error()) return } response.SuccessWithMessage(c, "公告已删除", nil) } // GetMyMemberInfo 获取当前用户在群组中的成员信息 // GET /api/groups/:id/me func (h *GroupHandler) GetMyMemberInfo(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "invalid group id") return } // 获取群组信息 group, err := h.groupService.GetGroupByID(groupID) if err != nil { if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } // 获取当前用户的成员信息 member, err := h.groupService.GetMember(groupID, userID) if err != nil { response.NotFound(c, "不是群成员") return } // 构建响应 memberResp := dto.GroupMemberToResponse(member) // 预加载用户信息 user, _ := h.userService.GetUserByID(c.Request.Context(), userID) if user != nil { memberResp.User = dto.ConvertUserToResponse(user) } // 添加群组禁言状态信息 response.Success(c, map[string]any{ "member": memberResp, "mute_all": group.MuteAll, "is_muted": member.Muted || group.MuteAll, "can_speak": !member.Muted && !group.MuteAll, }) } // SetMemberRole 设置成员角色 // PUT /api/groups/:id/members/:userId/role func (h *GroupHandler) SetMemberRole(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "invalid group id") return } targetUserID := parseUserIDFromPath(c) if targetUserID == "" { response.BadRequest(c, "invalid user id") return } var req dto.SetRoleRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } if err := h.groupService.SetMemberRole(userID, groupID, targetUserID, req.Role); err != nil { if err == service.ErrNotGroupOwner { response.Forbidden(c, "只有群主可以设置成员角色") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } if err == service.ErrNotGroupMember { response.BadRequest(c, "该用户不是群成员") return } response.InternalServerError(c, err.Error()) return } response.SuccessWithMessage(c, "角色已更新", nil) } // SetNickname 设置群内昵称 // PUT /api/groups/:id/nickname func (h *GroupHandler) SetNickname(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "invalid group id") return } var req dto.SetNicknameRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } if err := h.groupService.SetMemberNickname(userID, groupID, req.Nickname); err != nil { if err == service.ErrNotGroupMember { response.BadRequest(c, "不是群成员") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } response.SuccessWithMessage(c, "昵称已更新", nil) } // MuteMember 禁言/解禁成员 // PUT /api/groups/:id/members/:userId/mute func (h *GroupHandler) MuteMember(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "invalid group id") return } targetUserID := parseUserIDFromPath(c) if targetUserID == "" { response.BadRequest(c, "invalid user id") return } var req dto.MuteMemberRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } if err := h.groupService.MuteMember(userID, groupID, targetUserID, req.Muted); err != nil { if err == service.ErrNotGroupAdmin { response.Forbidden(c, "只有群主或管理员可以禁言成员") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } if err == service.ErrNotGroupMember { response.BadRequest(c, "该用户不是群成员") return } if err == service.ErrCannotMuteOwner { response.Forbidden(c, "不能禁言群主") return } response.InternalServerError(c, err.Error()) return } if req.Muted { response.SuccessWithMessage(c, "已禁言该成员", nil) } else { response.SuccessWithMessage(c, "已解除禁言", nil) } } // ==================== 群设置 ==================== // SetMuteAll 设置全员禁言 // PUT /api/groups/:id/mute-all func (h *GroupHandler) SetMuteAll(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "invalid group id") return } var req dto.SetMuteAllRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } if err := h.groupService.SetMuteAll(userID, groupID, req.MuteAll); err != nil { if err == service.ErrNotGroupOwner { response.Forbidden(c, "只有群主可以设置全员禁言") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } if req.MuteAll { response.SuccessWithMessage(c, "已开启全员禁言", nil) } else { response.SuccessWithMessage(c, "已关闭全员禁言", nil) } } // SetJoinType 设置加群方式 // PUT /api/groups/:id/join-type func (h *GroupHandler) SetJoinType(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "invalid group id") return } var req dto.SetJoinTypeRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } if err := h.groupService.SetJoinType(userID, groupID, req.JoinType); err != nil { if err == service.ErrNotGroupOwner { response.Forbidden(c, "只有群主可以设置加群方式") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } joinTypeStr := "允许任何人加入" switch req.JoinType { case int(model.JoinTypeApproval): joinTypeStr = "需要审批" case int(model.JoinTypeForbidden): joinTypeStr = "不允许加入" } response.SuccessWithMessage(c, "加群方式已更新为: "+joinTypeStr, nil) } // ==================== 群公告 ==================== // CreateAnnouncement 创建群公告 // POST /api/groups/:id/announcements func (h *GroupHandler) CreateAnnouncement(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "invalid group id") return } var req dto.CreateAnnouncementRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } announcement, err := h.groupService.CreateAnnouncement(userID, groupID, req.Content) if err != nil { if err == service.ErrNotGroupAdmin { response.Forbidden(c, "只有群主或管理员可以发布公告") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } response.Success(c, dto.GroupAnnouncementToResponse(announcement)) } // GetAnnouncements 获取群公告列表 // GET /api/groups/:id/announcements func (h *GroupHandler) GetAnnouncements(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "invalid group id") return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) announcements, total, err := h.groupService.GetAnnouncements(groupID, page, pageSize) if err != nil { if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } response.Paginated(c, dto.GroupAnnouncementsToResponse(announcements), total, page, pageSize) } // DeleteAnnouncement 删除群公告 // DELETE /api/groups/:id/announcements/:announcementId func (h *GroupHandler) DeleteAnnouncement(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "invalid group id") return } announcementID := parseAnnouncementID(c) if announcementID == "" { response.BadRequest(c, "invalid announcement id") return } if err := h.groupService.DeleteAnnouncement(userID, announcementID); err != nil { if err == service.ErrNotGroupAdmin { response.Forbidden(c, "只有群主或管理员可以删除公告") return } response.InternalServerError(c, err.Error()) return } response.SuccessWithMessage(c, "公告已删除", nil) } // ==================== 游标分页方法 ==================== // GetGroupsByCursor 游标分页获取群组列表 // GET /api/groups/cursor func (h *GroupHandler) GetGroupsByCursor(c *gin.Context) { // 解析游标分页请求 req := h.parseCursorRequest(c) // 调用游标分页服务 result, err := h.groupService.GetGroupsByCursor(c.Request.Context(), req) if err != nil { response.InternalServerError(c, err.Error()) return } // 转换为响应结构 groupResponses := dto.GroupsToResponseFromPtr(result.Items) // 构建游标分页响应 cursorResp := &dto.GroupCursorPageResponse{ Items: groupResponses, NextCursor: result.NextCursor, PrevCursor: result.PrevCursor, HasMore: result.HasMore, } response.Success(c, cursorResp) } // GetUserGroupsByCursor 游标分页获取用户加入的群组列表 // GET /api/users/:id/groups/cursor func (h *GroupHandler) GetUserGroupsByCursor(c *gin.Context) { userID := parseUserIDFromPath(c) if userID == "" { response.Unauthorized(c, "") return } // 解析游标分页请求 req := h.parseCursorRequest(c) // 调用游标分页服务 result, err := h.groupService.GetUserGroupsByCursor(c.Request.Context(), userID, req) if err != nil { response.InternalServerError(c, err.Error()) return } // 转换为响应结构 groupResponses := dto.GroupsToResponseFromPtr(result.Items) // 构建游标分页响应 cursorResp := &dto.GroupCursorPageResponse{ Items: groupResponses, NextCursor: result.NextCursor, PrevCursor: result.PrevCursor, HasMore: result.HasMore, } response.Success(c, cursorResp) } // GetMembersByCursor 游标分页获取群成员列表 // GET /api/groups/:id/members/cursor func (h *GroupHandler) GetMembersByCursor(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "invalid group id") return } // 解析游标分页请求 req := h.parseCursorRequest(c) // 调用游标分页服务 result, err := h.groupService.GetMembersByCursor(c.Request.Context(), groupID, req) if err != nil { if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } // 转换为响应结构 memberResponses := dto.GroupMembersToResponseFromPtr(result.Items) // 构建游标分页响应 cursorResp := &dto.GroupMemberCursorPageResponse{ Items: memberResponses, NextCursor: result.NextCursor, PrevCursor: result.PrevCursor, HasMore: result.HasMore, } response.Success(c, cursorResp) } // GetAnnouncementsByCursor 游标分页获取群公告列表 // GET /api/v1/groups/:id/announcements/cursor // // 可见性策略:仅群成员可见。非成员返回 403 NOT_GROUP_MEMBER。 func (h *GroupHandler) GetAnnouncementsByCursor(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "invalid group id") return } // 成员校验 if !h.groupService.IsGroupMember(userID, groupID) { response.Forbidden(c, "不是群成员") return } // 解析游标分页请求 req := h.parseCursorRequest(c) // 调用游标分页服务 result, err := h.groupService.GetAnnouncementsByCursor(c.Request.Context(), groupID, req) if err != nil { if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } // 转换为响应结构 announcementResponses := dto.GroupAnnouncementsToResponseFromPtr(result.Items) // 构建游标分页响应 cursorResp := &dto.GroupAnnouncementCursorPageResponse{ Items: announcementResponses, NextCursor: result.NextCursor, PrevCursor: result.PrevCursor, HasMore: result.HasMore, } response.Success(c, cursorResp) } // parseCursorRequest 解析游标分页请求参数 func (h *GroupHandler) parseCursorRequest(c *gin.Context) *cursor.PageRequest { cursorStr := c.Query("cursor") direction := cursor.Direction(c.DefaultQuery("direction", "forward")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) return cursor.NewPageRequest(cursorStr, direction, pageSize) } // ==================== RESTful Action 端点 ==================== // HandleSetGroupKick 群组踢人 // POST /api/v1/groups/:id/members/kick func (h *GroupHandler) HandleSetGroupKick(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "group_id is required") return } var params dto.SetGroupKickParams if err := c.ShouldBindJSON(¶ms); err != nil { response.BadRequest(c, err.Error()) return } if params.UserID == "" { response.BadRequest(c, "user_id is required") return } // 使用 RemoveMember 方法 err := h.groupService.RemoveMember(userID, groupID, params.UserID) if err != nil { if err == service.ErrNotGroupAdmin { response.Forbidden(c, "只有群主或管理员可以移除成员") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } if err == service.ErrNotGroupMember { response.BadRequest(c, "该用户不是群成员") return } if err == service.ErrCannotRemoveOwner { response.Forbidden(c, "不能移除群主") return } response.InternalServerError(c, err.Error()) return } response.SuccessWithMessage(c, "已移除成员", nil) } // HandleSetGroupBan 群组单人禁言 // POST /api/v1/groups/:id/members/ban func (h *GroupHandler) HandleSetGroupBan(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "group_id is required") return } var params dto.SetGroupBanParams if err := c.ShouldBindJSON(¶ms); err != nil { response.BadRequest(c, err.Error()) return } if params.UserID == "" { response.BadRequest(c, "user_id is required") return } // duration > 0 或 duration = -1 表示禁言,duration = 0 表示解除禁言 muted := params.Duration != 0 zap.L().Info("开始禁言操作", zap.String("component", "HandleSetGroupBan"), zap.String("userID", userID), zap.String("groupID", groupID), zap.String("targetUserID", params.UserID), zap.Int64("duration", params.Duration), zap.Bool("muted", muted), ) err := h.groupService.MuteMember(userID, groupID, params.UserID, muted) if err != nil { zap.L().Warn("禁言操作失败", zap.String("component", "HandleSetGroupBan"), zap.Error(err), ) } else { zap.L().Info("禁言操作成功", zap.String("component", "HandleSetGroupBan"), ) } if err != nil { if err == service.ErrNotGroupAdmin { response.Forbidden(c, "只有群主或管理员可以禁言成员") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } if err == service.ErrNotGroupMember { response.BadRequest(c, "该用户不是群成员") return } if err == service.ErrCannotMuteOwner { response.Forbidden(c, "不能禁言群主") return } response.InternalServerError(c, err.Error()) return } if muted { response.SuccessWithMessage(c, "已禁言该成员", nil) } else { response.SuccessWithMessage(c, "已解除禁言", nil) } } // HandleSetGroupWholeBan 群组全员禁言 // PUT /api/v1/groups/:id/ban func (h *GroupHandler) HandleSetGroupWholeBan(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "group_id is required") return } var params dto.SetGroupWholeBanParams if err := c.ShouldBindJSON(¶ms); err != nil { response.BadRequest(c, err.Error()) return } err := h.groupService.SetMuteAll(userID, groupID, params.Enable) if err != nil { if err == service.ErrNotGroupOwner { response.Forbidden(c, "只有群主可以设置全员禁言") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } if params.Enable { response.SuccessWithMessage(c, "已开启全员禁言", nil) } else { response.SuccessWithMessage(c, "已关闭全员禁言", nil) } } // HandleSetGroupAdmin 群组设置管理员 // PUT /api/v1/groups/:id/members/:user_id/admin func (h *GroupHandler) HandleSetGroupAdmin(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "group_id is required") return } targetUserID := parseUserIDFromPath(c) if targetUserID == "" { response.BadRequest(c, "user_id is required") return } var params dto.SetGroupAdminParams if err := c.ShouldBindJSON(¶ms); err != nil { response.BadRequest(c, err.Error()) return } // 根据 enable 参数设置角色 role := model.GroupRoleMember if params.Enable { role = model.GroupRoleAdmin } err := h.groupService.SetMemberRole(userID, groupID, targetUserID, role) if err != nil { if err == service.ErrNotGroupOwner { response.Forbidden(c, "只有群主可以设置管理员") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } if err == service.ErrNotGroupMember { response.BadRequest(c, "该用户不是群成员") return } response.InternalServerError(c, err.Error()) return } if params.Enable { response.SuccessWithMessage(c, "已设置为管理员", nil) } else { response.SuccessWithMessage(c, "已取消管理员", nil) } } // HandleSetGroupName 设置群名 // PUT /api/v1/groups/:id/name func (h *GroupHandler) HandleSetGroupName(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "group_id is required") return } var params dto.SetGroupNameParams if err := c.ShouldBindJSON(¶ms); err != nil { response.BadRequest(c, err.Error()) return } if params.GroupName == "" { response.BadRequest(c, "group_name is required") return } updates := map[string]any{ "name": params.GroupName, } err := h.groupService.UpdateGroup(userID, groupID, updates) if err != nil { if err == service.ErrNotGroupAdmin { response.Forbidden(c, "没有权限修改群组信息") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } // 获取更新后的群组信息 group, _ := h.groupService.GetGroupByID(groupID) response.Success(c, dto.GroupToResponse(group)) } // HandleSetGroupAvatar 设置群头像 // PUT /api/v1/groups/:id/avatar func (h *GroupHandler) HandleSetGroupAvatar(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "group_id is required") return } var params dto.SetGroupAvatarParams if err := c.ShouldBindJSON(¶ms); err != nil { response.BadRequest(c, err.Error()) return } if params.Avatar == "" { response.BadRequest(c, "avatar is required") return } updates := map[string]any{ "avatar": params.Avatar, } err := h.groupService.UpdateGroup(userID, groupID, updates) if err != nil { if err == service.ErrNotGroupAdmin { response.Forbidden(c, "没有权限修改群组信息") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } // 获取更新后的群组信息 group, _ := h.groupService.GetGroupByID(groupID) response.Success(c, dto.GroupToResponse(group)) } // HandleSetGroupDescription 设置群描述 // PUT /api/v1/groups/:id/description func (h *GroupHandler) HandleSetGroupDescription(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "group_id is required") return } var params dto.SetGroupDescriptionParams if err := c.ShouldBindJSON(¶ms); err != nil { response.BadRequest(c, err.Error()) return } updates := map[string]any{ "description": params.Description, } err := h.groupService.UpdateGroup(userID, groupID, updates) if err != nil { if err == service.ErrNotGroupAdmin { response.Forbidden(c, "没有权限修改群组信息") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } // 获取更新后的群组信息 group, _ := h.groupService.GetGroupByID(groupID) response.Success(c, dto.GroupToResponse(group)) } // HandleSetGroupLeave 退出群组 // POST /api/v1/groups/:id/leave func (h *GroupHandler) HandleSetGroupLeave(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "group_id is required") return } err := h.groupService.LeaveGroup(userID, groupID) if err != nil { if err == service.ErrNotGroupMember { response.BadRequest(c, "不是群成员") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } response.SuccessWithMessage(c, "已退出群组", nil) } // HandleSetGroupAddRequest 处理加群审批 // POST /api/v1/groups/:id/join-requests/handle func (h *GroupHandler) HandleSetGroupAddRequest(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } var params dto.SetGroupAddRequestParams if err := c.ShouldBindJSON(¶ms); err != nil { response.BadRequest(c, err.Error()) return } if params.Flag == "" { response.BadRequest(c, "flag is required") return } if err := h.groupService.SetGroupAddRequest(userID, params.Flag, params.Approve, params.Reason); err != nil { if err == service.ErrGroupRequestNotFound { response.NotFound(c, "加群申请不存在") return } if err == service.ErrGroupRequestHandled { response.BadRequest(c, "该加群申请已处理") return } if err == service.ErrNotGroupAdmin { response.Forbidden(c, "仅群主或管理员可审批") return } if err == service.ErrGroupFull { response.BadRequest(c, "群已满") return } response.InternalServerError(c, err.Error()) return } if params.Approve { response.SuccessWithMessage(c, "已同意加群申请", nil) return } response.SuccessWithMessage(c, "已拒绝加群申请", nil) } // HandleRespondInvite 处理群邀请响应 // POST /api/v1/groups/:id/join-requests/respond func (h *GroupHandler) HandleRespondInvite(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } var params dto.SetGroupAddRequestParams if err := c.ShouldBindJSON(¶ms); err != nil { response.BadRequest(c, err.Error()) return } if params.Flag == "" { response.BadRequest(c, "flag is required") return } if err := h.groupService.RespondInvite(userID, params.Flag, params.Approve, params.Reason); err != nil { if err == service.ErrGroupRequestNotFound { response.NotFound(c, "邀请不存在") return } if err == service.ErrGroupRequestHandled { response.BadRequest(c, "邀请已处理") return } if err == service.ErrNotRequestTarget { response.Forbidden(c, "无权处理该邀请") return } if err == service.ErrGroupFull { response.BadRequest(c, "群已满") return } response.InternalServerError(c, err.Error()) return } if params.Approve { response.SuccessWithMessage(c, "已接受邀请", nil) return } response.SuccessWithMessage(c, "已拒绝邀请", nil) } // HandleGetGroupInfo 获取群信息 // GET /api/v1/groups/:id // // 可见性策略:成员返回完整详情;非成员仅返回公开字段(name/avatar/description/member_count)。 func (h *GroupHandler) HandleGetGroupInfo(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "group_id is required") return } group, err := h.groupService.GetGroupByID(groupID) if err != nil { if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } // 实时计算群成员数量 memberCount, _ := h.groupService.GetMemberCount(groupID) // 创建响应并设置实时计算的member_count resp := dto.GroupToResponse(group) resp.MemberCount = memberCount // 非成员仅返回公开字段:与成员分支同构(仍是 GroupResponse),但清空敏感字段。 // 与成员分支共用 GroupResponse 结构,依赖完整字段的客户端解析不会因字段缺失出错。 if !h.groupService.IsGroupMember(userID, groupID) { resp.OwnerID = "" resp.MaxMembers = 0 resp.JoinType = 0 resp.MuteAll = false resp.IsMember = false response.Success(c, resp) return } resp.IsMember = true response.Success(c, resp) } // HandleGetGroupMemberList 获取群成员列表 // GET /api/v1/groups/:id/members // // 可见性策略:仅群成员可见。非成员返回 403 NOT_GROUP_MEMBER。 func (h *GroupHandler) HandleGetGroupMemberList(c *gin.Context) { userID := parseUserID(c) if userID == "" { response.Unauthorized(c, "") return } groupID := parseGroupID(c) if groupID == "" { response.BadRequest(c, "group_id is required") return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "50")) // 成员校验在 service 层完成。 members, total, err := h.groupService.GetMembersForUser(userID, groupID, page, pageSize) if err != nil { if errors.Is(err, service.ErrNotGroupMember) { response.Forbidden(c, "不是群成员") return } if errors.Is(err, service.ErrGroupNotFound) { response.NotFound(c, "群组不存在") return } response.InternalServerError(c, err.Error()) return } // 转换为响应格式,并预加载用户信息 result := make([]*dto.GroupMemberResponse, 0, len(members)) for _, member := range members { memberResp := dto.GroupMemberToResponse(&member) // 预加载用户信息 user, _ := h.userService.GetUserByID(c.Request.Context(), member.UserID) if user != nil { memberResp.User = dto.ConvertUserToResponse(user) } result = append(result, memberResp) } response.Paginated(c, result, total, page, pageSize) }