Commit b814a87361ce5a49c3757b89c755b30f78efa220

Authored by YunaiV
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,6 +35,8 @@ public interface ErrorCodeConstants {
35 ErrorCode GROUP_MEMBER_NOT_EXISTS = new ErrorCode(1_040_500_000, "群成员不存在"); 35 ErrorCode GROUP_MEMBER_NOT_EXISTS = new ErrorCode(1_040_500_000, "群成员不存在");
36 ErrorCode GROUP_MEMBER_NOT_IN_GROUP = new ErrorCode(1_040_500_001, "您已不在该群中"); 36 ErrorCode GROUP_MEMBER_NOT_IN_GROUP = new ErrorCode(1_040_500_001, "您已不在该群中");
37 ErrorCode GROUP_MEMBER_ALREADY_QUIT = new ErrorCode(1_040_500_002, "您已退出该群"); 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 // ========== 好友 (1-040-600-000) ========== 41 // ========== 好友 (1-040-600-000) ==========
40 ErrorCode FRIEND_NOT_EXISTS = new ErrorCode(1_040_600_000, "好友关系不存在"); 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,5 +51,13 @@ public interface ImCommonConstants {
51 * 群解散系统提示 51 * 群解散系统提示
52 */ 52 */
53 String GROUP_DISSOLVE_TIP_MESSAGE = "'{}'解散了群聊"; 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,7 +34,7 @@ public enum ImMessageTypeEnum implements ArrayValuable<Integer> {
34 34
35 GROUP_CREATE(200, "群创建"), // 暂无 35 GROUP_CREATE(200, "群创建"), // 暂无
36 GROUP_UPDATE(201, "群信息变更"), // 暂无 36 GROUP_UPDATE(201, "群信息变更"), // 暂无
37 - GROUP_DISSOLVE(202, "群解散"); // 暂无 37 + GROUP_DELETE(202, "群删除"); // 暂无(解散/退群/踢出均用此类型)
38 38
39 public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImMessageTypeEnum::getType).toArray(Integer[]::new); 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,6 +5,7 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; 5 import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO;
6 import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupRespVO; 6 import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupRespVO;
7 import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; 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 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; 9 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
9 import cn.iocoder.yudao.module.im.service.group.ImGroupService; 10 import cn.iocoder.yudao.module.im.service.group.ImGroupService;
10 import io.swagger.v3.oas.annotations.Operation; 11 import io.swagger.v3.oas.annotations.Operation;
@@ -30,6 +31,8 @@ public class ImGroupController { @@ -30,6 +31,8 @@ public class ImGroupController {
30 @Resource 31 @Resource
31 private ImGroupService imGroupService; 32 private ImGroupService imGroupService;
32 33
  34 + // ==================== 群的写操作 ====================
  35 +
33 @PostMapping("/create") 36 @PostMapping("/create")
34 @Operation(summary = "创建群") 37 @Operation(summary = "创建群")
35 @PreAuthorize("@ss.hasPermission('im:group:create')") 38 @PreAuthorize("@ss.hasPermission('im:group:create')")
@@ -55,6 +58,8 @@ public class ImGroupController { @@ -55,6 +58,8 @@ public class ImGroupController {
55 return success(true); 58 return success(true);
56 } 59 }
57 60
  61 + // ==================== 群的读操作 ====================
  62 +
58 @GetMapping("/get") 63 @GetMapping("/get")
59 @Operation(summary = "获得群") 64 @Operation(summary = "获得群")
60 @Parameter(name = "id", description = "编号", required = true, example = "1024") 65 @Parameter(name = "id", description = "编号", required = true, example = "1024")
@@ -71,4 +76,31 @@ public class ImGroupController { @@ -71,4 +76,31 @@ public class ImGroupController {
71 return success(BeanUtils.toBean(groups, ImGroupRespVO.class)); 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,7 +3,6 @@ package cn.iocoder.yudao.module.im.controller.admin.group;
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.util.collection.MapUtils; 4 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 -import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO;  
7 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRespVO; 6 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRespVO;
8 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; 7 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO;
9 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; 8 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
@@ -24,6 +23,7 @@ import java.util.Map; @@ -24,6 +23,7 @@ import java.util.Map;
24 23
25 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 24 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
26 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; 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 @Tag(name = "管理后台 - 群成员") 28 @Tag(name = "管理后台 - 群成员")
29 @RestController 29 @RestController
@@ -32,32 +32,16 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. @@ -32,32 +32,16 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
32 public class ImGroupMemberController { 32 public class ImGroupMemberController {
33 33
34 @Resource 34 @Resource
35 - private ImGroupMemberService imGroupMemberService; 35 + private ImGroupMemberService groupMemberService;
36 36
37 @Resource 37 @Resource
38 private AdminUserApi adminUserApi; 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 @PutMapping("/update") 40 @PutMapping("/update")
48 @Operation(summary = "更新群成员") 41 @Operation(summary = "更新群成员")
49 @PreAuthorize("@ss.hasPermission('im:group-member:update')") 42 @PreAuthorize("@ss.hasPermission('im:group-member:update')")
50 public CommonResult<Boolean> updateGroupMember(@Valid @RequestBody ImGroupMemberUpdateReqVO updateReqVO) { 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 return success(true); 45 return success(true);
62 } 46 }
63 47
@@ -66,7 +50,7 @@ public class ImGroupMemberController { @@ -66,7 +50,7 @@ public class ImGroupMemberController {
66 @Parameter(name = "id", description = "编号", required = true, example = "1024") 50 @Parameter(name = "id", description = "编号", required = true, example = "1024")
67 @PreAuthorize("@ss.hasPermission('im:group-member:query')") 51 @PreAuthorize("@ss.hasPermission('im:group-member:query')")
68 public CommonResult<ImGroupMemberRespVO> getGroupMember(@RequestParam("id") Long id) { 52 public CommonResult<ImGroupMemberRespVO> getGroupMember(@RequestParam("id") Long id) {
69 - ImGroupMemberDO groupMember = imGroupMemberService.getGroupMember(id); 53 + ImGroupMemberDO groupMember = groupMemberService.getGroupMember(id);
70 return success(BeanUtils.toBean(groupMember, ImGroupMemberRespVO.class)); 54 return success(BeanUtils.toBean(groupMember, ImGroupMemberRespVO.class));
71 } 55 }
72 56
@@ -74,7 +58,7 @@ public class ImGroupMemberController { @@ -74,7 +58,7 @@ public class ImGroupMemberController {
74 @Operation(summary = "获得指定群的成员列表") 58 @Operation(summary = "获得指定群的成员列表")
75 @Parameter(name = "groupId", description = "群编号", required = true, example = "1024") 59 @Parameter(name = "groupId", description = "群编号", required = true, example = "1024")
76 public CommonResult<List<ImGroupMemberRespVO>> getGroupMemberList(@RequestParam("groupId") Long groupId) { 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 // 批量聚合 AdminUser 信息(昵称 / 头像) 62 // 批量聚合 AdminUser 信息(昵称 / 头像)
79 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( 63 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
80 convertList(members, ImGroupMemberDO::getUserId)); 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 \ No newline at end of file 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 \ No newline at end of file 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,14 +3,22 @@ package cn.iocoder.yudao.module.im.controller.admin.group.vo.member;
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import jakarta.validation.constraints.NotNull; 4 import jakarta.validation.constraints.NotNull;
5 import lombok.Data; 5 import lombok.Data;
  6 +import lombok.experimental.Accessors;
6 7
7 @Schema(description = "管理后台 - 群成员更新 Request VO") 8 @Schema(description = "管理后台 - 群成员更新 Request VO")
8 @Data 9 @Data
  10 +@Accessors(chain = true)
9 public class ImGroupMemberUpdateReqVO { 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 @Schema(description = "是否免打扰") 23 @Schema(description = "是否免打扰")
16 private Boolean muted; 24 private Boolean muted;
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java
1 package cn.iocoder.yudao.module.im.service.group; 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 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; 3 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO;
5 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; 4 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
6 import jakarta.validation.Valid; 5 import jakarta.validation.Valid;
@@ -16,28 +15,6 @@ import java.util.List; @@ -16,28 +15,6 @@ import java.util.List;
16 public interface ImGroupMemberService { 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 * @param id 编号 20 * @param id 编号
@@ -101,17 +78,24 @@ public interface ImGroupMemberService { @@ -101,17 +78,24 @@ public interface ImGroupMemberService {
101 ImGroupMemberDO validateMemberInGroup(Long groupId, Long userId); 78 ImGroupMemberDO validateMemberInGroup(Long groupId, Long userId);
102 79
103 /** 80 /**
104 - * 更新群成员的展示信息(群内昵称、群名备注) 81 + * 更新群成员信息(群内昵称、群名备注、免打扰等)
105 * <p> 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 * 移除群的全部成员(设置为 DISABLE 状态) 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,7 +2,6 @@ package cn.iocoder.yudao.module.im.service.group;
2 2
3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 -import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO;  
6 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; 5 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO;
7 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; 6 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
8 import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper; 7 import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper;
@@ -14,7 +13,6 @@ import java.time.LocalDateTime; @@ -14,7 +13,6 @@ import java.time.LocalDateTime;
14 import java.util.List; 13 import java.util.List;
15 14
16 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 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 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.GROUP_MEMBER_NOT_IN_GROUP; 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,43 +28,6 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
30 private ImGroupMemberMapper imGroupMemberMapper; 28 private ImGroupMemberMapper imGroupMemberMapper;
31 29
32 @Override 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 public ImGroupMemberDO getGroupMember(Long id) { 31 public ImGroupMemberDO getGroupMember(Long id) {
71 return imGroupMemberMapper.selectById(id); 32 return imGroupMemberMapper.selectById(id);
72 } 33 }
@@ -96,7 +57,6 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @@ -96,7 +57,6 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
96 ImGroupMemberDO member = new ImGroupMemberDO() 57 ImGroupMemberDO member = new ImGroupMemberDO()
97 .setGroupId(groupId).setUserId(userId) 58 .setGroupId(groupId).setUserId(userId)
98 .setStatus(CommonStatusEnum.ENABLE.getStatus()).setJoinTime(LocalDateTime.now()); 59 .setStatus(CommonStatusEnum.ENABLE.getStatus()).setJoinTime(LocalDateTime.now());
99 - // TODO @AI:要不要有个 initXXX 方法,设置默认字段的值;  
100 imGroupMemberMapper.insert(member); 60 imGroupMemberMapper.insert(member);
101 return member; 61 return member;
102 } 62 }
@@ -111,15 +71,23 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @@ -111,15 +71,23 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
111 } 71 }
112 72
113 @Override 73 @Override
114 - public void updateGroupMember(Long groupId, Long userId, String displayUserName, String displayGroupName) { 74 + public void updateGroupMember(Long userId, ImGroupMemberUpdateReqVO updateReqVO) {
115 // 1. 校验是群的有效成员 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 imGroupMemberMapper.updateById(new ImGroupMemberDO().setId(member.getId()) 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 @Override 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,6 +2,7 @@ package cn.iocoder.yudao.module.im.service.group;
2 2
3 import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; 3 import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO;
4 import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; 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 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; 6 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
6 import jakarta.validation.Valid; 7 import jakarta.validation.Valid;
7 8
@@ -14,6 +15,8 @@ import java.util.List; @@ -14,6 +15,8 @@ import java.util.List;
14 */ 15 */
15 public interface ImGroupService { 16 public interface ImGroupService {
16 17
  18 + // ==================== 群的写操作 ====================
  19 +
17 /** 20 /**
18 * 创建群 21 * 创建群
19 * <p> 22 * <p>
@@ -44,6 +47,8 @@ public interface ImGroupService { @@ -44,6 +47,8 @@ public interface ImGroupService {
44 */ 47 */
45 void dissolveGroup(Long id, Long userId); 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,4 +85,40 @@ public interface ImGroupService {
80 */ 85 */
81 List<ImGroupDO> getActiveGroupList(Long userId); 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,6 +10,8 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
10 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 10 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
11 import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; 11 import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO;
12 import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; 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 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; 15 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
14 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; 16 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
15 import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper; 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,7 +37,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
35 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 37 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
36 import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP; 38 import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP;
37 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; 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 // TODO @芋艿:群主的调整,暂时不考虑。后续在弄; 42 // TODO @芋艿:群主的调整,暂时不考虑。后续在弄;
41 /** 43 /**
@@ -62,6 +64,8 @@ public class ImGroupServiceImpl implements ImGroupService { @@ -62,6 +64,8 @@ public class ImGroupServiceImpl implements ImGroupService {
62 @Resource 64 @Resource
63 private AdminUserApi adminUserApi; 65 private AdminUserApi adminUserApi;
64 66
  67 + // ==================== 群的写操作 ====================
  68 +
65 @Override 69 @Override
66 @Transactional(rollbackFor = Exception.class) 70 @Transactional(rollbackFor = Exception.class)
67 public ImGroupDO createGroup(ImGroupCreateReqVO createReqVO, Long userId) { 71 public ImGroupDO createGroup(ImGroupCreateReqVO createReqVO, Long userId) {
@@ -107,10 +111,10 @@ public class ImGroupServiceImpl implements ImGroupService { @@ -107,10 +111,10 @@ public class ImGroupServiceImpl implements ImGroupService {
107 } 111 }
108 112
109 // 3. 更新当前用户的群成员信息(displayUserName、displayGroupName → im_group_member 表) 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 return group; 119 return group;
116 } 120 }
@@ -137,12 +141,68 @@ public class ImGroupServiceImpl implements ImGroupService { @@ -137,12 +141,68 @@ public class ImGroupServiceImpl implements ImGroupService {
137 AdminUserRespDTO user = adminUserApi.getUser(userId); 141 AdminUserRespDTO user = adminUserApi.getUser(userId);
138 String tipContent = StrUtil.format(GROUP_DISSOLVE_TIP_MESSAGE, user.getNickname()); 142 String tipContent = StrUtil.format(GROUP_DISSOLVE_TIP_MESSAGE, user.getNickname());
139 groupMessageService.sendTipGroupMessage(userId, id, memberUserIds, tipContent); 143 groupMessageService.sendTipGroupMessage(userId, id, memberUserIds, tipContent);
140 - // 3.2 推送群解散事件给所有原群成员 144 + // 3.2 推送群删除事件给所有原群成员
141 webSocketService.sendGroupMessageAsync(memberUserIds, 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 @Override 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 public ImGroupDO validateGroupExists(Long id) { 206 public ImGroupDO validateGroupExists(Long id) {
147 ImGroupDO group = getSelf().getGroup(id); 207 ImGroupDO group = getSelf().getGroup(id);
148 if (group == null) { 208 if (group == null) {
@@ -185,7 +245,7 @@ public class ImGroupServiceImpl implements ImGroupService { @@ -185,7 +245,7 @@ public class ImGroupServiceImpl implements ImGroupService {
185 } 245 }
186 246
187 // 2. 批量查询群信息(仅 ENABLE 状态) 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 private ImGroupServiceImpl getSelf() { 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,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 * @param groupId 群编号 155 * @param groupId 群编号
154 * @return 群聊 DTO 156 * @return 群聊 DTO
155 */ 157 */
156 - public static ImGroupMessageDTO ofGroupDissolve(Long senderId, Long groupId) { 158 + public static ImGroupMessageDTO ofGroupDelete(Long senderId, Long groupId) {
157 return new ImGroupMessageDTO() 159 return new ImGroupMessageDTO()
158 - .setType(ImMessageTypeEnum.GROUP_DISSOLVE.getType()) 160 + .setType(ImMessageTypeEnum.GROUP_DELETE.getType())
159 .setSenderId(senderId).setGroupId(groupId) 161 .setSenderId(senderId).setGroupId(groupId)
160 .setSendTime(LocalDateTime.now()); 162 .setSendTime(LocalDateTime.now());
161 } 163 }