Commit b814a87361ce5a49c3757b89c755b30f78efa220
1 parent
ea29dea4
✨ feat(group): 添加群成员管理功能
实现群成员的邀请、退出和移除功能,支持群主对成员的管理操作。新增相关提示信息和错误码,优化群成员更新逻辑,确保群主无法退出群聊。 BREAKING CHANGE: 群解散相关逻辑已调整为群删除,更新了相关消息推送类型
Showing
13 changed files
with
202 additions
and
170 deletions
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java
| ... | ... | @@ -35,6 +35,8 @@ public interface ErrorCodeConstants { |
| 35 | 35 | ErrorCode GROUP_MEMBER_NOT_EXISTS = new ErrorCode(1_040_500_000, "群成员不存在"); |
| 36 | 36 | ErrorCode GROUP_MEMBER_NOT_IN_GROUP = new ErrorCode(1_040_500_001, "您已不在该群中"); |
| 37 | 37 | ErrorCode GROUP_MEMBER_ALREADY_QUIT = new ErrorCode(1_040_500_002, "您已退出该群"); |
| 38 | + ErrorCode GROUP_OWNER_CANNOT_QUIT = new ErrorCode(1_040_500_003, "您是群主,不可退出群聊"); | |
| 39 | + ErrorCode GROUP_CANNOT_REMOVE_SELF = new ErrorCode(1_040_500_004, "不能移除自己"); | |
| 38 | 40 | |
| 39 | 41 | // ========== 好友 (1-040-600-000) ========== |
| 40 | 42 | ErrorCode FRIEND_NOT_EXISTS = new ErrorCode(1_040_600_000, "好友关系不存在"); | ... | ... |
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/ImCommonConstants.java
| ... | ... | @@ -51,5 +51,13 @@ public interface ImCommonConstants { |
| 51 | 51 | * 群解散系统提示 |
| 52 | 52 | */ |
| 53 | 53 | String GROUP_DISSOLVE_TIP_MESSAGE = "'{}'解散了群聊"; |
| 54 | + /** | |
| 55 | + * 退群系统提示 | |
| 56 | + */ | |
| 57 | + String GROUP_QUIT_TIP_MESSAGE = "您已退出群聊"; | |
| 58 | + /** | |
| 59 | + * 踢出群系统提示 | |
| 60 | + */ | |
| 61 | + String GROUP_REMOVE_TIP_MESSAGE = "您已被移出群聊"; | |
| 54 | 62 | |
| 55 | 63 | } | ... | ... |
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java
| ... | ... | @@ -34,7 +34,7 @@ public enum ImMessageTypeEnum implements ArrayValuable<Integer> { |
| 34 | 34 | |
| 35 | 35 | GROUP_CREATE(200, "群创建"), // 暂无 |
| 36 | 36 | GROUP_UPDATE(201, "群信息变更"), // 暂无 |
| 37 | - GROUP_DISSOLVE(202, "群解散"); // 暂无 | |
| 37 | + GROUP_DELETE(202, "群删除"); // 暂无(解散/退群/踢出均用此类型) | |
| 38 | 38 | |
| 39 | 39 | public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImMessageTypeEnum::getType).toArray(Integer[]::new); |
| 40 | 40 | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java
| ... | ... | @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 5 | 5 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; |
| 6 | 6 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupRespVO; |
| 7 | 7 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; |
| 8 | +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; | |
| 8 | 9 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; |
| 9 | 10 | import cn.iocoder.yudao.module.im.service.group.ImGroupService; |
| 10 | 11 | import io.swagger.v3.oas.annotations.Operation; |
| ... | ... | @@ -30,6 +31,8 @@ public class ImGroupController { |
| 30 | 31 | @Resource |
| 31 | 32 | private ImGroupService imGroupService; |
| 32 | 33 | |
| 34 | + // ==================== 群的写操作 ==================== | |
| 35 | + | |
| 33 | 36 | @PostMapping("/create") |
| 34 | 37 | @Operation(summary = "创建群") |
| 35 | 38 | @PreAuthorize("@ss.hasPermission('im:group:create')") |
| ... | ... | @@ -55,6 +58,8 @@ public class ImGroupController { |
| 55 | 58 | return success(true); |
| 56 | 59 | } |
| 57 | 60 | |
| 61 | + // ==================== 群的读操作 ==================== | |
| 62 | + | |
| 58 | 63 | @GetMapping("/get") |
| 59 | 64 | @Operation(summary = "获得群") |
| 60 | 65 | @Parameter(name = "id", description = "编号", required = true, example = "1024") |
| ... | ... | @@ -71,4 +76,31 @@ public class ImGroupController { |
| 71 | 76 | return success(BeanUtils.toBean(groups, ImGroupRespVO.class)); |
| 72 | 77 | } |
| 73 | 78 | |
| 79 | + // ==================== 群成员的写操作 ==================== | |
| 80 | + | |
| 81 | + @PostMapping("/invite") | |
| 82 | + @Operation(summary = "邀请用户加入群") | |
| 83 | + @PreAuthorize("@ss.hasPermission('im:group:update')") | |
| 84 | + public CommonResult<Long> inviteGroupMember(@Valid @RequestBody ImGroupMemberInviteReqVO inviteReqVO) { | |
| 85 | + return success(imGroupService.inviteGroupMember(getLoginUserId(), inviteReqVO)); | |
| 86 | + } | |
| 87 | + | |
| 88 | + @DeleteMapping("/quit") | |
| 89 | + @Operation(summary = "退出群") | |
| 90 | + @Parameter(name = "groupId", description = "群编号", required = true) | |
| 91 | + @PreAuthorize("@ss.hasPermission('im:group:update')") | |
| 92 | + public CommonResult<Boolean> quitGroup(@RequestParam("groupId") Long groupId) { | |
| 93 | + imGroupService.quitGroup(groupId, getLoginUserId()); | |
| 94 | + return success(true); | |
| 95 | + } | |
| 96 | + | |
| 97 | + @DeleteMapping("/kicking") | |
| 98 | + @Operation(summary = "移除群成员") | |
| 99 | + @PreAuthorize("@ss.hasPermission('im:group:update')") | |
| 100 | + public CommonResult<Boolean> removeGroupMember(@RequestParam("groupId") Long groupId, | |
| 101 | + @RequestParam("memberUserId") Long memberUserId) { | |
| 102 | + imGroupService.removeGroupMember(groupId, memberUserId, getLoginUserId()); | |
| 103 | + return success(true); | |
| 104 | + } | |
| 105 | + | |
| 74 | 106 | } | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupMemberController.java
| ... | ... | @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.im.controller.admin.group; |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.util.collection.MapUtils; |
| 5 | 5 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 6 | -import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; | |
| 7 | 6 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRespVO; |
| 8 | 7 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; |
| 9 | 8 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| ... | ... | @@ -24,6 +23,7 @@ import java.util.Map; |
| 24 | 23 | |
| 25 | 24 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 26 | 25 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| 26 | +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; | |
| 27 | 27 | |
| 28 | 28 | @Tag(name = "管理后台 - 群成员") |
| 29 | 29 | @RestController |
| ... | ... | @@ -32,32 +32,16 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. |
| 32 | 32 | public class ImGroupMemberController { |
| 33 | 33 | |
| 34 | 34 | @Resource |
| 35 | - private ImGroupMemberService imGroupMemberService; | |
| 35 | + private ImGroupMemberService groupMemberService; | |
| 36 | 36 | |
| 37 | 37 | @Resource |
| 38 | 38 | private AdminUserApi adminUserApi; |
| 39 | 39 | |
| 40 | - @PostMapping("/invite") | |
| 41 | - @Operation(summary = "邀请用户加入群") | |
| 42 | - @PreAuthorize("@ss.hasPermission('im:group-member:create')") | |
| 43 | - public CommonResult<Long> inviteGroupMember(@Valid @RequestBody ImGroupMemberInviteReqVO inviteReqVO) { | |
| 44 | - return success(imGroupMemberService.inviteGroupMember(inviteReqVO)); | |
| 45 | - } | |
| 46 | - | |
| 47 | 40 | @PutMapping("/update") |
| 48 | 41 | @Operation(summary = "更新群成员") |
| 49 | 42 | @PreAuthorize("@ss.hasPermission('im:group-member:update')") |
| 50 | 43 | public CommonResult<Boolean> updateGroupMember(@Valid @RequestBody ImGroupMemberUpdateReqVO updateReqVO) { |
| 51 | - imGroupMemberService.updateGroupMember(updateReqVO); | |
| 52 | - return success(true); | |
| 53 | - } | |
| 54 | - | |
| 55 | - @DeleteMapping("/remove") | |
| 56 | - @Operation(summary = "移除群成员(踢人)") | |
| 57 | - @Parameter(name = "id", description = "群成员编号", required = true) | |
| 58 | - @PreAuthorize("@ss.hasPermission('im:group-member:delete')") | |
| 59 | - public CommonResult<Boolean> removeGroupMember(@RequestParam("id") Long id) { | |
| 60 | - imGroupMemberService.removeGroupMember(id); | |
| 44 | + groupMemberService.updateGroupMember(getLoginUserId(), updateReqVO); | |
| 61 | 45 | return success(true); |
| 62 | 46 | } |
| 63 | 47 | |
| ... | ... | @@ -66,7 +50,7 @@ public class ImGroupMemberController { |
| 66 | 50 | @Parameter(name = "id", description = "编号", required = true, example = "1024") |
| 67 | 51 | @PreAuthorize("@ss.hasPermission('im:group-member:query')") |
| 68 | 52 | public CommonResult<ImGroupMemberRespVO> getGroupMember(@RequestParam("id") Long id) { |
| 69 | - ImGroupMemberDO groupMember = imGroupMemberService.getGroupMember(id); | |
| 53 | + ImGroupMemberDO groupMember = groupMemberService.getGroupMember(id); | |
| 70 | 54 | return success(BeanUtils.toBean(groupMember, ImGroupMemberRespVO.class)); |
| 71 | 55 | } |
| 72 | 56 | |
| ... | ... | @@ -74,7 +58,7 @@ public class ImGroupMemberController { |
| 74 | 58 | @Operation(summary = "获得指定群的成员列表") |
| 75 | 59 | @Parameter(name = "groupId", description = "群编号", required = true, example = "1024") |
| 76 | 60 | public CommonResult<List<ImGroupMemberRespVO>> getGroupMemberList(@RequestParam("groupId") Long groupId) { |
| 77 | - List<ImGroupMemberDO> members = imGroupMemberService.getGroupMemberListByGroupId(groupId); | |
| 61 | + List<ImGroupMemberDO> members = groupMemberService.getGroupMemberListByGroupId(groupId); | |
| 78 | 62 | // 批量聚合 AdminUser 信息(昵称 / 头像) |
| 79 | 63 | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( |
| 80 | 64 | convertList(members, ImGroupMemberDO::getUserId)); | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberPageReqVO.java deleted
| 1 | -package cn.iocoder.yudao.module.im.controller.admin.group.vo.member; | |
| 2 | - | |
| 3 | -import cn.iocoder.yudao.framework.common.pojo.PageParam; | |
| 4 | -import io.swagger.v3.oas.annotations.media.Schema; | |
| 5 | -import lombok.Data; | |
| 6 | -import lombok.EqualsAndHashCode; | |
| 7 | -import lombok.ToString; | |
| 8 | -import org.springframework.format.annotation.DateTimeFormat; | |
| 9 | - | |
| 10 | -import java.time.LocalDateTime; | |
| 11 | - | |
| 12 | -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; | |
| 13 | - | |
| 14 | -@Schema(description = "管理后台 - 群成员分页 Request VO") | |
| 15 | -@Data | |
| 16 | -@EqualsAndHashCode(callSuper = true) | |
| 17 | -@ToString(callSuper = true) | |
| 18 | -public class ImGroupMemberPageReqVO extends PageParam { | |
| 19 | - | |
| 20 | - @Schema(description = "群编号", example = "13279") | |
| 21 | - private Long groupId; | |
| 22 | - | |
| 23 | - @Schema(description = "用户编号", example = "21730") | |
| 24 | - private Long userId; | |
| 25 | - | |
| 26 | - @Schema(description = "成员状态", example = "0") | |
| 27 | - private Integer status; | |
| 28 | - | |
| 29 | - @Schema(description = "创建时间") | |
| 30 | - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | |
| 31 | - private LocalDateTime[] createTime; | |
| 32 | - | |
| 33 | -} | |
| 34 | 0 | \ No newline at end of file |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberSaveReqVO.java deleted
| 1 | -package cn.iocoder.yudao.module.im.controller.admin.group.vo.member; | |
| 2 | - | |
| 3 | -import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | -import jakarta.validation.constraints.NotNull; | |
| 5 | -import lombok.Data; | |
| 6 | - | |
| 7 | -@Schema(description = "管理后台 - 群成员新增/修改 Request VO") | |
| 8 | -@Data | |
| 9 | -public class ImGroupMemberSaveReqVO { | |
| 10 | - | |
| 11 | - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17071") | |
| 12 | - private Long id; | |
| 13 | - | |
| 14 | - @Schema(description = "群编号", example = "13279") | |
| 15 | - private Long groupId; | |
| 16 | - | |
| 17 | - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21730") | |
| 18 | - @NotNull(message = "用户编号不能为空") | |
| 19 | - private Long userId; | |
| 20 | - | |
| 21 | - @Schema(description = "是否免打扰") | |
| 22 | - private Boolean muted; | |
| 23 | - | |
| 24 | -} | |
| 25 | 0 | \ No newline at end of file |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberUpdateReqVO.java
| ... | ... | @@ -3,14 +3,22 @@ package cn.iocoder.yudao.module.im.controller.admin.group.vo.member; |
| 3 | 3 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | 4 | import jakarta.validation.constraints.NotNull; |
| 5 | 5 | import lombok.Data; |
| 6 | +import lombok.experimental.Accessors; | |
| 6 | 7 | |
| 7 | 8 | @Schema(description = "管理后台 - 群成员更新 Request VO") |
| 8 | 9 | @Data |
| 10 | +@Accessors(chain = true) | |
| 9 | 11 | public class ImGroupMemberUpdateReqVO { |
| 10 | 12 | |
| 11 | - @Schema(description = "群成员编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17071") | |
| 12 | - @NotNull(message = "群成员编号不能为空") | |
| 13 | - private Long id; | |
| 13 | + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 14 | + @NotNull(message = "群编号不能为空") | |
| 15 | + private Long groupId; | |
| 16 | + | |
| 17 | + @Schema(description = "群内昵称", example = "芋头") | |
| 18 | + private String displayUserName; | |
| 19 | + | |
| 20 | + @Schema(description = "群名备注", example = "公司群") | |
| 21 | + private String displayGroupName; | |
| 14 | 22 | |
| 15 | 23 | @Schema(description = "是否免打扰") |
| 16 | 24 | private Boolean muted; | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java
| 1 | 1 | package cn.iocoder.yudao.module.im.service.group; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; | |
| 4 | 3 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; |
| 5 | 4 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| 6 | 5 | import jakarta.validation.Valid; |
| ... | ... | @@ -16,28 +15,6 @@ import java.util.List; |
| 16 | 15 | public interface ImGroupMemberService { |
| 17 | 16 | |
| 18 | 17 | /** |
| 19 | - * 邀请用户加入群 | |
| 20 | - * | |
| 21 | - * @param inviteReqVO 邀请信息 | |
| 22 | - * @return 编号 | |
| 23 | - */ | |
| 24 | - Long inviteGroupMember(@Valid ImGroupMemberInviteReqVO inviteReqVO); | |
| 25 | - | |
| 26 | - /** | |
| 27 | - * 更新群成员信息 | |
| 28 | - * | |
| 29 | - * @param updateReqVO 更新信息 | |
| 30 | - */ | |
| 31 | - void updateGroupMember(@Valid ImGroupMemberUpdateReqVO updateReqVO); | |
| 32 | - | |
| 33 | - /** | |
| 34 | - * 删除群成员 | |
| 35 | - * | |
| 36 | - * @param id 编号 | |
| 37 | - */ | |
| 38 | - void removeGroupMember(Long id); | |
| 39 | - | |
| 40 | - /** | |
| 41 | 18 | * 获得群成员 |
| 42 | 19 | * |
| 43 | 20 | * @param id 编号 |
| ... | ... | @@ -101,17 +78,24 @@ public interface ImGroupMemberService { |
| 101 | 78 | ImGroupMemberDO validateMemberInGroup(Long groupId, Long userId); |
| 102 | 79 | |
| 103 | 80 | /** |
| 104 | - * 更新群成员的展示信息(群内昵称、群名备注) | |
| 81 | + * 更新群成员信息(群内昵称、群名备注、免打扰等) | |
| 105 | 82 | * <p> |
| 106 | 83 | * 内部会校验用户是否为群的有效成员 |
| 107 | 84 | * |
| 108 | - * @param groupId 群编号 | |
| 109 | - * @param userId 用户编号 | |
| 110 | - * @param displayUserName 群内昵称(null 则不更新) | |
| 111 | - * @param displayGroupName 群名备注(null 则不更新) | |
| 85 | + * @param userId 当前登录用户编号 | |
| 86 | + * @param updateReqVO 更新信息 | |
| 87 | + */ | |
| 88 | + void updateGroupMember(Long userId, @Valid ImGroupMemberUpdateReqVO updateReqVO); | |
| 89 | + | |
| 90 | + /** | |
| 91 | + * 移除指定群成员(设置为 DISABLE 状态) | |
| 92 | + * <p> | |
| 93 | + * 用于退群、踢出场景 | |
| 94 | + * | |
| 95 | + * @param groupId 群编号 | |
| 96 | + * @param userId 用户编号 | |
| 112 | 97 | */ |
| 113 | - void updateGroupMember(Long groupId, Long userId, | |
| 114 | - String displayUserName, String displayGroupName); | |
| 98 | + void removeGroupMember(Long groupId, Long userId); | |
| 115 | 99 | |
| 116 | 100 | /** |
| 117 | 101 | * 移除群的全部成员(设置为 DISABLE 状态) | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java
| ... | ... | @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.im.service.group; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 5 | -import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; | |
| 6 | 5 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; |
| 7 | 6 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| 8 | 7 | import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper; |
| ... | ... | @@ -14,7 +13,6 @@ import java.time.LocalDateTime; |
| 14 | 13 | import java.util.List; |
| 15 | 14 | |
| 16 | 15 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 17 | -import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.GROUP_MEMBER_NOT_EXISTS; | |
| 18 | 16 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.GROUP_MEMBER_NOT_IN_GROUP; |
| 19 | 17 | |
| 20 | 18 | /** |
| ... | ... | @@ -30,43 +28,6 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { |
| 30 | 28 | private ImGroupMemberMapper imGroupMemberMapper; |
| 31 | 29 | |
| 32 | 30 | @Override |
| 33 | - public Long inviteGroupMember(ImGroupMemberInviteReqVO inviteReqVO) { | |
| 34 | - // TODO @AI:需要群主校验下; | |
| 35 | - // 插入 | |
| 36 | - ImGroupMemberDO groupMember = BeanUtils.toBean(inviteReqVO, ImGroupMemberDO.class); | |
| 37 | - // TODO @AI:默认字段的设置; | |
| 38 | - imGroupMemberMapper.insert(groupMember); | |
| 39 | - // TODO @AI:或者调用内部的 createGroupMember 方法,设置默认字段的值; | |
| 40 | - // 返回 | |
| 41 | - return groupMember.getId(); | |
| 42 | - } | |
| 43 | - | |
| 44 | - @Override | |
| 45 | - public void updateGroupMember(ImGroupMemberUpdateReqVO updateReqVO) { | |
| 46 | - // 校验存在 | |
| 47 | - validateGroupMemberExists(updateReqVO.getId()); | |
| 48 | - // 更新 | |
| 49 | - ImGroupMemberDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupMemberDO.class); | |
| 50 | - imGroupMemberMapper.updateById(updateObj); | |
| 51 | - } | |
| 52 | - | |
| 53 | - @Override | |
| 54 | - public void removeGroupMember(Long id) { | |
| 55 | - // TODO @AI:需要群主校验下; | |
| 56 | - // 校验存在 | |
| 57 | - validateGroupMemberExists(id); | |
| 58 | - | |
| 59 | - // 删除 | |
| 60 | - imGroupMemberMapper.deleteById(id); | |
| 61 | - } | |
| 62 | - | |
| 63 | - private void validateGroupMemberExists(Long id) { | |
| 64 | - if (imGroupMemberMapper.selectById(id) == null) { | |
| 65 | - throw exception(GROUP_MEMBER_NOT_EXISTS); | |
| 66 | - } | |
| 67 | - } | |
| 68 | - | |
| 69 | - @Override | |
| 70 | 31 | public ImGroupMemberDO getGroupMember(Long id) { |
| 71 | 32 | return imGroupMemberMapper.selectById(id); |
| 72 | 33 | } |
| ... | ... | @@ -96,7 +57,6 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { |
| 96 | 57 | ImGroupMemberDO member = new ImGroupMemberDO() |
| 97 | 58 | .setGroupId(groupId).setUserId(userId) |
| 98 | 59 | .setStatus(CommonStatusEnum.ENABLE.getStatus()).setJoinTime(LocalDateTime.now()); |
| 99 | - // TODO @AI:要不要有个 initXXX 方法,设置默认字段的值; | |
| 100 | 60 | imGroupMemberMapper.insert(member); |
| 101 | 61 | return member; |
| 102 | 62 | } |
| ... | ... | @@ -111,15 +71,23 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { |
| 111 | 71 | } |
| 112 | 72 | |
| 113 | 73 | @Override |
| 114 | - public void updateGroupMember(Long groupId, Long userId, String displayUserName, String displayGroupName) { | |
| 74 | + public void updateGroupMember(Long userId, ImGroupMemberUpdateReqVO updateReqVO) { | |
| 115 | 75 | // 1. 校验是群的有效成员 |
| 116 | - ImGroupMemberDO member = validateMemberInGroup(groupId, userId); | |
| 76 | + ImGroupMemberDO member = validateMemberInGroup(updateReqVO.getGroupId(), userId); | |
| 77 | + // 2. 更新群成员信息 | |
| 78 | + ImGroupMemberDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupMemberDO.class) | |
| 79 | + .setId(member.getId()); | |
| 80 | + imGroupMemberMapper.updateById(updateObj); | |
| 81 | + } | |
| 117 | 82 | |
| 118 | - // 2. 更新展示信息 | |
| 83 | + @Override | |
| 84 | + public void removeGroupMember(Long groupId, Long userId) { | |
| 85 | + // 1. 校验是群的有效成员 | |
| 86 | + ImGroupMemberDO member = validateMemberInGroup(groupId, userId); | |
| 87 | + // 2. 更新为退群状态 | |
| 119 | 88 | imGroupMemberMapper.updateById(new ImGroupMemberDO().setId(member.getId()) |
| 120 | - .setDisplayUserName(displayUserName).setDisplayGroupName(displayGroupName)); | |
| 121 | - | |
| 122 | - // TODO @AI:需要分析下,是否需要 | |
| 89 | + .setStatus(CommonStatusEnum.DISABLE.getStatus()) | |
| 90 | + .setQuitTime(LocalDateTime.now())); | |
| 123 | 91 | } |
| 124 | 92 | |
| 125 | 93 | @Override | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java
| ... | ... | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.im.service.group; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; |
| 4 | 4 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; |
| 5 | +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; | |
| 5 | 6 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; |
| 6 | 7 | import jakarta.validation.Valid; |
| 7 | 8 | |
| ... | ... | @@ -14,6 +15,8 @@ import java.util.List; |
| 14 | 15 | */ |
| 15 | 16 | public interface ImGroupService { |
| 16 | 17 | |
| 18 | + // ==================== 群的写操作 ==================== | |
| 19 | + | |
| 17 | 20 | /** |
| 18 | 21 | * 创建群 |
| 19 | 22 | * <p> |
| ... | ... | @@ -44,6 +47,8 @@ public interface ImGroupService { |
| 44 | 47 | */ |
| 45 | 48 | void dissolveGroup(Long id, Long userId); |
| 46 | 49 | |
| 50 | + // ==================== 群的读操作 ==================== | |
| 51 | + | |
| 47 | 52 | /** |
| 48 | 53 | * 获得群 |
| 49 | 54 | * |
| ... | ... | @@ -80,4 +85,40 @@ public interface ImGroupService { |
| 80 | 85 | */ |
| 81 | 86 | List<ImGroupDO> getActiveGroupList(Long userId); |
| 82 | 87 | |
| 88 | + // ==================== 群成员的写操作 ==================== | |
| 89 | + // 说明:群成员的写操作统一放在 ImGroupService,而非 ImGroupMemberService, | |
| 90 | + // 保持 ImGroupMemberService 无 WebSocket 推送等外部依赖,职责更单一。 | |
| 91 | + | |
| 92 | + /** | |
| 93 | + * 邀请用户加入群 | |
| 94 | + * <p> | |
| 95 | + * 仅群主可执行 | |
| 96 | + * | |
| 97 | + * @param userId 当前登录用户编号(群主) | |
| 98 | + * @param inviteReqVO 邀请信息 | |
| 99 | + * @return 群成员编号 | |
| 100 | + */ | |
| 101 | + Long inviteGroupMember(Long userId, @Valid ImGroupMemberInviteReqVO inviteReqVO); | |
| 102 | + | |
| 103 | + /** | |
| 104 | + * 退群 | |
| 105 | + * <p> | |
| 106 | + * 群主不可退群(只能解散) | |
| 107 | + * | |
| 108 | + * @param groupId 群编号 | |
| 109 | + * @param userId 当前登录用户编号 | |
| 110 | + */ | |
| 111 | + void quitGroup(Long groupId, Long userId); | |
| 112 | + | |
| 113 | + /** | |
| 114 | + * 移除群成员(踢人) | |
| 115 | + * <p> | |
| 116 | + * 仅群主可执行,且不能移除自己 | |
| 117 | + * | |
| 118 | + * @param groupId 群编号 | |
| 119 | + * @param memberUserId 被移除的用户编号 | |
| 120 | + * @param userId 当前登录用户编号(群主) | |
| 121 | + */ | |
| 122 | + void removeGroupMember(Long groupId, Long memberUserId, Long userId); | |
| 123 | + | |
| 83 | 124 | } | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java
| ... | ... | @@ -10,6 +10,8 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 10 | 10 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 11 | 11 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; |
| 12 | 12 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; |
| 13 | +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; | |
| 14 | +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; | |
| 13 | 15 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; |
| 14 | 16 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| 15 | 17 | import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper; |
| ... | ... | @@ -35,7 +37,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU |
| 35 | 37 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| 36 | 38 | import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP; |
| 37 | 39 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; |
| 38 | -import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.GROUP_DISSOLVE_TIP_MESSAGE; | |
| 40 | +import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.*; | |
| 39 | 41 | |
| 40 | 42 | // TODO @芋艿:群主的调整,暂时不考虑。后续在弄; |
| 41 | 43 | /** |
| ... | ... | @@ -62,6 +64,8 @@ public class ImGroupServiceImpl implements ImGroupService { |
| 62 | 64 | @Resource |
| 63 | 65 | private AdminUserApi adminUserApi; |
| 64 | 66 | |
| 67 | + // ==================== 群的写操作 ==================== | |
| 68 | + | |
| 65 | 69 | @Override |
| 66 | 70 | @Transactional(rollbackFor = Exception.class) |
| 67 | 71 | public ImGroupDO createGroup(ImGroupCreateReqVO createReqVO, Long userId) { |
| ... | ... | @@ -107,10 +111,10 @@ public class ImGroupServiceImpl implements ImGroupService { |
| 107 | 111 | } |
| 108 | 112 | |
| 109 | 113 | // 3. 更新当前用户的群成员信息(displayUserName、displayGroupName → im_group_member 表) |
| 110 | - // TODO @AI:ObjUtil.isAllEmpty(? | |
| 111 | - if (updateReqVO.getDisplayUserName() != null || updateReqVO.getDisplayGroupName() != null) { | |
| 112 | - groupMemberService.updateGroupMember(updateReqVO.getId(), userId, | |
| 113 | - updateReqVO.getDisplayUserName(), updateReqVO.getDisplayGroupName()); | |
| 114 | + if (!ObjUtil.isAllEmpty(updateReqVO.getDisplayUserName(), updateReqVO.getDisplayGroupName())) { | |
| 115 | + groupMemberService.updateGroupMember(userId, new ImGroupMemberUpdateReqVO() | |
| 116 | + .setGroupId(updateReqVO.getId()).setDisplayUserName(updateReqVO.getDisplayUserName()) | |
| 117 | + .setDisplayGroupName(updateReqVO.getDisplayGroupName())); | |
| 114 | 118 | } |
| 115 | 119 | return group; |
| 116 | 120 | } |
| ... | ... | @@ -137,12 +141,68 @@ public class ImGroupServiceImpl implements ImGroupService { |
| 137 | 141 | AdminUserRespDTO user = adminUserApi.getUser(userId); |
| 138 | 142 | String tipContent = StrUtil.format(GROUP_DISSOLVE_TIP_MESSAGE, user.getNickname()); |
| 139 | 143 | groupMessageService.sendTipGroupMessage(userId, id, memberUserIds, tipContent); |
| 140 | - // 3.2 推送群解散事件给所有原群成员 | |
| 144 | + // 3.2 推送群删除事件给所有原群成员 | |
| 141 | 145 | webSocketService.sendGroupMessageAsync(memberUserIds, |
| 142 | - ImGroupMessageDTO.ofGroupDissolve(userId, id)); | |
| 146 | + ImGroupMessageDTO.ofGroupDelete(userId, id)); | |
| 147 | + } | |
| 148 | + | |
| 149 | + // ==================== 群成员的写操作 ==================== | |
| 150 | + | |
| 151 | + @Override | |
| 152 | + public Long inviteGroupMember(Long userId, ImGroupMemberInviteReqVO inviteReqVO) { | |
| 153 | + // 1. 校验群存在 + 当前用户是群主 | |
| 154 | + validateGroupOwner(inviteReqVO.getGroupId(), userId); | |
| 155 | + | |
| 156 | + // 2. 添加群成员 | |
| 157 | + ImGroupMemberDO member = groupMemberService.addGroupMember(inviteReqVO.getGroupId(), inviteReqVO.getUserId()); | |
| 158 | + return member.getId(); | |
| 143 | 159 | } |
| 144 | 160 | |
| 145 | 161 | @Override |
| 162 | + public void quitGroup(Long groupId, Long userId) { | |
| 163 | + // 1. 校验群存在 | |
| 164 | + ImGroupDO group = validateGroupExists(groupId); | |
| 165 | + // 2. 群主不可退群 | |
| 166 | + if (ObjUtil.equal(group.getOwnerUserId(), userId)) { | |
| 167 | + throw exception(GROUP_OWNER_CANNOT_QUIT); | |
| 168 | + } | |
| 169 | + | |
| 170 | + // 3.1 移除群成员 | |
| 171 | + groupMemberService.removeGroupMember(groupId, userId); | |
| 172 | + // 3.2 清理已读缓存 | |
| 173 | + groupMessageService.deleteReadMaxMessageId(groupId, userId); | |
| 174 | + | |
| 175 | + // 4.1 发送退群提示消息(TIP_TEXT) | |
| 176 | + groupMessageService.sendTipGroupMessage(userId, groupId, Set.of(userId), GROUP_QUIT_TIP_MESSAGE); | |
| 177 | + // 4.2 推送群删除事件 | |
| 178 | + webSocketService.sendGroupMessageAsync(Set.of(userId), | |
| 179 | + ImGroupMessageDTO.ofGroupDelete(userId, groupId)); | |
| 180 | + } | |
| 181 | + | |
| 182 | + @Override | |
| 183 | + public void removeGroupMember(Long groupId, Long memberUserId, Long userId) { | |
| 184 | + // 1. 校验群存在 + 当前用户是群主 | |
| 185 | + validateGroupOwner(groupId, userId); | |
| 186 | + // 2. 不能移除自己 | |
| 187 | + if (ObjUtil.equal(memberUserId, userId)) { | |
| 188 | + throw exception(GROUP_CANNOT_REMOVE_SELF); | |
| 189 | + } | |
| 190 | + | |
| 191 | + // 3.1 移除群成员 | |
| 192 | + groupMemberService.removeGroupMember(groupId, memberUserId); | |
| 193 | + // 3.2 清理已读缓存 | |
| 194 | + groupMessageService.deleteReadMaxMessageId(groupId, memberUserId); | |
| 195 | + | |
| 196 | + // 4.1 发送踢出提示消息(TIP_TEXT) | |
| 197 | + groupMessageService.sendTipGroupMessage(userId, groupId, Set.of(memberUserId), GROUP_REMOVE_TIP_MESSAGE); | |
| 198 | + // 4.2 推送群删除事件 | |
| 199 | + webSocketService.sendGroupMessageAsync(Set.of(memberUserId), | |
| 200 | + ImGroupMessageDTO.ofGroupDelete(userId, groupId)); | |
| 201 | + } | |
| 202 | + | |
| 203 | + // ==================== 群的读操作 ==================== | |
| 204 | + | |
| 205 | + @Override | |
| 146 | 206 | public ImGroupDO validateGroupExists(Long id) { |
| 147 | 207 | ImGroupDO group = getSelf().getGroup(id); |
| 148 | 208 | if (group == null) { |
| ... | ... | @@ -185,7 +245,7 @@ public class ImGroupServiceImpl implements ImGroupService { |
| 185 | 245 | } |
| 186 | 246 | |
| 187 | 247 | // 2. 批量查询群信息(仅 ENABLE 状态) |
| 188 | - return groupMapper.selectListByIds(groupIds, CommonStatusEnum.ENABLE.getStatus(), null); | |
| 248 | + return groupMapper.selectListByIds(groupIds, CommonStatusEnum.ENABLE.getStatus(), false); | |
| 189 | 249 | } |
| 190 | 250 | |
| 191 | 251 | private ImGroupServiceImpl getSelf() { | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImGroupMessageDTO.java
| ... | ... | @@ -147,15 +147,17 @@ public class ImGroupMessageDTO { |
| 147 | 147 | } |
| 148 | 148 | |
| 149 | 149 | /** |
| 150 | - * 构建群解散推送 DTO(通知所有群成员"群已解散") | |
| 150 | + * 构建群删除推送 DTO(通知群成员"群已移除") | |
| 151 | + * <p> | |
| 152 | + * 适用于群解散、退群、踢出等场景 | |
| 151 | 153 | * |
| 152 | - * @param senderId 操作者(群主)用户编号 | |
| 154 | + * @param senderId 操作者用户编号 | |
| 153 | 155 | * @param groupId 群编号 |
| 154 | 156 | * @return 群聊 DTO |
| 155 | 157 | */ |
| 156 | - public static ImGroupMessageDTO ofGroupDissolve(Long senderId, Long groupId) { | |
| 158 | + public static ImGroupMessageDTO ofGroupDelete(Long senderId, Long groupId) { | |
| 157 | 159 | return new ImGroupMessageDTO() |
| 158 | - .setType(ImMessageTypeEnum.GROUP_DISSOLVE.getType()) | |
| 160 | + .setType(ImMessageTypeEnum.GROUP_DELETE.getType()) | |
| 159 | 161 | .setSenderId(senderId).setGroupId(groupId) |
| 160 | 162 | .setSendTime(LocalDateTime.now()); |
| 161 | 163 | } | ... | ... |