Commit 84304718dc19627516fab687d7c010c9eade9b81

Authored by YunaiV
1 parent b814a873

✨ feat(group): 支持批量邀请和移除群成员

yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java
... ... @@ -6,6 +6,7 @@ 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 8 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO;
  9 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO;
9 10 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
10 11 import cn.iocoder.yudao.module.im.service.group.ImGroupService;
11 12 import io.swagger.v3.oas.annotations.Operation;
... ... @@ -29,7 +30,7 @@ import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLogi
29 30 public class ImGroupController {
30 31  
31 32 @Resource
32   - private ImGroupService imGroupService;
  33 + private ImGroupService groupService;
33 34  
34 35 // ==================== 群的写操作 ====================
35 36  
... ... @@ -37,7 +38,7 @@ public class ImGroupController {
37 38 @Operation(summary = "创建群")
38 39 @PreAuthorize("@ss.hasPermission('im:group:create')")
39 40 public CommonResult<ImGroupRespVO> createGroup(@Valid @RequestBody ImGroupCreateReqVO createReqVO) {
40   - ImGroupDO group = imGroupService.createGroup(createReqVO, getLoginUserId());
  41 + ImGroupDO group = groupService.createGroup(createReqVO, getLoginUserId());
41 42 return success(BeanUtils.toBean(group, ImGroupRespVO.class));
42 43 }
43 44  
... ... @@ -45,7 +46,7 @@ public class ImGroupController {
45 46 @Operation(summary = "更新群")
46 47 @PreAuthorize("@ss.hasPermission('im:group:update')")
47 48 public CommonResult<ImGroupRespVO> updateGroup(@Valid @RequestBody ImGroupUpdateReqVO updateReqVO) {
48   - ImGroupDO group = imGroupService.updateGroup(updateReqVO, getLoginUserId());
  49 + ImGroupDO group = groupService.updateGroup(updateReqVO, getLoginUserId());
49 50 return success(BeanUtils.toBean(group, ImGroupRespVO.class));
50 51 }
51 52  
... ... @@ -54,7 +55,7 @@ public class ImGroupController {
54 55 @Parameter(name = "id", description = "群编号", required = true)
55 56 @PreAuthorize("@ss.hasPermission('im:group:delete')")
56 57 public CommonResult<Boolean> dissolveGroup(@RequestParam("id") Long id) {
57   - imGroupService.dissolveGroup(id, getLoginUserId());
  58 + groupService.dissolveGroup(id, getLoginUserId());
58 59 return success(true);
59 60 }
60 61  
... ... @@ -65,14 +66,14 @@ public class ImGroupController {
65 66 @Parameter(name = "id", description = "编号", required = true, example = "1024")
66 67 @PreAuthorize("@ss.hasPermission('im:group:query')")
67 68 public CommonResult<ImGroupRespVO> getGroup(@RequestParam("id") Long id) {
68   - ImGroupDO group = imGroupService.getGroup(id);
  69 + ImGroupDO group = groupService.getGroup(id);
69 70 return success(BeanUtils.toBean(group, ImGroupRespVO.class));
70 71 }
71 72  
72 73 @GetMapping("/list")
73 74 @Operation(summary = "获得当前登录用户的群列表")
74 75 public CommonResult<List<ImGroupRespVO>> getMyGroupList() {
75   - List<ImGroupDO> groups = imGroupService.getActiveGroupList(getLoginUserId());
  76 + List<ImGroupDO> groups = groupService.getActiveGroupList(getLoginUserId());
76 77 return success(BeanUtils.toBean(groups, ImGroupRespVO.class));
77 78 }
78 79  
... ... @@ -81,8 +82,9 @@ public class ImGroupController {
81 82 @PostMapping("/invite")
82 83 @Operation(summary = "邀请用户加入群")
83 84 @PreAuthorize("@ss.hasPermission('im:group:update')")
84   - public CommonResult<Long> inviteGroupMember(@Valid @RequestBody ImGroupMemberInviteReqVO inviteReqVO) {
85   - return success(imGroupService.inviteGroupMember(getLoginUserId(), inviteReqVO));
  85 + public CommonResult<Boolean> inviteGroupMember(@Valid @RequestBody ImGroupMemberInviteReqVO inviteReqVO) {
  86 + groupService.inviteGroupMember(getLoginUserId(), inviteReqVO);
  87 + return success(true);
86 88 }
87 89  
88 90 @DeleteMapping("/quit")
... ... @@ -90,16 +92,15 @@ public class ImGroupController {
90 92 @Parameter(name = "groupId", description = "群编号", required = true)
91 93 @PreAuthorize("@ss.hasPermission('im:group:update')")
92 94 public CommonResult<Boolean> quitGroup(@RequestParam("groupId") Long groupId) {
93   - imGroupService.quitGroup(groupId, getLoginUserId());
  95 + groupService.quitGroup(groupId, getLoginUserId());
94 96 return success(true);
95 97 }
96 98  
97 99 @DeleteMapping("/kicking")
98 100 @Operation(summary = "移除群成员")
99 101 @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());
  102 + public CommonResult<Boolean> removeGroupMember(@Valid @RequestBody ImGroupMemberRemoveReqVO removeReqVO) {
  103 + groupService.removeGroupMember(getLoginUserId(), removeReqVO);
103 104 return success(true);
104 105 }
105 106  
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberInviteReqVO.java
1 1 package cn.iocoder.yudao.module.im.controller.admin.group.vo.member;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
4 5 import jakarta.validation.constraints.NotNull;
5 6 import lombok.Data;
6 7  
  8 +import java.util.List;
  9 +
7 10 @Schema(description = "管理后台 - 群成员邀请 Request VO")
8 11 @Data
9 12 public class ImGroupMemberInviteReqVO {
... ... @@ -12,8 +15,8 @@ public class ImGroupMemberInviteReqVO {
12 15 @NotNull(message = "群编号不能为空")
13 16 private Long groupId;
14 17  
15   - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21730")
16   - @NotNull(message = "用户编号不能为空")
17   - private Long userId;
  18 + @Schema(description = "被邀请的用户编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2, 3]")
  19 + @NotEmpty(message = "被邀请的用户编号列表不能为空")
  20 + private List<Long> memberUserIds;
18 21  
19 22 }
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberRemoveReqVO.java 0 → 100644
  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.NotEmpty;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +import java.util.List;
  9 +
  10 +@Schema(description = "管理后台 - 群成员移除 Request VO")
  11 +@Data
  12 +public class ImGroupMemberRemoveReqVO {
  13 +
  14 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  15 + @NotNull(message = "群编号不能为空")
  16 + private Long groupId;
  17 +
  18 + @Schema(description = "被移除的用户编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2, 3]")
  19 + @NotEmpty(message = "被移除的用户编号列表不能为空")
  20 + private List<Long> memberUserIds;
  21 +
  22 +}
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java
... ... @@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
7 7 import org.apache.ibatis.annotations.Mapper;
8 8  
9 9 import java.time.LocalDateTime;
  10 +import java.util.Collection;
10 11 import java.util.List;
11 12  
12 13 /**
... ... @@ -27,6 +28,12 @@ public interface ImGroupMemberMapper extends BaseMapperX&lt;ImGroupMemberDO&gt; {
27 28 .eq(ImGroupMemberDO::getUserId, userId));
28 29 }
29 30  
  31 + default List<ImGroupMemberDO> selectListByGroupIdAndUserIds(Long groupId, Collection<Long> userIds) {
  32 + return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>()
  33 + .eq(ImGroupMemberDO::getGroupId, groupId)
  34 + .in(ImGroupMemberDO::getUserId, userIds));
  35 + }
  36 +
30 37 default List<ImGroupMemberDO> selectListByGroupIdAndStatus(Long groupId, Integer status) {
31 38 return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>()
32 39 .eq(ImGroupMemberDO::getGroupId, groupId)
... ... @@ -62,4 +69,13 @@ public interface ImGroupMemberMapper extends BaseMapperX&lt;ImGroupMemberDO&gt; {
62 69 .eq(ImGroupMemberDO::getStatus, oldStatus));
63 70 }
64 71  
  72 + @SuppressWarnings("UnusedReturnValue")
  73 + default int updateByGroupIdAndUserIdsAndStatus(Long groupId, Collection<Long> userIds,
  74 + Integer oldStatus, ImGroupMemberDO updateObj) {
  75 + return update(updateObj, new LambdaQueryWrapperX<ImGroupMemberDO>()
  76 + .eq(ImGroupMemberDO::getGroupId, groupId)
  77 + .in(ImGroupMemberDO::getUserId, userIds)
  78 + .eq(ImGroupMemberDO::getStatus, oldStatus));
  79 + }
  80 +
65 81 }
66 82 \ No newline at end of file
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/GroupMessageReadRedisDAO.java
... ... @@ -5,6 +5,7 @@ import jakarta.annotation.Resource;
5 5 import org.springframework.data.redis.core.StringRedisTemplate;
6 6 import org.springframework.stereotype.Repository;
7 7  
  8 +import java.util.Collection;
8 9 import java.util.HashMap;
9 10 import java.util.Map;
10 11  
... ... @@ -73,6 +74,18 @@ public class GroupMessageReadRedisDAO {
73 74 }
74 75  
75 76 /**
  77 + * 批量删除用户在某群的已读位置
  78 + *
  79 + * @param groupId 群编号
  80 + * @param userIds 用户编号集合
  81 + */
  82 + public void deleteReadMaxMessageIds(Long groupId, Collection<Long> userIds) {
  83 + String key = formatKey(groupId);
  84 + Object[] hashKeys = userIds.stream().map(String::valueOf).toArray();
  85 + stringRedisTemplate.opsForHash().delete(key, hashKeys);
  86 + }
  87 +
  88 + /**
76 89 * 删除某群所有用户的已读位置(整个 Hash Key)
77 90 *
78 91 * @param groupId 群编号
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java
... ... @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
5 5 import jakarta.validation.Valid;
6 6  
7 7 import java.time.LocalDateTime;
  8 +import java.util.Collection;
8 9 import java.util.List;
9 10  
10 11 /**
... ... @@ -69,6 +70,14 @@ public interface ImGroupMemberService {
69 70 ImGroupMemberDO addGroupMember(Long groupId, Long userId);
70 71  
71 72 /**
  73 + * 批量添加群成员(入群)
  74 + *
  75 + * @param groupId 群编号
  76 + * @param userIds 用户编号集合
  77 + */
  78 + void addGroupMembers(Long groupId, Collection<Long> userIds);
  79 +
  80 + /**
72 81 * 校验用户是否为群的有效成员
73 82 *
74 83 * @param groupId 群编号
... ... @@ -98,6 +107,16 @@ public interface ImGroupMemberService {
98 107 void removeGroupMember(Long groupId, Long userId);
99 108  
100 109 /**
  110 + * 批量移除指定群成员(设置为 DISABLE 状态)
  111 + * <p>
  112 + * 用于批量踢出场景
  113 + *
  114 + * @param groupId 群编号
  115 + * @param userIds 用户编号集合
  116 + */
  117 + void removeGroupMembers(Long groupId, Collection<Long> userIds);
  118 +
  119 + /**
101 120 * 移除群的全部成员(设置为 DISABLE 状态)
102 121 * <p>
103 122 * 用于群解散场景
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java
1 1 package cn.iocoder.yudao.module.im.service.group;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 6 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO;
... ... @@ -10,9 +11,13 @@ import org.springframework.stereotype.Service;
10 11 import org.springframework.validation.annotation.Validated;
11 12  
12 13 import java.time.LocalDateTime;
  14 +import java.util.ArrayList;
  15 +import java.util.Collection;
13 16 import java.util.List;
  17 +import java.util.Map;
14 18  
15 19 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  20 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
16 21 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.GROUP_MEMBER_NOT_IN_GROUP;
17 22  
18 23 /**
... ... @@ -25,31 +30,31 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.GROUP_MEMBER_N
25 30 public class ImGroupMemberServiceImpl implements ImGroupMemberService {
26 31  
27 32 @Resource
28   - private ImGroupMemberMapper imGroupMemberMapper;
  33 + private ImGroupMemberMapper groupMemberMapper;
29 34  
30 35 @Override
31 36 public ImGroupMemberDO getGroupMember(Long id) {
32   - return imGroupMemberMapper.selectById(id);
  37 + return groupMemberMapper.selectById(id);
33 38 }
34 39  
35 40 @Override
36 41 public List<ImGroupMemberDO> getGroupMemberListByGroupId(Long groupId) {
37   - return imGroupMemberMapper.selectListByGroupId(groupId);
  42 + return groupMemberMapper.selectListByGroupId(groupId);
38 43 }
39 44  
40 45 @Override
41 46 public List<ImGroupMemberDO> getActiveGroupMemberListByGroupId(Long groupId) {
42   - return imGroupMemberMapper.selectListByGroupIdAndStatus(groupId, CommonStatusEnum.ENABLE.getStatus());
  47 + return groupMemberMapper.selectListByGroupIdAndStatus(groupId, CommonStatusEnum.ENABLE.getStatus());
43 48 }
44 49  
45 50 @Override
46 51 public List<ImGroupMemberDO> getActiveGroupMemberListByUserId(Long userId) {
47   - return imGroupMemberMapper.selectListByUserIdAndStatus(userId, CommonStatusEnum.ENABLE.getStatus());
  52 + return groupMemberMapper.selectListByUserIdAndStatus(userId, CommonStatusEnum.ENABLE.getStatus());
48 53 }
49 54  
50 55 @Override
51 56 public List<ImGroupMemberDO> getQuitGroupMemberListByUserId(Long userId, LocalDateTime minQuitTime) {
52   - return imGroupMemberMapper.selectQuitListByUserId(userId, minQuitTime);
  57 + return groupMemberMapper.selectQuitListByUserId(userId, minQuitTime);
53 58 }
54 59  
55 60 @Override
... ... @@ -57,13 +62,42 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
57 62 ImGroupMemberDO member = new ImGroupMemberDO()
58 63 .setGroupId(groupId).setUserId(userId)
59 64 .setStatus(CommonStatusEnum.ENABLE.getStatus()).setJoinTime(LocalDateTime.now());
60   - imGroupMemberMapper.insert(member);
  65 + groupMemberMapper.insert(member);
61 66 return member;
62 67 }
63 68  
64 69 @Override
  70 + public void addGroupMembers(Long groupId, Collection<Long> userIds) {
  71 + LocalDateTime now = LocalDateTime.now();
  72 + // 1.1 查询已有记录(含已退群的 DISABLE 记录)
  73 + List<ImGroupMemberDO> existMembers = groupMemberMapper.selectListByGroupIdAndUserIds(groupId, userIds);
  74 + Map<Long, ImGroupMemberDO> existMap = convertMap(existMembers, ImGroupMemberDO::getUserId);
  75 + // 1.2 分类:已有记录 → UPDATE,新成员 → INSERT
  76 + List<ImGroupMemberDO> inserts = new ArrayList<>();
  77 + List<ImGroupMemberDO> updates = new ArrayList<>();
  78 + for (Long userId : userIds) {
  79 + ImGroupMemberDO exist = existMap.get(userId);
  80 + if (exist == null) {
  81 + inserts.add(new ImGroupMemberDO().setGroupId(groupId).setUserId(userId)
  82 + .setStatus(CommonStatusEnum.ENABLE.getStatus()).setJoinTime(now));
  83 + } else if (CommonStatusEnum.DISABLE.getStatus().equals(exist.getStatus())) {
  84 + updates.add(new ImGroupMemberDO().setId(exist.getId())
  85 + .setStatus(CommonStatusEnum.ENABLE.getStatus()).setJoinTime(now));
  86 + }
  87 + }
  88 +
  89 + // 2. 执行
  90 + if (CollUtil.isNotEmpty(inserts)) {
  91 + groupMemberMapper.insertBatch(inserts);
  92 + }
  93 + if (CollUtil.isNotEmpty(updates)) {
  94 + groupMemberMapper.updateBatch(updates);
  95 + }
  96 + }
  97 +
  98 + @Override
65 99 public ImGroupMemberDO validateMemberInGroup(Long groupId, Long userId) {
66   - ImGroupMemberDO member = imGroupMemberMapper.selectByGroupIdAndUserId(groupId, userId);
  100 + ImGroupMemberDO member = groupMemberMapper.selectByGroupIdAndUserId(groupId, userId);
67 101 if (member == null || CommonStatusEnum.DISABLE.getStatus().equals(member.getStatus())) {
68 102 throw exception(GROUP_MEMBER_NOT_IN_GROUP);
69 103 }
... ... @@ -77,7 +111,7 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
77 111 // 2. 更新群成员信息
78 112 ImGroupMemberDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupMemberDO.class)
79 113 .setId(member.getId());
80   - imGroupMemberMapper.updateById(updateObj);
  114 + groupMemberMapper.updateById(updateObj);
81 115 }
82 116  
83 117 @Override
... ... @@ -85,14 +119,22 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
85 119 // 1. 校验是群的有效成员
86 120 ImGroupMemberDO member = validateMemberInGroup(groupId, userId);
87 121 // 2. 更新为退群状态
88   - imGroupMemberMapper.updateById(new ImGroupMemberDO().setId(member.getId())
  122 + groupMemberMapper.updateById(new ImGroupMemberDO().setId(member.getId())
89 123 .setStatus(CommonStatusEnum.DISABLE.getStatus())
90 124 .setQuitTime(LocalDateTime.now()));
91 125 }
92 126  
93 127 @Override
  128 + public void removeGroupMembers(Long groupId, Collection<Long> userIds) {
  129 + groupMemberMapper.updateByGroupIdAndUserIdsAndStatus(groupId, userIds,
  130 + CommonStatusEnum.ENABLE.getStatus(),
  131 + new ImGroupMemberDO().setStatus(CommonStatusEnum.DISABLE.getStatus())
  132 + .setQuitTime(LocalDateTime.now()));
  133 + }
  134 +
  135 + @Override
94 136 public void removeGroupMembersByGroupId(Long groupId) {
95   - imGroupMemberMapper.updateByGroupIdAndStatus(groupId, CommonStatusEnum.ENABLE.getStatus(),
  137 + groupMemberMapper.updateByGroupIdAndStatus(groupId, CommonStatusEnum.ENABLE.getStatus(),
96 138 new ImGroupMemberDO().setStatus(CommonStatusEnum.DISABLE.getStatus())
97 139 .setQuitTime(LocalDateTime.now()));
98 140 }
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.im.service.group;
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 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.ImGroupMemberRemoveReqVO;
6 7 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
7 8 import jakarta.validation.Valid;
8 9  
... ... @@ -92,13 +93,13 @@ public interface ImGroupService {
92 93 /**
93 94 * 邀请用户加入群
94 95 * <p>
95   - * 仅群主可执行
  96 + * 群成员即可执行,支持批量。
  97 + * 校验群人数上限,邀请后推送提示消息和群创建事件给被邀请人。
96 98 *
97   - * @param userId 当前登录用户编号(群主)
  99 + * @param userId 当前登录用户编号
98 100 * @param inviteReqVO 邀请信息
99   - * @return 群成员编号
100 101 */
101   - Long inviteGroupMember(Long userId, @Valid ImGroupMemberInviteReqVO inviteReqVO);
  102 + void inviteGroupMember(Long userId, @Valid ImGroupMemberInviteReqVO inviteReqVO);
102 103  
103 104 /**
104 105 * 退群
... ... @@ -113,12 +114,11 @@ public interface ImGroupService {
113 114 /**
114 115 * 移除群成员(踢人)
115 116 * <p>
116   - * 仅群主可执行,且不能移除自己
  117 + * 仅群主可执行,且不能移除自己,支持批量
117 118 *
118   - * @param groupId 群编号
119   - * @param memberUserId 被移除的用户编号
120   - * @param userId 当前登录用户编号(群主)
  119 + * @param userId 当前登录用户编号(群主)
  120 + * @param removeReqVO 移除信息
121 121 */
122   - void removeGroupMember(Long groupId, Long memberUserId, Long userId);
  122 + void removeGroupMember(Long userId, @Valid ImGroupMemberRemoveReqVO removeReqVO);
123 123  
124 124 }
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java
... ... @@ -11,6 +11,7 @@ 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 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.ImGroupMemberRemoveReqVO;
14 15 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO;
15 16 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
16 17 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
... ... @@ -30,8 +31,11 @@ import org.springframework.validation.annotation.Validated;
30 31  
31 32 import java.time.LocalDateTime;
32 33 import java.util.Collections;
  34 +import java.util.HashSet;
33 35 import java.util.List;
  36 +import java.util.Map;
34 37 import java.util.Set;
  38 +import java.util.stream.Collectors;
35 39  
36 40 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
37 41 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
... ... @@ -149,13 +153,39 @@ public class ImGroupServiceImpl implements ImGroupService {
149 153 // ==================== 群成员的写操作 ====================
150 154  
151 155 @Override
152   - public Long inviteGroupMember(Long userId, ImGroupMemberInviteReqVO inviteReqVO) {
153   - // 1. 校验群存在 + 当前用户是群主
154   - validateGroupOwner(inviteReqVO.getGroupId(), userId);
  156 + public void inviteGroupMember(Long userId, ImGroupMemberInviteReqVO inviteReqVO) {
  157 + Long groupId = inviteReqVO.getGroupId();
  158 + // 1.1 校验群存在 + 当前用户是群成员
  159 + validateGroupExists(groupId);
  160 + groupMemberService.validateMemberInGroup(groupId, userId);
  161 + // 1.2 排除已在群中的用户
  162 + List<ImGroupMemberDO> activeMembers = groupMemberService.getActiveGroupMemberListByGroupId(groupId);
  163 + activeMembers.forEach(member -> inviteReqVO.getMemberUserIds().remove(member.getUserId()));
  164 + if (CollUtil.isEmpty(inviteReqVO.getMemberUserIds())) {
  165 + return;
  166 + }
  167 + // 1.3 校验群人数上限
  168 + List<Long> memberUserIds = inviteReqVO.getMemberUserIds();
  169 + if (activeMembers.size() + memberUserIds.size() > MAX_GROUP_MEMBER) {
  170 + throw exception(GROUP_MEMBER_EXCEED, MAX_GROUP_MEMBER);
  171 + }
155 172  
156   - // 2. 添加群成员
157   - ImGroupMemberDO member = groupMemberService.addGroupMember(inviteReqVO.getGroupId(), inviteReqVO.getUserId());
158   - return member.getId();
  173 + // 2. 批量添加群成员
  174 + groupMemberService.addGroupMembers(groupId, memberUserIds);
  175 +
  176 + // 3.1 推送群创建事件给被邀请人(让客户端新增群会话)
  177 + webSocketService.sendGroupMessageAsync(new HashSet<>(memberUserIds),
  178 + ImGroupMessageDTO.ofGroupCreate(userId, groupId));
  179 + // 3.2 发送邀请提示消息给所有群成员
  180 + AdminUserRespDTO inviter = adminUserApi.getUser(userId);
  181 + Map<Long, AdminUserRespDTO> invitedUserMap = adminUserApi.getUserMap(memberUserIds);
  182 + String invitedNames = memberUserIds.stream()
  183 + .map(id -> invitedUserMap.containsKey(id) ? invitedUserMap.get(id).getNickname() : String.valueOf(id))
  184 + .collect(Collectors.joining(","));
  185 + String tipContent = StrUtil.format(GROUP_INVITE_TIP_MESSAGE, inviter.getNickname(), invitedNames);
  186 + List<ImGroupMemberDO> allMembers = groupMemberService.getActiveGroupMemberListByGroupId(groupId);
  187 + Set<Long> allMemberUserIds = convertSet(allMembers, ImGroupMemberDO::getUserId);
  188 + groupMessageService.sendTipGroupMessage(userId, groupId, allMemberUserIds, tipContent);
159 189 }
160 190  
161 191 @Override
... ... @@ -180,23 +210,25 @@ public class ImGroupServiceImpl implements ImGroupService {
180 210 }
181 211  
182 212 @Override
183   - public void removeGroupMember(Long groupId, Long memberUserId, Long userId) {
  213 + public void removeGroupMember(Long userId, ImGroupMemberRemoveReqVO removeReqVO) {
  214 + Long groupId = removeReqVO.getGroupId();
184 215 // 1. 校验群存在 + 当前用户是群主
185 216 validateGroupOwner(groupId, userId);
186 217 // 2. 不能移除自己
187   - if (ObjUtil.equal(memberUserId, userId)) {
  218 + if (removeReqVO.getMemberUserIds().contains(userId)) {
188 219 throw exception(GROUP_CANNOT_REMOVE_SELF);
189 220 }
190 221  
191   - // 3.1 移除群成员
192   - groupMemberService.removeGroupMember(groupId, memberUserId);
193   - // 3.2 清理已读缓存
194   - groupMessageService.deleteReadMaxMessageId(groupId, memberUserId);
  222 + // 3.1 批量移除群成员
  223 + Set<Long> memberUserIds = new HashSet<>(removeReqVO.getMemberUserIds());
  224 + groupMemberService.removeGroupMembers(groupId, memberUserIds);
  225 + // 3.2 批量清理已读缓存
  226 + groupMessageService.deleteReadMaxMessageIds(groupId, memberUserIds);
195 227  
196 228 // 4.1 发送踢出提示消息(TIP_TEXT)
197   - groupMessageService.sendTipGroupMessage(userId, groupId, Set.of(memberUserId), GROUP_REMOVE_TIP_MESSAGE);
  229 + groupMessageService.sendTipGroupMessage(userId, groupId, memberUserIds, GROUP_REMOVE_TIP_MESSAGE);
198 230 // 4.2 推送群删除事件
199   - webSocketService.sendGroupMessageAsync(Set.of(memberUserId),
  231 + webSocketService.sendGroupMessageAsync(memberUserIds,
200 232 ImGroupMessageDTO.ofGroupDelete(userId, groupId));
201 233 }
202 234  
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java
... ... @@ -4,6 +4,7 @@ import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessa
4 4 import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO;
5 5 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
6 6  
  7 +import java.util.Collection;
7 8 import java.util.List;
8 9 import java.util.Set;
9 10  
... ... @@ -81,6 +82,16 @@ public interface ImGroupMessageService {
81 82 void deleteReadMaxMessageId(Long groupId, Long userId);
82 83  
83 84 /**
  85 + * 批量清理用户在某群的已读位置缓存
  86 + * <p>
  87 + * 用于批量踢出场景
  88 + *
  89 + * @param groupId 群编号
  90 + * @param userIds 用户编号集合
  91 + */
  92 + void deleteReadMaxMessageIds(Long groupId, Collection<Long> userIds);
  93 +
  94 + /**
84 95 * 清理某群所有用户的已读位置缓存
85 96 * <p>
86 97 * 用于群解散场景
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java
... ... @@ -477,6 +477,11 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
477 477 }
478 478  
479 479 @Override
  480 + public void deleteReadMaxMessageIds(Long groupId, Collection<Long> userIds) {
  481 + groupMessageReadRedisDAO.deleteReadMaxMessageIds(groupId, userIds);
  482 + }
  483 +
  484 + @Override
480 485 public void deleteReadMaxMessageIdMap(Long groupId) {
481 486 groupMessageReadRedisDAO.deleteReadMaxMessageIdMap(groupId);
482 487 }
... ...