Commit 84304718dc19627516fab687d7c010c9eade9b81
1 parent
b814a873
✨ feat(group): 支持批量邀请和移除群成员
Showing
11 changed files
with
213 additions
and
49 deletions
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<ImGroupMemberDO> { |
| 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<ImGroupMemberDO> { |
| 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 | } | ... | ... |