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 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 }
... ...