From 241064e7d07424892b33c2d0d840ec894123cf23 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 6 May 2026 23:57:03 +0800 Subject: [PATCH] ✨ feat(im): 初始化群申请 v0.3:第四把 review(优化界面,进一步对齐微信界面) --- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupRequestController.java | 23 +++++++++-------------- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupRequestMapper.java | 14 ++++++-------- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestService.java | 21 +++++++++++---------- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImpl.java | 17 ++++++++++++----- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java | 12 +++++++----- yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImplTest.java | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 93 insertions(+), 44 deletions(-) diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupRequestController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupRequestController.java index 7106930..d6a0489 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupRequestController.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupRequestController.java @@ -21,8 +21,6 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.validation.Valid; -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import org.springframework.validation.annotation.Validated; @@ -82,18 +80,6 @@ public class ImGroupRequestController { return success(true); } - @GetMapping("/list") - @Operation(summary = "查询「我相关」的加群申请列表(含我主动申请、我被邀请待审);游标分页") - public CommonResult> getGroupRequestList( - @Parameter(description = "当前列表最旧记录的 id;首页不传") - @RequestParam(value = "lastRequestId", required = false) Long lastRequestId, - @Parameter(description = "单次拉取条数", required = true) - @RequestParam("limit") @Min(1) @Max(200) Integer limit) { - List list = groupRequestService.getGroupRequestListByUserId( - getLoginUserId(), lastRequestId, limit); - return success(buildVOList(list)); - } - @GetMapping("/unhandled-list") @Operation(summary = "查询「我管理的所有群」下的未处理加群申请列表(不分页);前端 store 据此派生横幅红点 + Drawer 列表") public CommonResult> getUnhandledRequestList() { @@ -101,6 +87,15 @@ public class ImGroupRequestController { return success(buildVOList(list)); } + @GetMapping("/list-by-group") + @Operation(summary = "查询指定群下的全部加群申请(含已处理);仅群主 / 管理员可查") + @Parameter(name = "groupId", description = "群编号", required = true, example = "1024") + public CommonResult> getGroupRequestListByGroupId( + @RequestParam("groupId") @NotNull(message = "群编号不能为空") Long groupId) { + List list = groupRequestService.getGroupRequestListByGroupId(getLoginUserId(), groupId); + return success(buildVOList(list)); + } + @GetMapping("/get") @Operation(summary = "按 id 单查申请记录(带越权过滤;WebSocket 通知到达后用)") @Parameter(name = "id", description = "申请记录编号", required = true) diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupRequestMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupRequestMapper.java index edcd4bc..bd10db0 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupRequestMapper.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupRequestMapper.java @@ -25,14 +25,6 @@ public interface ImGroupRequestMapper extends BaseMapperX { .eq(ImGroupRequestDO::getUserId, userId)); } - default List selectListByUserId(Long userId, Long lastRequestId, int limit) { - return selectList(new LambdaQueryWrapperX() - .eq(ImGroupRequestDO::getUserId, userId) - .ltIfPresent(ImGroupRequestDO::getId, lastRequestId) - .orderByDesc(ImGroupRequestDO::getId) - .last("LIMIT " + limit)); - } - default List selectListByGroupIdsAndHandleResult(Collection groupIds, Integer handleResult) { return selectList(new LambdaQueryWrapperX() .in(ImGroupRequestDO::getGroupId, groupIds) @@ -40,6 +32,12 @@ public interface ImGroupRequestMapper extends BaseMapperX { .orderByDesc(ImGroupRequestDO::getId)); } + default List selectListByGroupId(Long groupId) { + return selectList(new LambdaQueryWrapperX() + .eq(ImGroupRequestDO::getGroupId, groupId) + .orderByDesc(ImGroupRequestDO::getId)); + } + default int updateByIdAndHandleResult(Long id, Integer expectedHandleResult, ImGroupRequestDO updateObj) { return update(updateObj, new LambdaUpdateWrapper() .eq(ImGroupRequestDO::getId, id).eq(ImGroupRequestDO::getHandleResult, expectedHandleResult)); diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestService.java index 58b757c..43de7be 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestService.java @@ -54,16 +54,6 @@ public interface ImGroupRequestService { void createInviteRequestList(Long groupId, Long inviterUserId, Collection invitedUserIds); /** - * 拉取「我相关」加群申请列表(含我主动申请、我被邀请待审);游标分页 - * - * @param userId 用户编号 - * @param lastRequestId 当前列表最旧记录的 id;首页传 null - * @param limit 单次拉取条数 - * @return 申请记录列表,按 id 倒序 - */ - List getGroupRequestListByUserId(Long userId, Long lastRequestId, Integer limit); - - /** * 拉取「我管理的所有群」下的未处理申请列表 *

* 前端 store 据此派生:每个群的未处理总数(用于群顶部横幅红点)+ 列表内容(用于 Drawer) @@ -74,6 +64,17 @@ public interface ImGroupRequestService { List getUnhandledRequestListByOwnerOrAdmin(Long userId); /** + * 拉取指定群下的全部加群申请(含已处理);仅群主 / 管理员可查 + *

+ * 用于群「进群申请」子页:最新一条卡片化突出 + 历史申请按 id 倒序 + * + * @param userId 当前用户编号;用于校验 owner / admin 身份 + * @param groupId 群编号 + * @return 申请记录列表,按 id 倒序 + */ + List getGroupRequestListByGroupId(Long userId, Long groupId); + + /** * 按 id 单查申请记录;通用读接口,越权过滤交由调用方 * * @param id 申请记录编号 diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImpl.java index 09a2fd4..7136d13 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImpl.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImpl.java @@ -246,11 +246,6 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { } @Override - public List getGroupRequestListByUserId(Long userId, Long lastRequestId, Integer limit) { - return groupRequestMapper.selectListByUserId(userId, lastRequestId, limit); - } - - @Override public List getUnhandledRequestListByOwnerOrAdmin(Long userId) { // 1. 找出当前用户作为 OWNER / ADMIN 的所有群 List myMembers = groupMemberService.getActiveGroupMemberListByUserId(userId); @@ -266,6 +261,18 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { } @Override + public List getGroupRequestListByGroupId(Long userId, Long groupId) { + // 1. 校验群存在 + 当前用户是群主 / 管理员 + groupService.validateGroupExists(groupId); + ImGroupMemberDO operator = groupMemberService.validateMemberInGroup(groupId, userId); + if (!ImGroupMemberRoleEnum.isOwnerOrAdmin(operator.getRole())) { + throw exception(GROUP_REQUEST_NOT_TO_ME); + } + // 2. 拉取该群下全部申请(含已处理);按 id 倒序,前端首条卡片化展示 + return groupRequestMapper.selectListByGroupId(groupId); + } + + @Override public ImGroupRequestDO getGroupRequest(Long id) { return groupRequestMapper.selectById(id); } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java index deb70b1..8bddb6c 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java @@ -191,9 +191,9 @@ public class ImGroupServiceImpl implements ImGroupService { @Override public void inviteGroupMember(Long userId, ImGroupMemberInviteReqVO inviteReqVO) { Long groupId = inviteReqVO.getGroupId(); - // 1.1 校验群存在 + 当前用户是群成员 + // 1.1 校验群存在 + 当前用户是群成员;同时拿到 role 供下面审批分支判断 ImGroupDO group = validateGroupExists(groupId); - groupMemberService.validateMemberInGroup(groupId, userId); + ImGroupMemberDO operator = groupMemberService.validateMemberInGroup(groupId, userId); // 1.2 排除已在群中的用户 List activeMembers = groupMemberService.getActiveGroupMemberListByGroupId(groupId); activeMembers.forEach(member -> inviteReqVO.getMemberUserIds().remove(member.getUserId())); @@ -213,13 +213,15 @@ public class ImGroupServiceImpl implements ImGroupService { throw exception(GROUP_MEMBER_EXCEED, MAX_GROUP_MEMBER); } - // 2. 情况一:群开启了审批,邀请也走审批,落 group_request 等群主 / 管理员处理 - if (Boolean.TRUE.equals(group.getJoinApproval())) { + // 2. 情况一:群开启审批 + 邀请人是普通成员,落 group_request 等群主 / 管理员处理 + // 群主 / 管理员邀请视同已审批通过,绕过审批直接拉人进群(对齐 OpenIM / 微信) + if (Boolean.TRUE.equals(group.getJoinApproval()) + && !ImGroupMemberRoleEnum.isOwnerOrAdmin(operator.getRole())) { groupRequestService.createInviteRequestList(groupId, userId, memberUserIds); return; } - // 3. 情况二:未开审批,直进;批量添加群成员,写 addSource=INVITE / inviterUserId=操作人 留痕 + // 3. 情况二:未开审批 / 群主 / 管理员邀请,直进;批量添加群成员,写 addSource=INVITE / inviterUserId=操作人 留痕 groupMemberService.addGroupMembers(groupId, memberUserIds, ImGroupAddSourceEnum.INVITE.getSource(), userId); diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImplTest.java index 60e9588..258fe80 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImplTest.java +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImplTest.java @@ -301,16 +301,20 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { } @Test - public void testInviteGroupMember_approval_routesToApproval() { + public void testInviteGroupMember_approval_normalRoutesToApproval() { try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) .thenReturn(groupService); - // 准备:joinApproval=true,开启审批;任何邀请都需审批,落 group_request + // 准备:joinApproval=true,开启审批;普通成员邀请走审批,落 group_request ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) .joinApproval(true) .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupMapper.selectById(10L)).thenReturn(group); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.NORMAL.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build()); when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(List.of( ImGroupMemberDO.builder().groupId(10L).userId(99L) .role(ImGroupMemberRoleEnum.OWNER.getRole()) @@ -339,6 +343,48 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { } @Test + public void testInviteGroupMember_approval_ownerBypassesApproval() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + // 准备:joinApproval=true,但邀请人是群主;视同已审批,直进群 + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) + .joinApproval(true) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.OWNER.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build()); + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(List.of( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.OWNER.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build())); + + ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO(); + reqVO.setGroupId(10L); + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L, 3L))); + when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(List.of( + ImFriendDO.builder().userId(1L).friendUserId(2L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImFriendDO.builder().userId(1L).friendUserId(3L) + .status(CommonStatusEnum.ENABLE.getStatus()).build())); + + // 调用 + groupService.inviteGroupMember(1L, reqVO); + + // 断言:绕过审批,直接 addGroupMembers + 推 1509;不落 group_request + verify(groupRequestService, never()).createInviteRequestList(anyLong(), anyLong(), anyCollection()); + verify(groupMemberService).addGroupMembers(eq(10L), anyCollection(), + eq(ImGroupAddSourceEnum.INVITE.getSource()), eq(1L)); + ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); + verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture()); + assertEquals(ImMessageTypeEnum.GROUP_MEMBER_INVITE.getType(), dtoCaptor.getValue().getType()); + } + } + + @Test public void testInviteGroupMember_notFriend() { try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) -- libgit2 0.21.4