diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java index 0520785..15cf5b8 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java +++ b/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; import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupRespVO; import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; import cn.iocoder.yudao.module.im.service.group.ImGroupService; import io.swagger.v3.oas.annotations.Operation; @@ -29,7 +30,7 @@ import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLogi public class ImGroupController { @Resource - private ImGroupService imGroupService; + private ImGroupService groupService; // ==================== 群的写操作 ==================== @@ -37,7 +38,7 @@ public class ImGroupController { @Operation(summary = "创建群") @PreAuthorize("@ss.hasPermission('im:group:create')") public CommonResult createGroup(@Valid @RequestBody ImGroupCreateReqVO createReqVO) { - ImGroupDO group = imGroupService.createGroup(createReqVO, getLoginUserId()); + ImGroupDO group = groupService.createGroup(createReqVO, getLoginUserId()); return success(BeanUtils.toBean(group, ImGroupRespVO.class)); } @@ -45,7 +46,7 @@ public class ImGroupController { @Operation(summary = "更新群") @PreAuthorize("@ss.hasPermission('im:group:update')") public CommonResult updateGroup(@Valid @RequestBody ImGroupUpdateReqVO updateReqVO) { - ImGroupDO group = imGroupService.updateGroup(updateReqVO, getLoginUserId()); + ImGroupDO group = groupService.updateGroup(updateReqVO, getLoginUserId()); return success(BeanUtils.toBean(group, ImGroupRespVO.class)); } @@ -54,7 +55,7 @@ public class ImGroupController { @Parameter(name = "id", description = "群编号", required = true) @PreAuthorize("@ss.hasPermission('im:group:delete')") public CommonResult dissolveGroup(@RequestParam("id") Long id) { - imGroupService.dissolveGroup(id, getLoginUserId()); + groupService.dissolveGroup(id, getLoginUserId()); return success(true); } @@ -65,14 +66,14 @@ public class ImGroupController { @Parameter(name = "id", description = "编号", required = true, example = "1024") @PreAuthorize("@ss.hasPermission('im:group:query')") public CommonResult getGroup(@RequestParam("id") Long id) { - ImGroupDO group = imGroupService.getGroup(id); + ImGroupDO group = groupService.getGroup(id); return success(BeanUtils.toBean(group, ImGroupRespVO.class)); } @GetMapping("/list") @Operation(summary = "获得当前登录用户的群列表") public CommonResult> getMyGroupList() { - List groups = imGroupService.getActiveGroupList(getLoginUserId()); + List groups = groupService.getActiveGroupList(getLoginUserId()); return success(BeanUtils.toBean(groups, ImGroupRespVO.class)); } @@ -81,8 +82,9 @@ public class ImGroupController { @PostMapping("/invite") @Operation(summary = "邀请用户加入群") @PreAuthorize("@ss.hasPermission('im:group:update')") - public CommonResult inviteGroupMember(@Valid @RequestBody ImGroupMemberInviteReqVO inviteReqVO) { - return success(imGroupService.inviteGroupMember(getLoginUserId(), inviteReqVO)); + public CommonResult inviteGroupMember(@Valid @RequestBody ImGroupMemberInviteReqVO inviteReqVO) { + groupService.inviteGroupMember(getLoginUserId(), inviteReqVO); + return success(true); } @DeleteMapping("/quit") @@ -90,16 +92,15 @@ public class ImGroupController { @Parameter(name = "groupId", description = "群编号", required = true) @PreAuthorize("@ss.hasPermission('im:group:update')") public CommonResult quitGroup(@RequestParam("groupId") Long groupId) { - imGroupService.quitGroup(groupId, getLoginUserId()); + groupService.quitGroup(groupId, getLoginUserId()); return success(true); } @DeleteMapping("/kicking") @Operation(summary = "移除群成员") @PreAuthorize("@ss.hasPermission('im:group:update')") - public CommonResult removeGroupMember(@RequestParam("groupId") Long groupId, - @RequestParam("memberUserId") Long memberUserId) { - imGroupService.removeGroupMember(groupId, memberUserId, getLoginUserId()); + public CommonResult removeGroupMember(@Valid @RequestBody ImGroupMemberRemoveReqVO removeReqVO) { + groupService.removeGroupMember(getLoginUserId(), removeReqVO); return success(true); } diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberInviteReqVO.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberInviteReqVO.java index 656b42f..12cd09d 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberInviteReqVO.java +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberInviteReqVO.java @@ -1,9 +1,12 @@ package cn.iocoder.yudao.module.im.controller.admin.group.vo.member; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; +import java.util.List; + @Schema(description = "管理后台 - 群成员邀请 Request VO") @Data public class ImGroupMemberInviteReqVO { @@ -12,8 +15,8 @@ public class ImGroupMemberInviteReqVO { @NotNull(message = "群编号不能为空") private Long groupId; - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21730") - @NotNull(message = "用户编号不能为空") - private Long userId; + @Schema(description = "被邀请的用户编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2, 3]") + @NotEmpty(message = "被邀请的用户编号列表不能为空") + private List memberUserIds; } diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberRemoveReqVO.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberRemoveReqVO.java new file mode 100644 index 0000000..9862b12 --- /dev/null +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberRemoveReqVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo.member; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 群成员移除 Request VO") +@Data +public class ImGroupMemberRemoveReqVO { + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "群编号不能为空") + private Long groupId; + + @Schema(description = "被移除的用户编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2, 3]") + @NotEmpty(message = "被移除的用户编号列表不能为空") + private List memberUserIds; + +} diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java index 0816d02..0f23180 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java +++ b/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; import org.apache.ibatis.annotations.Mapper; import java.time.LocalDateTime; +import java.util.Collection; import java.util.List; /** @@ -27,6 +28,12 @@ public interface ImGroupMemberMapper extends BaseMapperX { .eq(ImGroupMemberDO::getUserId, userId)); } + default List selectListByGroupIdAndUserIds(Long groupId, Collection userIds) { + return selectList(new LambdaQueryWrapperX() + .eq(ImGroupMemberDO::getGroupId, groupId) + .in(ImGroupMemberDO::getUserId, userIds)); + } + default List selectListByGroupIdAndStatus(Long groupId, Integer status) { return selectList(new LambdaQueryWrapperX() .eq(ImGroupMemberDO::getGroupId, groupId) @@ -62,4 +69,13 @@ public interface ImGroupMemberMapper extends BaseMapperX { .eq(ImGroupMemberDO::getStatus, oldStatus)); } + @SuppressWarnings("UnusedReturnValue") + default int updateByGroupIdAndUserIdsAndStatus(Long groupId, Collection userIds, + Integer oldStatus, ImGroupMemberDO updateObj) { + return update(updateObj, new LambdaQueryWrapperX() + .eq(ImGroupMemberDO::getGroupId, groupId) + .in(ImGroupMemberDO::getUserId, userIds) + .eq(ImGroupMemberDO::getStatus, oldStatus)); + } + } \ No newline at end of file diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/GroupMessageReadRedisDAO.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/GroupMessageReadRedisDAO.java index 4ef7dc3..ede9986 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/GroupMessageReadRedisDAO.java +++ b/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; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Repository; +import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -73,6 +74,18 @@ public class GroupMessageReadRedisDAO { } /** + * 批量删除用户在某群的已读位置 + * + * @param groupId 群编号 + * @param userIds 用户编号集合 + */ + public void deleteReadMaxMessageIds(Long groupId, Collection userIds) { + String key = formatKey(groupId); + Object[] hashKeys = userIds.stream().map(String::valueOf).toArray(); + stringRedisTemplate.opsForHash().delete(key, hashKeys); + } + + /** * 删除某群所有用户的已读位置(整个 Hash Key) * * @param groupId 群编号 diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java index 02206a7..9470fba 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java +++ b/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; import jakarta.validation.Valid; import java.time.LocalDateTime; +import java.util.Collection; import java.util.List; /** @@ -69,6 +70,14 @@ public interface ImGroupMemberService { ImGroupMemberDO addGroupMember(Long groupId, Long userId); /** + * 批量添加群成员(入群) + * + * @param groupId 群编号 + * @param userIds 用户编号集合 + */ + void addGroupMembers(Long groupId, Collection userIds); + + /** * 校验用户是否为群的有效成员 * * @param groupId 群编号 @@ -98,6 +107,16 @@ public interface ImGroupMemberService { void removeGroupMember(Long groupId, Long userId); /** + * 批量移除指定群成员(设置为 DISABLE 状态) + *

+ * 用于批量踢出场景 + * + * @param groupId 群编号 + * @param userIds 用户编号集合 + */ + void removeGroupMembers(Long groupId, Collection userIds); + + /** * 移除群的全部成员(设置为 DISABLE 状态) *

* 用于群解散场景 diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java index d6eb01e..efda2e0 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.im.service.group; +import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; @@ -10,9 +11,13 @@ import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.Map; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.GROUP_MEMBER_NOT_IN_GROUP; /** @@ -25,31 +30,31 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.GROUP_MEMBER_N public class ImGroupMemberServiceImpl implements ImGroupMemberService { @Resource - private ImGroupMemberMapper imGroupMemberMapper; + private ImGroupMemberMapper groupMemberMapper; @Override public ImGroupMemberDO getGroupMember(Long id) { - return imGroupMemberMapper.selectById(id); + return groupMemberMapper.selectById(id); } @Override public List getGroupMemberListByGroupId(Long groupId) { - return imGroupMemberMapper.selectListByGroupId(groupId); + return groupMemberMapper.selectListByGroupId(groupId); } @Override public List getActiveGroupMemberListByGroupId(Long groupId) { - return imGroupMemberMapper.selectListByGroupIdAndStatus(groupId, CommonStatusEnum.ENABLE.getStatus()); + return groupMemberMapper.selectListByGroupIdAndStatus(groupId, CommonStatusEnum.ENABLE.getStatus()); } @Override public List getActiveGroupMemberListByUserId(Long userId) { - return imGroupMemberMapper.selectListByUserIdAndStatus(userId, CommonStatusEnum.ENABLE.getStatus()); + return groupMemberMapper.selectListByUserIdAndStatus(userId, CommonStatusEnum.ENABLE.getStatus()); } @Override public List getQuitGroupMemberListByUserId(Long userId, LocalDateTime minQuitTime) { - return imGroupMemberMapper.selectQuitListByUserId(userId, minQuitTime); + return groupMemberMapper.selectQuitListByUserId(userId, minQuitTime); } @Override @@ -57,13 +62,42 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { ImGroupMemberDO member = new ImGroupMemberDO() .setGroupId(groupId).setUserId(userId) .setStatus(CommonStatusEnum.ENABLE.getStatus()).setJoinTime(LocalDateTime.now()); - imGroupMemberMapper.insert(member); + groupMemberMapper.insert(member); return member; } @Override + public void addGroupMembers(Long groupId, Collection userIds) { + LocalDateTime now = LocalDateTime.now(); + // 1.1 查询已有记录(含已退群的 DISABLE 记录) + List existMembers = groupMemberMapper.selectListByGroupIdAndUserIds(groupId, userIds); + Map existMap = convertMap(existMembers, ImGroupMemberDO::getUserId); + // 1.2 分类:已有记录 → UPDATE,新成员 → INSERT + List inserts = new ArrayList<>(); + List updates = new ArrayList<>(); + for (Long userId : userIds) { + ImGroupMemberDO exist = existMap.get(userId); + if (exist == null) { + inserts.add(new ImGroupMemberDO().setGroupId(groupId).setUserId(userId) + .setStatus(CommonStatusEnum.ENABLE.getStatus()).setJoinTime(now)); + } else if (CommonStatusEnum.DISABLE.getStatus().equals(exist.getStatus())) { + updates.add(new ImGroupMemberDO().setId(exist.getId()) + .setStatus(CommonStatusEnum.ENABLE.getStatus()).setJoinTime(now)); + } + } + + // 2. 执行 + if (CollUtil.isNotEmpty(inserts)) { + groupMemberMapper.insertBatch(inserts); + } + if (CollUtil.isNotEmpty(updates)) { + groupMemberMapper.updateBatch(updates); + } + } + + @Override public ImGroupMemberDO validateMemberInGroup(Long groupId, Long userId) { - ImGroupMemberDO member = imGroupMemberMapper.selectByGroupIdAndUserId(groupId, userId); + ImGroupMemberDO member = groupMemberMapper.selectByGroupIdAndUserId(groupId, userId); if (member == null || CommonStatusEnum.DISABLE.getStatus().equals(member.getStatus())) { throw exception(GROUP_MEMBER_NOT_IN_GROUP); } @@ -77,7 +111,7 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { // 2. 更新群成员信息 ImGroupMemberDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupMemberDO.class) .setId(member.getId()); - imGroupMemberMapper.updateById(updateObj); + groupMemberMapper.updateById(updateObj); } @Override @@ -85,14 +119,22 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { // 1. 校验是群的有效成员 ImGroupMemberDO member = validateMemberInGroup(groupId, userId); // 2. 更新为退群状态 - imGroupMemberMapper.updateById(new ImGroupMemberDO().setId(member.getId()) + groupMemberMapper.updateById(new ImGroupMemberDO().setId(member.getId()) .setStatus(CommonStatusEnum.DISABLE.getStatus()) .setQuitTime(LocalDateTime.now())); } @Override + public void removeGroupMembers(Long groupId, Collection userIds) { + groupMemberMapper.updateByGroupIdAndUserIdsAndStatus(groupId, userIds, + CommonStatusEnum.ENABLE.getStatus(), + new ImGroupMemberDO().setStatus(CommonStatusEnum.DISABLE.getStatus()) + .setQuitTime(LocalDateTime.now())); + } + + @Override public void removeGroupMembersByGroupId(Long groupId) { - imGroupMemberMapper.updateByGroupIdAndStatus(groupId, CommonStatusEnum.ENABLE.getStatus(), + groupMemberMapper.updateByGroupIdAndStatus(groupId, CommonStatusEnum.ENABLE.getStatus(), new ImGroupMemberDO().setStatus(CommonStatusEnum.DISABLE.getStatus()) .setQuitTime(LocalDateTime.now())); } diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java index 01a033c..5f3b15b 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java +++ b/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; import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; import jakarta.validation.Valid; @@ -92,13 +93,13 @@ public interface ImGroupService { /** * 邀请用户加入群 *

- * 仅群主可执行 + * 群成员即可执行,支持批量。 + * 校验群人数上限,邀请后推送提示消息和群创建事件给被邀请人。 * - * @param userId 当前登录用户编号(群主) + * @param userId 当前登录用户编号 * @param inviteReqVO 邀请信息 - * @return 群成员编号 */ - Long inviteGroupMember(Long userId, @Valid ImGroupMemberInviteReqVO inviteReqVO); + void inviteGroupMember(Long userId, @Valid ImGroupMemberInviteReqVO inviteReqVO); /** * 退群 @@ -113,12 +114,11 @@ public interface ImGroupService { /** * 移除群成员(踢人) *

- * 仅群主可执行,且不能移除自己 + * 仅群主可执行,且不能移除自己,支持批量 * - * @param groupId 群编号 - * @param memberUserId 被移除的用户编号 - * @param userId 当前登录用户编号(群主) + * @param userId 当前登录用户编号(群主) + * @param removeReqVO 移除信息 */ - void removeGroupMember(Long groupId, Long memberUserId, Long userId); + void removeGroupMember(Long userId, @Valid ImGroupMemberRemoveReqVO removeReqVO); } diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java index c7c8bed..b653d27 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java +++ b/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; import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO; import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; @@ -30,8 +31,11 @@ import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; @@ -149,13 +153,39 @@ public class ImGroupServiceImpl implements ImGroupService { // ==================== 群成员的写操作 ==================== @Override - public Long inviteGroupMember(Long userId, ImGroupMemberInviteReqVO inviteReqVO) { - // 1. 校验群存在 + 当前用户是群主 - validateGroupOwner(inviteReqVO.getGroupId(), userId); + public void inviteGroupMember(Long userId, ImGroupMemberInviteReqVO inviteReqVO) { + Long groupId = inviteReqVO.getGroupId(); + // 1.1 校验群存在 + 当前用户是群成员 + validateGroupExists(groupId); + groupMemberService.validateMemberInGroup(groupId, userId); + // 1.2 排除已在群中的用户 + List activeMembers = groupMemberService.getActiveGroupMemberListByGroupId(groupId); + activeMembers.forEach(member -> inviteReqVO.getMemberUserIds().remove(member.getUserId())); + if (CollUtil.isEmpty(inviteReqVO.getMemberUserIds())) { + return; + } + // 1.3 校验群人数上限 + List memberUserIds = inviteReqVO.getMemberUserIds(); + if (activeMembers.size() + memberUserIds.size() > MAX_GROUP_MEMBER) { + throw exception(GROUP_MEMBER_EXCEED, MAX_GROUP_MEMBER); + } - // 2. 添加群成员 - ImGroupMemberDO member = groupMemberService.addGroupMember(inviteReqVO.getGroupId(), inviteReqVO.getUserId()); - return member.getId(); + // 2. 批量添加群成员 + groupMemberService.addGroupMembers(groupId, memberUserIds); + + // 3.1 推送群创建事件给被邀请人(让客户端新增群会话) + webSocketService.sendGroupMessageAsync(new HashSet<>(memberUserIds), + ImGroupMessageDTO.ofGroupCreate(userId, groupId)); + // 3.2 发送邀请提示消息给所有群成员 + AdminUserRespDTO inviter = adminUserApi.getUser(userId); + Map invitedUserMap = adminUserApi.getUserMap(memberUserIds); + String invitedNames = memberUserIds.stream() + .map(id -> invitedUserMap.containsKey(id) ? invitedUserMap.get(id).getNickname() : String.valueOf(id)) + .collect(Collectors.joining(",")); + String tipContent = StrUtil.format(GROUP_INVITE_TIP_MESSAGE, inviter.getNickname(), invitedNames); + List allMembers = groupMemberService.getActiveGroupMemberListByGroupId(groupId); + Set allMemberUserIds = convertSet(allMembers, ImGroupMemberDO::getUserId); + groupMessageService.sendTipGroupMessage(userId, groupId, allMemberUserIds, tipContent); } @Override @@ -180,23 +210,25 @@ public class ImGroupServiceImpl implements ImGroupService { } @Override - public void removeGroupMember(Long groupId, Long memberUserId, Long userId) { + public void removeGroupMember(Long userId, ImGroupMemberRemoveReqVO removeReqVO) { + Long groupId = removeReqVO.getGroupId(); // 1. 校验群存在 + 当前用户是群主 validateGroupOwner(groupId, userId); // 2. 不能移除自己 - if (ObjUtil.equal(memberUserId, userId)) { + if (removeReqVO.getMemberUserIds().contains(userId)) { throw exception(GROUP_CANNOT_REMOVE_SELF); } - // 3.1 移除群成员 - groupMemberService.removeGroupMember(groupId, memberUserId); - // 3.2 清理已读缓存 - groupMessageService.deleteReadMaxMessageId(groupId, memberUserId); + // 3.1 批量移除群成员 + Set memberUserIds = new HashSet<>(removeReqVO.getMemberUserIds()); + groupMemberService.removeGroupMembers(groupId, memberUserIds); + // 3.2 批量清理已读缓存 + groupMessageService.deleteReadMaxMessageIds(groupId, memberUserIds); // 4.1 发送踢出提示消息(TIP_TEXT) - groupMessageService.sendTipGroupMessage(userId, groupId, Set.of(memberUserId), GROUP_REMOVE_TIP_MESSAGE); + groupMessageService.sendTipGroupMessage(userId, groupId, memberUserIds, GROUP_REMOVE_TIP_MESSAGE); // 4.2 推送群删除事件 - webSocketService.sendGroupMessageAsync(Set.of(memberUserId), + webSocketService.sendGroupMessageAsync(memberUserIds, ImGroupMessageDTO.ofGroupDelete(userId, groupId)); } diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java index 7a6c946..bfd279b 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java +++ b/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 import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; +import java.util.Collection; import java.util.List; import java.util.Set; @@ -81,6 +82,16 @@ public interface ImGroupMessageService { void deleteReadMaxMessageId(Long groupId, Long userId); /** + * 批量清理用户在某群的已读位置缓存 + *

+ * 用于批量踢出场景 + * + * @param groupId 群编号 + * @param userIds 用户编号集合 + */ + void deleteReadMaxMessageIds(Long groupId, Collection userIds); + + /** * 清理某群所有用户的已读位置缓存 *

* 用于群解散场景 diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java index b254efd..0a02bc0 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java +++ b/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 { } @Override + public void deleteReadMaxMessageIds(Long groupId, Collection userIds) { + groupMessageReadRedisDAO.deleteReadMaxMessageIds(groupId, userIds); + } + + @Override public void deleteReadMaxMessageIdMap(Long groupId) { groupMessageReadRedisDAO.deleteReadMaxMessageIdMap(groupId); }