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 +6,7 @@ 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.controller.admin.group.vo.member.ImGroupMemberInviteReqVO;
  9 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO;
9 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; 10 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
10 import cn.iocoder.yudao.module.im.service.group.ImGroupService; 11 import cn.iocoder.yudao.module.im.service.group.ImGroupService;
11 import io.swagger.v3.oas.annotations.Operation; 12 import io.swagger.v3.oas.annotations.Operation;
@@ -29,7 +30,7 @@ import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLogi @@ -29,7 +30,7 @@ import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLogi
29 public class ImGroupController { 30 public class ImGroupController {
30 31
31 @Resource 32 @Resource
32 - private ImGroupService imGroupService; 33 + private ImGroupService groupService;
33 34
34 // ==================== 群的写操作 ==================== 35 // ==================== 群的写操作 ====================
35 36
@@ -37,7 +38,7 @@ public class ImGroupController { @@ -37,7 +38,7 @@ public class ImGroupController {
37 @Operation(summary = "创建群") 38 @Operation(summary = "创建群")
38 @PreAuthorize("@ss.hasPermission('im:group:create')") 39 @PreAuthorize("@ss.hasPermission('im:group:create')")
39 public CommonResult<ImGroupRespVO> createGroup(@Valid @RequestBody ImGroupCreateReqVO createReqVO) { 40 public CommonResult<ImGroupRespVO> createGroup(@Valid @RequestBody ImGroupCreateReqVO createReqVO) {
40 - ImGroupDO group = imGroupService.createGroup(createReqVO, getLoginUserId()); 41 + ImGroupDO group = groupService.createGroup(createReqVO, getLoginUserId());
41 return success(BeanUtils.toBean(group, ImGroupRespVO.class)); 42 return success(BeanUtils.toBean(group, ImGroupRespVO.class));
42 } 43 }
43 44
@@ -45,7 +46,7 @@ public class ImGroupController { @@ -45,7 +46,7 @@ public class ImGroupController {
45 @Operation(summary = "更新群") 46 @Operation(summary = "更新群")
46 @PreAuthorize("@ss.hasPermission('im:group:update')") 47 @PreAuthorize("@ss.hasPermission('im:group:update')")
47 public CommonResult<ImGroupRespVO> updateGroup(@Valid @RequestBody ImGroupUpdateReqVO updateReqVO) { 48 public CommonResult<ImGroupRespVO> updateGroup(@Valid @RequestBody ImGroupUpdateReqVO updateReqVO) {
48 - ImGroupDO group = imGroupService.updateGroup(updateReqVO, getLoginUserId()); 49 + ImGroupDO group = groupService.updateGroup(updateReqVO, getLoginUserId());
49 return success(BeanUtils.toBean(group, ImGroupRespVO.class)); 50 return success(BeanUtils.toBean(group, ImGroupRespVO.class));
50 } 51 }
51 52
@@ -54,7 +55,7 @@ public class ImGroupController { @@ -54,7 +55,7 @@ public class ImGroupController {
54 @Parameter(name = "id", description = "群编号", required = true) 55 @Parameter(name = "id", description = "群编号", required = true)
55 @PreAuthorize("@ss.hasPermission('im:group:delete')") 56 @PreAuthorize("@ss.hasPermission('im:group:delete')")
56 public CommonResult<Boolean> dissolveGroup(@RequestParam("id") Long id) { 57 public CommonResult<Boolean> dissolveGroup(@RequestParam("id") Long id) {
57 - imGroupService.dissolveGroup(id, getLoginUserId()); 58 + groupService.dissolveGroup(id, getLoginUserId());
58 return success(true); 59 return success(true);
59 } 60 }
60 61
@@ -65,14 +66,14 @@ public class ImGroupController { @@ -65,14 +66,14 @@ public class ImGroupController {
65 @Parameter(name = "id", description = "编号", required = true, example = "1024") 66 @Parameter(name = "id", description = "编号", required = true, example = "1024")
66 @PreAuthorize("@ss.hasPermission('im:group:query')") 67 @PreAuthorize("@ss.hasPermission('im:group:query')")
67 public CommonResult<ImGroupRespVO> getGroup(@RequestParam("id") Long id) { 68 public CommonResult<ImGroupRespVO> getGroup(@RequestParam("id") Long id) {
68 - ImGroupDO group = imGroupService.getGroup(id); 69 + ImGroupDO group = groupService.getGroup(id);
69 return success(BeanUtils.toBean(group, ImGroupRespVO.class)); 70 return success(BeanUtils.toBean(group, ImGroupRespVO.class));
70 } 71 }
71 72
72 @GetMapping("/list") 73 @GetMapping("/list")
73 @Operation(summary = "获得当前登录用户的群列表") 74 @Operation(summary = "获得当前登录用户的群列表")
74 public CommonResult<List<ImGroupRespVO>> getMyGroupList() { 75 public CommonResult<List<ImGroupRespVO>> getMyGroupList() {
75 - List<ImGroupDO> groups = imGroupService.getActiveGroupList(getLoginUserId()); 76 + List<ImGroupDO> groups = groupService.getActiveGroupList(getLoginUserId());
76 return success(BeanUtils.toBean(groups, ImGroupRespVO.class)); 77 return success(BeanUtils.toBean(groups, ImGroupRespVO.class));
77 } 78 }
78 79
@@ -81,8 +82,9 @@ public class ImGroupController { @@ -81,8 +82,9 @@ public class ImGroupController {
81 @PostMapping("/invite") 82 @PostMapping("/invite")
82 @Operation(summary = "邀请用户加入群") 83 @Operation(summary = "邀请用户加入群")
83 @PreAuthorize("@ss.hasPermission('im:group:update')") 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 @DeleteMapping("/quit") 90 @DeleteMapping("/quit")
@@ -90,16 +92,15 @@ public class ImGroupController { @@ -90,16 +92,15 @@ public class ImGroupController {
90 @Parameter(name = "groupId", description = "群编号", required = true) 92 @Parameter(name = "groupId", description = "群编号", required = true)
91 @PreAuthorize("@ss.hasPermission('im:group:update')") 93 @PreAuthorize("@ss.hasPermission('im:group:update')")
92 public CommonResult<Boolean> quitGroup(@RequestParam("groupId") Long groupId) { 94 public CommonResult<Boolean> quitGroup(@RequestParam("groupId") Long groupId) {
93 - imGroupService.quitGroup(groupId, getLoginUserId()); 95 + groupService.quitGroup(groupId, getLoginUserId());
94 return success(true); 96 return success(true);
95 } 97 }
96 98
97 @DeleteMapping("/kicking") 99 @DeleteMapping("/kicking")
98 @Operation(summary = "移除群成员") 100 @Operation(summary = "移除群成员")
99 @PreAuthorize("@ss.hasPermission('im:group:update')") 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 return success(true); 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 package cn.iocoder.yudao.module.im.controller.admin.group.vo.member; 1 package cn.iocoder.yudao.module.im.controller.admin.group.vo.member;
2 2
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
4 import jakarta.validation.constraints.NotNull; 5 import jakarta.validation.constraints.NotNull;
5 import lombok.Data; 6 import lombok.Data;
6 7
  8 +import java.util.List;
  9 +
7 @Schema(description = "管理后台 - 群成员邀请 Request VO") 10 @Schema(description = "管理后台 - 群成员邀请 Request VO")
8 @Data 11 @Data
9 public class ImGroupMemberInviteReqVO { 12 public class ImGroupMemberInviteReqVO {
@@ -12,8 +15,8 @@ public class ImGroupMemberInviteReqVO { @@ -12,8 +15,8 @@ public class ImGroupMemberInviteReqVO {
12 @NotNull(message = "群编号不能为空") 15 @NotNull(message = "群编号不能为空")
13 private Long groupId; 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,6 +7,7 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
7 import org.apache.ibatis.annotations.Mapper; 7 import org.apache.ibatis.annotations.Mapper;
8 8
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
  10 +import java.util.Collection;
10 import java.util.List; 11 import java.util.List;
11 12
12 /** 13 /**
@@ -27,6 +28,12 @@ public interface ImGroupMemberMapper extends BaseMapperX&lt;ImGroupMemberDO&gt; { @@ -27,6 +28,12 @@ public interface ImGroupMemberMapper extends BaseMapperX&lt;ImGroupMemberDO&gt; {
27 .eq(ImGroupMemberDO::getUserId, userId)); 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 default List<ImGroupMemberDO> selectListByGroupIdAndStatus(Long groupId, Integer status) { 37 default List<ImGroupMemberDO> selectListByGroupIdAndStatus(Long groupId, Integer status) {
31 return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>() 38 return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>()
32 .eq(ImGroupMemberDO::getGroupId, groupId) 39 .eq(ImGroupMemberDO::getGroupId, groupId)
@@ -62,4 +69,13 @@ public interface ImGroupMemberMapper extends BaseMapperX&lt;ImGroupMemberDO&gt; { @@ -62,4 +69,13 @@ public interface ImGroupMemberMapper extends BaseMapperX&lt;ImGroupMemberDO&gt; {
62 .eq(ImGroupMemberDO::getStatus, oldStatus)); 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 \ No newline at end of file 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,6 +5,7 @@ import jakarta.annotation.Resource;
5 import org.springframework.data.redis.core.StringRedisTemplate; 5 import org.springframework.data.redis.core.StringRedisTemplate;
6 import org.springframework.stereotype.Repository; 6 import org.springframework.stereotype.Repository;
7 7
  8 +import java.util.Collection;
8 import java.util.HashMap; 9 import java.util.HashMap;
9 import java.util.Map; 10 import java.util.Map;
10 11
@@ -73,6 +74,18 @@ public class GroupMessageReadRedisDAO { @@ -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 * 删除某群所有用户的已读位置(整个 Hash Key) 89 * 删除某群所有用户的已读位置(整个 Hash Key)
77 * 90 *
78 * @param groupId 群编号 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,6 +5,7 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
5 import jakarta.validation.Valid; 5 import jakarta.validation.Valid;
6 6
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
  8 +import java.util.Collection;
8 import java.util.List; 9 import java.util.List;
9 10
10 /** 11 /**
@@ -69,6 +70,14 @@ public interface ImGroupMemberService { @@ -69,6 +70,14 @@ public interface ImGroupMemberService {
69 ImGroupMemberDO addGroupMember(Long groupId, Long userId); 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 * @param groupId 群编号 83 * @param groupId 群编号
@@ -98,6 +107,16 @@ public interface ImGroupMemberService { @@ -98,6 +107,16 @@ public interface ImGroupMemberService {
98 void removeGroupMember(Long groupId, Long userId); 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 * 移除群的全部成员(设置为 DISABLE 状态) 120 * 移除群的全部成员(设置为 DISABLE 状态)
102 * <p> 121 * <p>
103 * 用于群解散场景 122 * 用于群解散场景
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java
1 package cn.iocoder.yudao.module.im.service.group; 1 package cn.iocoder.yudao.module.im.service.group;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; 6 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO;
@@ -10,9 +11,13 @@ import org.springframework.stereotype.Service; @@ -10,9 +11,13 @@ import org.springframework.stereotype.Service;
10 import org.springframework.validation.annotation.Validated; 11 import org.springframework.validation.annotation.Validated;
11 12
12 import java.time.LocalDateTime; 13 import java.time.LocalDateTime;
  14 +import java.util.ArrayList;
  15 +import java.util.Collection;
13 import java.util.List; 16 import java.util.List;
  17 +import java.util.Map;
14 18
15 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 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 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.GROUP_MEMBER_NOT_IN_GROUP; 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,31 +30,31 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.GROUP_MEMBER_N
25 public class ImGroupMemberServiceImpl implements ImGroupMemberService { 30 public class ImGroupMemberServiceImpl implements ImGroupMemberService {
26 31
27 @Resource 32 @Resource
28 - private ImGroupMemberMapper imGroupMemberMapper; 33 + private ImGroupMemberMapper groupMemberMapper;
29 34
30 @Override 35 @Override
31 public ImGroupMemberDO getGroupMember(Long id) { 36 public ImGroupMemberDO getGroupMember(Long id) {
32 - return imGroupMemberMapper.selectById(id); 37 + return groupMemberMapper.selectById(id);
33 } 38 }
34 39
35 @Override 40 @Override
36 public List<ImGroupMemberDO> getGroupMemberListByGroupId(Long groupId) { 41 public List<ImGroupMemberDO> getGroupMemberListByGroupId(Long groupId) {
37 - return imGroupMemberMapper.selectListByGroupId(groupId); 42 + return groupMemberMapper.selectListByGroupId(groupId);
38 } 43 }
39 44
40 @Override 45 @Override
41 public List<ImGroupMemberDO> getActiveGroupMemberListByGroupId(Long groupId) { 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 @Override 50 @Override
46 public List<ImGroupMemberDO> getActiveGroupMemberListByUserId(Long userId) { 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 @Override 55 @Override
51 public List<ImGroupMemberDO> getQuitGroupMemberListByUserId(Long userId, LocalDateTime minQuitTime) { 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 @Override 60 @Override
@@ -57,13 +62,42 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @@ -57,13 +62,42 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
57 ImGroupMemberDO member = new ImGroupMemberDO() 62 ImGroupMemberDO member = new ImGroupMemberDO()
58 .setGroupId(groupId).setUserId(userId) 63 .setGroupId(groupId).setUserId(userId)
59 .setStatus(CommonStatusEnum.ENABLE.getStatus()).setJoinTime(LocalDateTime.now()); 64 .setStatus(CommonStatusEnum.ENABLE.getStatus()).setJoinTime(LocalDateTime.now());
60 - imGroupMemberMapper.insert(member); 65 + groupMemberMapper.insert(member);
61 return member; 66 return member;
62 } 67 }
63 68
64 @Override 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 public ImGroupMemberDO validateMemberInGroup(Long groupId, Long userId) { 99 public ImGroupMemberDO validateMemberInGroup(Long groupId, Long userId) {
66 - ImGroupMemberDO member = imGroupMemberMapper.selectByGroupIdAndUserId(groupId, userId); 100 + ImGroupMemberDO member = groupMemberMapper.selectByGroupIdAndUserId(groupId, userId);
67 if (member == null || CommonStatusEnum.DISABLE.getStatus().equals(member.getStatus())) { 101 if (member == null || CommonStatusEnum.DISABLE.getStatus().equals(member.getStatus())) {
68 throw exception(GROUP_MEMBER_NOT_IN_GROUP); 102 throw exception(GROUP_MEMBER_NOT_IN_GROUP);
69 } 103 }
@@ -77,7 +111,7 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @@ -77,7 +111,7 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
77 // 2. 更新群成员信息 111 // 2. 更新群成员信息
78 ImGroupMemberDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupMemberDO.class) 112 ImGroupMemberDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupMemberDO.class)
79 .setId(member.getId()); 113 .setId(member.getId());
80 - imGroupMemberMapper.updateById(updateObj); 114 + groupMemberMapper.updateById(updateObj);
81 } 115 }
82 116
83 @Override 117 @Override
@@ -85,14 +119,22 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @@ -85,14 +119,22 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
85 // 1. 校验是群的有效成员 119 // 1. 校验是群的有效成员
86 ImGroupMemberDO member = validateMemberInGroup(groupId, userId); 120 ImGroupMemberDO member = validateMemberInGroup(groupId, userId);
87 // 2. 更新为退群状态 121 // 2. 更新为退群状态
88 - imGroupMemberMapper.updateById(new ImGroupMemberDO().setId(member.getId()) 122 + groupMemberMapper.updateById(new ImGroupMemberDO().setId(member.getId())
89 .setStatus(CommonStatusEnum.DISABLE.getStatus()) 123 .setStatus(CommonStatusEnum.DISABLE.getStatus())
90 .setQuitTime(LocalDateTime.now())); 124 .setQuitTime(LocalDateTime.now()));
91 } 125 }
92 126
93 @Override 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 public void removeGroupMembersByGroupId(Long groupId) { 136 public void removeGroupMembersByGroupId(Long groupId) {
95 - imGroupMemberMapper.updateByGroupIdAndStatus(groupId, CommonStatusEnum.ENABLE.getStatus(), 137 + groupMemberMapper.updateByGroupIdAndStatus(groupId, CommonStatusEnum.ENABLE.getStatus(),
96 new ImGroupMemberDO().setStatus(CommonStatusEnum.DISABLE.getStatus()) 138 new ImGroupMemberDO().setStatus(CommonStatusEnum.DISABLE.getStatus())
97 .setQuitTime(LocalDateTime.now())); 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,6 +3,7 @@ package cn.iocoder.yudao.module.im.service.group;
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.controller.admin.group.vo.member.ImGroupMemberInviteReqVO;
  6 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO;
6 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; 7 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
7 import jakarta.validation.Valid; 8 import jakarta.validation.Valid;
8 9
@@ -92,13 +93,13 @@ public interface ImGroupService { @@ -92,13 +93,13 @@ public interface ImGroupService {
92 /** 93 /**
93 * 邀请用户加入群 94 * 邀请用户加入群
94 * <p> 95 * <p>
95 - * 仅群主可执行 96 + * 群成员即可执行,支持批量。
  97 + * 校验群人数上限,邀请后推送提示消息和群创建事件给被邀请人。
96 * 98 *
97 - * @param userId 当前登录用户编号(群主) 99 + * @param userId 当前登录用户编号
98 * @param inviteReqVO 邀请信息 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,12 +114,11 @@ public interface ImGroupService {
113 /** 114 /**
114 * 移除群成员(踢人) 115 * 移除群成员(踢人)
115 * <p> 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,6 +11,7 @@ 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; 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 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; 15 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO;
15 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; 16 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
16 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; 17 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
@@ -30,8 +31,11 @@ import org.springframework.validation.annotation.Validated; @@ -30,8 +31,11 @@ import org.springframework.validation.annotation.Validated;
30 31
31 import java.time.LocalDateTime; 32 import java.time.LocalDateTime;
32 import java.util.Collections; 33 import java.util.Collections;
  34 +import java.util.HashSet;
33 import java.util.List; 35 import java.util.List;
  36 +import java.util.Map;
34 import java.util.Set; 37 import java.util.Set;
  38 +import java.util.stream.Collectors;
35 39
36 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 40 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
37 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 41 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@@ -149,13 +153,39 @@ public class ImGroupServiceImpl implements ImGroupService { @@ -149,13 +153,39 @@ public class ImGroupServiceImpl implements ImGroupService {
149 // ==================== 群成员的写操作 ==================== 153 // ==================== 群成员的写操作 ====================
150 154
151 @Override 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 @Override 191 @Override
@@ -180,23 +210,25 @@ public class ImGroupServiceImpl implements ImGroupService { @@ -180,23 +210,25 @@ public class ImGroupServiceImpl implements ImGroupService {
180 } 210 }
181 211
182 @Override 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 // 1. 校验群存在 + 当前用户是群主 215 // 1. 校验群存在 + 当前用户是群主
185 validateGroupOwner(groupId, userId); 216 validateGroupOwner(groupId, userId);
186 // 2. 不能移除自己 217 // 2. 不能移除自己
187 - if (ObjUtil.equal(memberUserId, userId)) { 218 + if (removeReqVO.getMemberUserIds().contains(userId)) {
188 throw exception(GROUP_CANNOT_REMOVE_SELF); 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 // 4.1 发送踢出提示消息(TIP_TEXT) 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 // 4.2 推送群删除事件 230 // 4.2 推送群删除事件
199 - webSocketService.sendGroupMessageAsync(Set.of(memberUserId), 231 + webSocketService.sendGroupMessageAsync(memberUserIds,
200 ImGroupMessageDTO.ofGroupDelete(userId, groupId)); 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,6 +4,7 @@ import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessa
4 import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO; 4 import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO;
5 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; 5 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
6 6
  7 +import java.util.Collection;
7 import java.util.List; 8 import java.util.List;
8 import java.util.Set; 9 import java.util.Set;
9 10
@@ -81,6 +82,16 @@ public interface ImGroupMessageService { @@ -81,6 +82,16 @@ public interface ImGroupMessageService {
81 void deleteReadMaxMessageId(Long groupId, Long userId); 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 * <p> 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,6 +477,11 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
477 } 477 }
478 478
479 @Override 479 @Override
  480 + public void deleteReadMaxMessageIds(Long groupId, Collection<Long> userIds) {
  481 + groupMessageReadRedisDAO.deleteReadMaxMessageIds(groupId, userIds);
  482 + }
  483 +
  484 + @Override
480 public void deleteReadMaxMessageIdMap(Long groupId) { 485 public void deleteReadMaxMessageIdMap(Long groupId) {
481 groupMessageReadRedisDAO.deleteReadMaxMessageIdMap(groupId); 486 groupMessageReadRedisDAO.deleteReadMaxMessageIdMap(groupId);
482 } 487 }