From b320e3c19a733d41ee44dfa63fe1f8513b4fbe87 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 6 May 2026 18:52:31 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(im):=20=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96=E7=BE=A4=E7=94=B3=E8=AF=B7=20v0.1=EF=BC=9A=E7=AC=AC?= =?UTF-8?q?=E4=BA=8C=E6=8A=8A=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/group/ImGroupController.java | 27 +--- .../admin/group/ImGroupRequestController.java | 70 ++++---- .../admin/group/vo/ImGroupCreateReqVO.java | 7 +- .../admin/group/vo/ImGroupRespVO.java | 8 +- .../admin/group/vo/ImGroupUpdateReqVO.java | 7 +- .../im/dal/dataobject/group/ImGroupDO.java | 7 +- .../mysql/friend/ImFriendRequestMapper.java | 6 +- .../dal/mysql/group/ImGroupRequestMapper.java | 52 ++---- .../im/enums/group/ImGroupAddSourceEnum.java | 7 +- .../im/enums/group/ImGroupJoinTypeEnum.java | 61 ------- .../friend/ImFriendRequestServiceImpl.java | 26 +-- .../service/group/ImGroupMemberService.java | 8 + .../group/ImGroupMemberServiceImpl.java | 12 ++ .../service/group/ImGroupRequestService.java | 37 ++--- .../group/ImGroupRequestServiceImpl.java | 151 +++++++++--------- .../im/service/group/ImGroupServiceImpl.java | 15 +- .../GroupRequestApprovedNotification.java | 2 +- .../GroupRequestRejectedNotification.java | 2 +- .../group/ImGroupRequestServiceImplTest.java | 23 ++- .../service/group/ImGroupServiceImplTest.java | 58 +------ .../src/test/resources/sql/create_tables.sql | 8 +- 21 files changed, 212 insertions(+), 382 deletions(-) delete mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/group/ImGroupJoinTypeEnum.java diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java index da2ce45cc3..cc822054ae 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java @@ -10,9 +10,7 @@ import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessa import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; -import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; -import cn.iocoder.yudao.module.im.service.group.ImGroupRequestService; import cn.iocoder.yudao.module.im.service.group.ImGroupService; import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; import io.swagger.v3.oas.annotations.Operation; @@ -42,8 +40,6 @@ public class ImGroupController { private ImGroupMemberService groupMemberService; @Resource private ImGroupMessageService groupMessageService; - @Resource - private ImGroupRequestService groupRequestService; // ==================== 群的写操作 ==================== @@ -181,34 +177,23 @@ public class ImGroupController { } /** - * 群列表批量转 VO + 关联回填 pinnedMessages 与 pendingRequestCount + * 群列表批量转 VO + 关联回填 pinnedMessages *

- * pinnedMessages 仅当前用户是该群有效成员时回填;pendingRequestCount 仅当前用户是该群 owner / admin 时回填 + * 仅当登录用户是某群的有效成员时才回填该群的 pinnedMessages,避免非成员 / 已退群用户越权拿到置顶消息内容 */ private List buildGroupRespVOList(List groups, Long loginUserId) { if (CollUtil.isEmpty(groups)) { return Collections.emptyList(); } - // 1.1 当前用户的活跃群成员记录;用于 pinnedMessages 回填的「成员」可见性 + pendingRequestCount 回填的「owner / admin」可见性 - List myMembers = groupMemberService.getActiveGroupMemberListByUserId(loginUserId); - Set activeGroupIds = convertSet(myMembers, ImGroupMemberDO::getGroupId); - Set ownerOrAdminGroupIds = convertSet(myMembers, - ImGroupMemberDO::getGroupId, - member -> ImGroupMemberRoleEnum.isOwnerOrAdmin(member.getRole())); - // 1.2 批量查置顶消息 + // 仅当前用户是有效成员的群才允许回填置顶消息 + Set activeGroupIds = convertSet( + groupMemberService.getActiveGroupMemberListByUserId(loginUserId), ImGroupMemberDO::getGroupId); Set allMessageIds = convertSetByFlatMap(groups, group -> activeGroupIds.contains(group.getId()) ? CollUtil.emptyIfNull(group.getPinnedMessageIds()).stream() : Stream.empty()); Map messageMap = groupMessageService.getGroupMessageMap(allMessageIds); - // 1.3 仅 owner / admin 群批量统计未处理申请数 - // TODO @AI:这块讨论中。。。 - Map pendingCountMap = groupRequestService.getPendingCountMap( - groups.stream().map(ImGroupDO::getId).filter(ownerOrAdminGroupIds::contains).toList()); - // 2. 转换输出 + // 转换输出 return convertList(groups, group -> { ImGroupRespVO vo = BeanUtils.toBean(group, ImGroupRespVO.class); - if (ownerOrAdminGroupIds.contains(group.getId())) { - vo.setPendingRequestCount(pendingCountMap.getOrDefault(group.getId(), 0L)); - } if (!activeGroupIds.contains(group.getId()) || CollUtil.isEmpty(group.getPinnedMessageIds())) { return vo; } 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 36f6de8aff..7106930848 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 @@ -8,7 +8,10 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.im.controller.admin.group.vo.request.ImGroupRequestApplyReqVO; import cn.iocoder.yudao.module.im.controller.admin.group.vo.request.ImGroupRequestRespVO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupRequestDO; +import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; +import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; import cn.iocoder.yudao.module.im.service.group.ImGroupRequestService; import cn.iocoder.yudao.module.im.service.group.ImGroupService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; @@ -47,6 +50,8 @@ public class ImGroupRequestController { private ImGroupRequestService groupRequestService; @Resource private ImGroupService groupService; + @Resource + private ImGroupMemberService groupMemberService; @Resource private AdminUserApi adminUserApi; @@ -79,71 +84,58 @@ public class ImGroupRequestController { @GetMapping("/list") @Operation(summary = "查询「我相关」的加群申请列表(含我主动申请、我被邀请待审);游标分页") - public CommonResult> getMyGroupRequestList( + 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.getMyGroupRequestList(getLoginUserId(), lastRequestId, limit); - return success(buildList(list)); + List list = groupRequestService.getGroupRequestListByUserId( + getLoginUserId(), lastRequestId, limit); + return success(buildVOList(list)); } - // TODO @AI:pending-list; - @GetMapping("/list-pending") - @Operation(summary = "查询指定群的未处理加群申请列表(仅群主或管理员可调);游标分页") - public CommonResult> getPendingGroupRequestList( - @RequestParam("groupId") @NotNull(message = "群编号不能为空") Long groupId, - @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.getPendingGroupRequestList( - getLoginUserId(), groupId, lastRequestId, limit); - return success(buildList(list)); + @GetMapping("/unhandled-list") + @Operation(summary = "查询「我管理的所有群」下的未处理加群申请列表(不分页);前端 store 据此派生横幅红点 + Drawer 列表") + public CommonResult> getUnhandledRequestList() { + List list = groupRequestService.getUnhandledRequestListByOwnerOrAdmin(getLoginUserId()); + return success(buildVOList(list)); } - // TODO @AI:群管理里,也有个地方,可以查询这个群所有的 list;这样上面的 list、list-pending 有点重复。看看怎么组合。可以和我讨论下; - - // TODO @AI:方法上,不用 my;因为这个方法,都是查询我的; @GetMapping("/get") - @Operation(summary = "按 id 单查「我相关」的申请记录(带越权过滤;WebSocket 通知到达后用)") + @Operation(summary = "按 id 单查申请记录(带越权过滤;WebSocket 通知到达后用)") @Parameter(name = "id", description = "申请记录编号", required = true) - public CommonResult getMyGroupRequest(@RequestParam("id") Long id) { + public CommonResult getGroupRequest(@RequestParam("id") Long id) { ImGroupRequestDO request = groupRequestService.getGroupRequest(id); - // 越权过滤:申请人 / 被邀请人 / 邀请人 / 群主 / 管理员之外,当不存在返回 null if (request == null) { return success(null); } + // 越权过滤:申请人 / 邀请人 / 群主 / 管理员之外,当不存在返回 null Long currentUserId = getLoginUserId(); boolean canSee = ObjUtil.equal(request.getUserId(), currentUserId) - || ObjUtil.equal(request.getInviterUserId(), currentUserId); - if (!canSee) { - // 群主 / 管理员可见;通过 list-pending 校验路径 - // TODO @AI:不要 try catch; - try { - // TODO @AI:这个方法的实现,不要里面抛出异常;应该调用查询是否为管理员;而不应该这么搞哈。这样,是不是可以上面 || 后面在加一个条件? - groupRequestService.getPendingGroupRequestList(currentUserId, request.getGroupId(), null, 1); - canSee = true; - } catch (Exception ignored) { - // 非 owner / admin - } - } + || ObjUtil.equal(request.getInviterUserId(), currentUserId) + || isGroupOwnerOrAdmin(request.getGroupId(), currentUserId); if (!canSee) { return success(null); } - return success(CollUtil.getFirst(buildList(Collections.singletonList(request)))); + + // 转换并返回 + return success(CollUtil.getFirst(buildVOList(Collections.singletonList(request)))); + } + + /** 当前用户是否该群的群主 / 管理员 */ + private boolean isGroupOwnerOrAdmin(Long groupId, Long userId) { + ImGroupMemberDO member = groupMemberService.getGroupMember(groupId, userId); + return member != null && ImGroupMemberRoleEnum.isOwnerOrAdmin(member.getRole()); } /** 申请记录列表批量转 VO + 关联回填用户 / 群信息 */ - private List buildList(List list) { + private List buildVOList(List list) { if (CollUtil.isEmpty(list)) { return Collections.emptyList(); } - // 1. 聚合 user / inviter 用户信息 - // TODO @AI:.filter(Objects::nonNull) 内部已经过滤了; + // 1. 聚合 user / inviter 用户信息;convertSetByFlatMap 内部已过滤 null Set userIds = convertSetByFlatMap(list, - request -> Stream.of(request.getUserId(), request.getInviterUserId()) - .filter(Objects::nonNull)); + request -> Stream.of(request.getUserId(), request.getInviterUserId())); Map userMap = adminUserApi.getUserMap(userIds); // 2. 聚合群信息(封禁 / 解散群也要回填,便于前端展示历史) Set groupIds = convertSet(list, ImGroupRequestDO::getGroupId); diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupCreateReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupCreateReqVO.java index 62455b1a93..0ddaf79438 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupCreateReqVO.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupCreateReqVO.java @@ -1,7 +1,5 @@ package cn.iocoder.yudao.module.im.controller.admin.group.vo; -import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.im.enums.group.ImGroupJoinTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Data; @@ -19,8 +17,7 @@ public class ImGroupCreateReqVO { @Schema(description = "初始成员用户编号列表(建群同时邀请的好友,不含创建者自己)", example = "[1024, 2048]") private List memberUserIds; - @Schema(description = "加群方式;不传默认 0 自由进群", example = "0") - @InEnum(ImGroupJoinTypeEnum.class) - private Integer joinType; // 参见 ImGroupJoinTypeEnum 枚举类 + @Schema(description = "进群是否需群主 / 管理员审批;不传默认 false 自由进群", example = "false") + private Boolean joinApproval; } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupRespVO.java index 5a2a09d4a9..2dac1467b2 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupRespVO.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupRespVO.java @@ -32,12 +32,8 @@ public class ImGroupRespVO { @Schema(description = "是否全群禁言") private Boolean mutedAll; - @Schema(description = "加群方式", example = "0") - private Integer joinType; // 参见 ImGroupJoinTypeEnum 枚举类 - - // TODO @AI:是不是不回填;前端自己查询 + 推送维护?类似微信,会展示有人加群,需要审批之类的。(晚点我可以给截图); - @Schema(description = "未处理加群申请数;仅当登录用户是该群群主 / 管理员时回填,否则为 null", example = "3") - private Long pendingRequestCount; + @Schema(description = "进群是否需群主 / 管理员审批", example = "false") + private Boolean joinApproval; @Schema(description = "封禁时间") private LocalDateTime bannedTime; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupUpdateReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupUpdateReqVO.java index b2cf14eb5c..128cdd9119 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupUpdateReqVO.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupUpdateReqVO.java @@ -1,7 +1,5 @@ package cn.iocoder.yudao.module.im.controller.admin.group.vo; -import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.im.enums.group.ImGroupJoinTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -23,8 +21,7 @@ public class ImGroupUpdateReqVO { @Schema(description = "群公告") private String notice; - @Schema(description = "加群方式", example = "1") - @InEnum(ImGroupJoinTypeEnum.class) - private Integer joinType; // 参见 ImGroupJoinTypeEnum 枚举类 + @Schema(description = "进群是否需群主 / 管理员审批", example = "true") + private Boolean joinApproval; } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupDO.java index 6e695c1563..f47f80d88a 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupDO.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupDO.java @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.im.dal.dataobject.group; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; -import cn.iocoder.yudao.module.im.enums.group.ImGroupJoinTypeEnum; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; @@ -52,11 +51,11 @@ public class ImGroupDO extends BaseDO { */ private String notice; /** - * 加群方式 + * 进群是否需群主 / 管理员审批 *

- * 枚举 {@link ImGroupJoinTypeEnum} + * false 自由进群(默认);true 需审批,所有「申请」「邀请」路径都需群主 / 管理员同意 */ - private Integer joinType; + private Boolean joinApproval; /** * 是否封禁 */ diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapper.java index bdf61aae97..b03c21d1e8 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapper.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapper.java @@ -18,12 +18,10 @@ import java.util.List; @Mapper public interface ImFriendRequestMapper extends BaseMapperX { - default ImFriendRequestDO selectLatestByFromUserIdAndToUserId(Long fromUserId, Long toUserId) { + default ImFriendRequestDO selectByFromUserIdAndToUserId(Long fromUserId, Long toUserId) { return selectOne(new LambdaQueryWrapperX() .eq(ImFriendRequestDO::getFromUserId, fromUserId) - .eq(ImFriendRequestDO::getToUserId, toUserId) - .orderByDesc(ImFriendRequestDO::getId) - .last("LIMIT 1")); + .eq(ImFriendRequestDO::getToUserId, toUserId)); } /** 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 67e1c2f2d7..edcd4bcd43 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 @@ -5,10 +5,10 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupRequestManagerPageReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupRequestDO; -import cn.iocoder.yudao.module.im.enums.group.ImGroupRequestHandleResultEnum; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.ibatis.annotations.Mapper; +import java.util.Collection; import java.util.List; /** @@ -19,31 +19,13 @@ import java.util.List; @Mapper public interface ImGroupRequestMapper extends BaseMapperX { - /** - * 取最新一条未处理申请;inviterUserId 为 null 表示主动申请 - *

- * 主动申请、不同邀请人发起的邀请申请彼此互不覆盖,复用键含 inviterUserId - */ - // TODO @AI:一般合理的,是不是可以覆盖???? - default ImGroupRequestDO selectLatestPendingByGroupIdAndUserIdAndInviter( - Long groupId, Long userId, Long inviterUserId) { - LambdaQueryWrapperX wrapper = new LambdaQueryWrapperX<>(); - wrapper.eq(ImGroupRequestDO::getGroupId, groupId) - .eq(ImGroupRequestDO::getUserId, userId) - .eq(ImGroupRequestDO::getHandleResult, ImGroupRequestHandleResultEnum.UNHANDLED.getResult()); - if (inviterUserId == null) { - wrapper.isNull(ImGroupRequestDO::getInviterUserId); - } else { - wrapper.eq(ImGroupRequestDO::getInviterUserId, inviterUserId); - } - return selectOne(wrapper.orderByDesc(ImGroupRequestDO::getId).last("LIMIT 1")); + default ImGroupRequestDO selectByGroupIdAndUserId(Long groupId, Long userId) { + return selectOne(new LambdaQueryWrapperX() + .eq(ImGroupRequestDO::getGroupId, groupId) + .eq(ImGroupRequestDO::getUserId, userId)); } - /** - * 拉取「我相关」的加群申请列表(含我主动申请、我被邀请待审);游标分页 - */ - // TODO @AI:selectListByMy;保持风格一致; - default List selectMyList(Long userId, Long lastRequestId, int limit) { + default List selectListByUserId(Long userId, Long lastRequestId, int limit) { return selectList(new LambdaQueryWrapperX() .eq(ImGroupRequestDO::getUserId, userId) .ltIfPresent(ImGroupRequestDO::getId, lastRequestId) @@ -51,26 +33,16 @@ public interface ImGroupRequestMapper extends BaseMapperX { .last("LIMIT " + limit)); } - // TODO @AI:类似下面的注释,减少 mapper 的业务型; - default List selectPendingListByGroupId(Long groupId, Long lastRequestId, int limit) { + default List selectListByGroupIdsAndHandleResult(Collection groupIds, Integer handleResult) { return selectList(new LambdaQueryWrapperX() - .eq(ImGroupRequestDO::getGroupId, groupId) - .eq(ImGroupRequestDO::getHandleResult, ImGroupRequestHandleResultEnum.UNHANDLED.getResult()) - .ltIfPresent(ImGroupRequestDO::getId, lastRequestId) - .orderByDesc(ImGroupRequestDO::getId) - .last("LIMIT " + limit)); + .in(ImGroupRequestDO::getGroupId, groupIds) + .eq(ImGroupRequestDO::getHandleResult, handleResult) + .orderByDesc(ImGroupRequestDO::getId)); } - // TODO @AI:应该 bygroupidandhandleResult,减少不必要的条件 - default Long selectPendingCountByGroupId(Long groupId) { - return selectCount(new LambdaQueryWrapperX() - .eq(ImGroupRequestDO::getGroupId, groupId) - .eq(ImGroupRequestDO::getHandleResult, ImGroupRequestHandleResultEnum.UNHANDLED.getResult())); - } - - default int updateByIdAndHandleResult(Long id, Integer handleResult, ImGroupRequestDO updateObj) { + default int updateByIdAndHandleResult(Long id, Integer expectedHandleResult, ImGroupRequestDO updateObj) { return update(updateObj, new LambdaUpdateWrapper() - .eq(ImGroupRequestDO::getId, id).eq(ImGroupRequestDO::getHandleResult, handleResult)); + .eq(ImGroupRequestDO::getId, id).eq(ImGroupRequestDO::getHandleResult, expectedHandleResult)); } default PageResult selectPage(ImGroupRequestManagerPageReqVO reqVO) { diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/group/ImGroupAddSourceEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/group/ImGroupAddSourceEnum.java index d393448320..d12e098c23 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/group/ImGroupAddSourceEnum.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/group/ImGroupAddSourceEnum.java @@ -17,11 +17,10 @@ import java.util.Arrays; @Getter public enum ImGroupAddSourceEnum implements ArrayValuable { - // TODO @AI:没实现的方式,记得在后面加个 TODO @芋艿:未实现,原因。。。。 - SEARCH(1, "搜索"), + SEARCH(1, "搜索"), // TODO @芋艿:SEARCH 暂未实现,原因 - 搜群入口尚未开发 INVITE(2, "邀请"), - QR_CODE(3, "扫码"), - SHARE_LINK(4, "分享链接"); + QR_CODE(3, "扫码"), // TODO @芋艿:QR_CODE 暂未实现,原因 - 群二维码扫码进群入口尚未开发 + SHARE_LINK(4, "分享链接"); // TODO @芋艿:SHARE_LINK 暂未实现,原因 - 群分享链接进群入口尚未开发 public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImGroupAddSourceEnum::getSource).toArray(Integer[]::new); diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/group/ImGroupJoinTypeEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/group/ImGroupJoinTypeEnum.java deleted file mode 100644 index 6f908c43cc..0000000000 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/group/ImGroupJoinTypeEnum.java +++ /dev/null @@ -1,61 +0,0 @@ -package cn.iocoder.yudao.module.im.enums.group; - -import cn.iocoder.yudao.framework.common.core.ArrayValuable; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -import java.util.Arrays; -import java.util.Objects; - -// TODO @AI:这里是不是对齐微信;申请只有需要审批,和不需要审批。(不然感觉有点复杂、难理解?!) -/** - * IM 群加群方式枚举 - *

- * 控制「外部用户主动申请」和「群成员邀请」两条入群路径是否需要群主 / 管理员审批 - *

- * - * @author 芋道源码 - */ -@RequiredArgsConstructor -@Getter -public enum ImGroupJoinTypeEnum implements ArrayValuable { - - FREE(0, "自由进群"), - APPLY(1, "申请需审批"), - APPLY_AND_NORMAL_INVITE(2, "申请、及普通成员邀请均需审批"); - - public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImGroupJoinTypeEnum::getType).toArray(Integer[]::new); - - /** - * 类型 - */ - private final Integer type; - /** - * 名字 - */ - private final String name; - - @Override - public Integer[] array() { - return ARRAYS; - } - - /** - * 是否「自由进群」;自由进群路径无需创建申请记录 - */ - public static boolean isFree(Integer type) { - return Objects.equals(FREE.type, type); - } - - /** - * 普通成员邀请是否需要审批 - */ - public static boolean isInviteNeedApproval(Integer type) { - return Objects.equals(APPLY_AND_NORMAL_INVITE.type, type); - } - -} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java index 838333fee7..537d9b82ae 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java @@ -88,19 +88,19 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService { return null; } - // 2. 落库:复用最新一条未处理记录走条件 update;否则新建 - ImFriendRequestDO request = friendRequestMapper.selectLatestByFromUserIdAndToUserId(fromUserId, toUserId); - if (request != null && ImFriendRequestHandleResultEnum.isUnhandled(request.getHandleResult())) { - ImFriendRequestDO updateObj = new ImFriendRequestDO().setApplyContent(reqVO.getApplyContent()) - .setDisplayName(reqVO.getDisplayName()).setAddSource(reqVO.getAddSource()); - int affected = friendRequestMapper.updateByIdAndHandleResult(request.getId(), - ImFriendRequestHandleResultEnum.UNHANDLED.getResult(), updateObj); - if (affected == 0) { - // 并发场景下另一方刚 agree/refuse 推进了状态,条件 update affected=0 抛已处理错让前端重试 - throw exception(FRIEND_REQUEST_HANDLED); - } - // 同步更新字段,下面推送 payload 用 - BeanUtil.copyProperties(reqVO, request, CopyOptions.create().setIgnoreNullValue(true)); + // 2. 落库:upsert 语义;同一对 (from, to) 唯一,已有记录覆盖申请理由 / 备注 / 来源 + 重置为未处理 + 清空旧处理痕迹 + ImFriendRequestDO request = friendRequestMapper.selectByFromUserIdAndToUserId(fromUserId, toUserId); + if (request != null) { + // TODO @AI:相同的放在一行里;然后 null 直接这样更新是清理不掉的;看看是不是在 basemapperx 里,增加一个 updateXXXX;可以根据传递的 DO,深度更新的方法?应该匹配这个场景的对哇? + ImFriendRequestDO updateObj = new ImFriendRequestDO() + .setApplyContent(reqVO.getApplyContent()) + .setDisplayName(reqVO.getDisplayName()) + .setAddSource(reqVO.getAddSource()) + .setHandleResult(ImFriendRequestHandleResultEnum.UNHANDLED.getResult()) + .setHandleContent(null).setHandleTime(null); + updateObj.setId(request.getId()); + friendRequestMapper.updateById(updateObj); + BeanUtil.copyProperties(updateObj, request, CopyOptions.create().setIgnoreNullValue(true)); } else { request = BeanUtils.toBean(reqVO, ImFriendRequestDO.class) .setFromUserId(fromUserId).setToUserId(toUserId) diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java index 3e794f01dc..dfddbe038b 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java @@ -58,6 +58,14 @@ public interface ImGroupMemberService { */ List getActiveGroupMemberListByGroupId(Long groupId); + /** + * 获取群里活跃的群主 + 管理员;用于审批通知定向推送 + * + * @param groupId 群编号 + * @return 群主 / 管理员的成员记录列表(按入群时间倒序的天然成员表顺序) + */ + List getGroupMemberListByOwnerAndAdmin(Long groupId); + /** * 根据群编号查询有效成员的 userId 列表(仅 ENABLE 状态) *

diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java index bc5c99496b..8b8f925a75 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java @@ -73,6 +73,18 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { return groupMemberMapper.selectListByGroupIdAndStatus(groupId, CommonStatusEnum.ENABLE.getStatus()); } + @Override + public List getGroupMemberListByOwnerAndAdmin(Long groupId) { + // TODO @AI:把条件往下传;这样减少加载数据量! + List members = getActiveGroupMemberListByGroupId(groupId); + if (CollUtil.isEmpty(members)) { + return Collections.emptyList(); + } + return members.stream() + .filter(member -> ImGroupMemberRoleEnum.isOwnerOrAdmin(member.getRole())) + .toList(); + } + /** * 只缓存 userId 列表而非整个 {@link ImGroupMemberDO},理由: *

    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 0e6766ceea..58b757ce96 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 @@ -8,7 +8,6 @@ import jakarta.validation.Valid; import java.util.Collection; import java.util.List; -import java.util.Map; /** * IM 加群申请 Service 接口 @@ -20,8 +19,7 @@ public interface ImGroupRequestService { /** * 用户主动申请加群 *

    - * 群 joinType=FREE 时直接入群 + 1510 全员广播 + 1505 推送给申请人; - * 否则落 im_group_request 待审批 + 1503 定向推送给群主 / 全部管理员 + * 群未开启审批时直接入群 + 1510 全员广播;开启审批则 upsert 一条 inviterUserId=null 的待审批记录 + 1503 推送 * * @param userId 申请人用户编号 * @param reqVO 申请请求 @@ -30,9 +28,7 @@ public interface ImGroupRequestService { ImGroupRequestDO applyJoinGroup(Long userId, @Valid ImGroupRequestApplyReqVO reqVO); /** - * 同意加群申请(群主或管理员) - *

    - * 处理前校验入群人数上限;通过后写 group_member(带 addSource / inviterUserId) + 推送通知 + * 同意加群申请(群主或管理员);处理前校验入群人数上限 * * @param userId 操作人用户编号 * @param requestId 申请记录编号 @@ -49,15 +45,13 @@ public interface ImGroupRequestService { void refuseGroupRequest(Long userId, Long requestId, String handleContent); /** - * 邀请创建审批申请(用于 inviteGroupMember 在 joinType=APPLY_AND_NORMAL_INVITE 且邀请人是普通成员时调用) - *

    - * 每个被邀请人创建一条 inviter_user_id=操作人 的待审批记录;通知群主 / 全部管理员 + * 邀请创建审批申请;inviteGroupMember 在群开启审批时调用,每个被邀请人 upsert 一条 inviterUserId=操作人 的记录 * * @param groupId 群编号 * @param inviterUserId 邀请人用户编号 * @param invitedUserIds 被邀请人用户编号集合 */ - void createInviteRequests(Long groupId, Long inviterUserId, Collection invitedUserIds); + void createInviteRequestList(Long groupId, Long inviterUserId, Collection invitedUserIds); /** * 拉取「我相关」加群申请列表(含我主动申请、我被邀请待审);游标分页 @@ -67,18 +61,17 @@ public interface ImGroupRequestService { * @param limit 单次拉取条数 * @return 申请记录列表,按 id 倒序 */ - List getMyGroupRequestList(Long userId, Long lastRequestId, Integer limit); + List getGroupRequestListByUserId(Long userId, Long lastRequestId, Integer limit); /** - * 拉取指定群下未处理申请;仅群主 / 管理员可调 + * 拉取「我管理的所有群」下的未处理申请列表 + *

    + * 前端 store 据此派生:每个群的未处理总数(用于群顶部横幅红点)+ 列表内容(用于 Drawer) * - * @param userId 操作人用户编号 - * @param groupId 群编号 - * @param lastRequestId 游标 id - * @param limit 单次拉取条数 - * @return 未处理申请列表 + * @param userId 当前用户编号;后端按 ImGroupMember.role 过滤出我作为 OWNER / ADMIN 的群 + * @return 未处理申请列表(不分页) */ - List getPendingGroupRequestList(Long userId, Long groupId, Long lastRequestId, Integer limit); + List getUnhandledRequestListByOwnerOrAdmin(Long userId); /** * 按 id 单查申请记录;通用读接口,越权过滤交由调用方 @@ -88,14 +81,6 @@ public interface ImGroupRequestService { */ ImGroupRequestDO getGroupRequest(Long id); - /** - * 批量统计指定群的未处理申请数量;用于 ImGroupRespVO.pendingRequestCount 回填 - * - * @param groupIds 群编号集合 - * @return 群编号 → 未处理申请数 Map - */ - Map getPendingCountMap(Collection groupIds); - // ==================== 管理后台 ==================== /** 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 35ed1f99f6..4bcf35d63f 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 @@ -12,7 +12,6 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupRequestDO; import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupRequestMapper; -import cn.iocoder.yudao.module.im.enums.group.ImGroupJoinTypeEnum; import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; import cn.iocoder.yudao.module.im.enums.group.ImGroupRequestHandleResultEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; @@ -37,9 +36,9 @@ import java.time.LocalDateTime; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; -// TODO @AI:payload 能不能改成 notification 更像通知一点。 /** * IM 加群申请 Service 实现类 * @@ -81,30 +80,32 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { throw exception(GROUP_REQUEST_ALREADY_MEMBER); } - // 2. FREE 模式:直接入群 + 推 1510 全员广播;不落申请记录 - if (ImGroupJoinTypeEnum.isFree(group.getJoinType())) { - // TODO @AI:这里还是写下注释;每一行的; + // 2. 情况一:群未开启审批,直接入群;写群成员留痕 + 推 1510 全员广播;不落申请记录 + if (!Boolean.TRUE.equals(group.getJoinApproval())) { + // 入群前校验人数上限 groupService.validateMemberCountLimit(groupId, 1); + // 写群成员;addSource 来自前端入口(搜索 / 二维码 / 分享链接),inviterUserId=null(主动申请) groupMemberService.addGroupMember(groupId, userId, ImGroupMemberRoleEnum.NORMAL.getRole(), reqVO.getAddSource(), null); + // 推 1510 给全员;payload 含进群者 + 来源,前端按 entrantUserId 局部插入新成员 groupMessageService.sendGroupMessage(userId, ImGroupMessageSendDTO.ofGroupMemberEnter(groupId, userId, reqVO.getAddSource())); return null; } - // 3. 非 FREE 模式:复用未处理记录或新建 - ImGroupRequestDO request = groupRequestMapper.selectLatestPendingByGroupIdAndUserIdAndInviter( - groupId, userId, null); + // 3. 情况二:群开启了审批;upsert 一条「主动申请」记录,inviterUserId=null + // 已有记录(无论之前是未处理 / 已同意 / 已拒绝),覆盖申请理由 + 来源 + 重置为未处理 + 清空旧处理痕迹 + ImGroupRequestDO request = groupRequestMapper.selectByGroupIdAndUserId(groupId, userId); if (request != null) { + // TODO @AI:相同的放在一行里;然后 null 直接这样更新是清理不掉的;看看是不是在 basemapperx 里,增加一个 updateXXXX;可以根据传递的 DO,深度更新的方法?应该匹配这个场景的对哇? ImGroupRequestDO updateObj = new ImGroupRequestDO() - .setApplyContent(reqVO.getApplyContent()).setAddSource(reqVO.getAddSource()); - int affected = groupRequestMapper.updateByIdAndHandleResult(request.getId(), - ImGroupRequestHandleResultEnum.UNHANDLED.getResult(), updateObj); - if (affected == 0) { - // 并发场景另一管理端刚处理过,提示重试 - throw exception(GROUP_REQUEST_HANDLED); - } - BeanUtil.copyProperties(reqVO, request, CopyOptions.create().setIgnoreNullValue(true)); + .setInviterUserId(null) + .setApplyContent(reqVO.getApplyContent()).setAddSource(reqVO.getAddSource()) + .setHandleResult(ImGroupRequestHandleResultEnum.UNHANDLED.getResult()) + .setHandleUserId(null).setHandleContent(null).setHandleTime(null); + updateObj.setId(request.getId()); + groupRequestMapper.updateById(updateObj); + BeanUtil.copyProperties(updateObj, request, CopyOptions.create().setIgnoreNullValue(true)); } else { request = BeanUtils.toBean(reqVO, ImGroupRequestDO.class) .setUserId(userId).setInviterUserId(null) @@ -126,7 +127,7 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { @Override @Transactional(rollbackFor = Exception.class) public void agreeGroupRequest(Long userId, Long requestId) { - // 1. 校验申请存在 + 未处理 + 操作人是 owner/admin + // 1. 校验申请存在 + 未处理 + 操作人是 owner / admin ImGroupRequestDO request = validateRequestForHandle(userId, requestId); // 2. 入群前校验人数上限;群已满抛错让操作人选择拒绝 groupService.validateMemberCountLimit(request.getGroupId(), 1); @@ -144,17 +145,18 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { request.setHandleResult(ImGroupRequestHandleResultEnum.AGREED.getResult()) .setHandleUserId(userId).setHandleTime(now); - // 4. 写群成员;带 addSource / inviterUserId + // 4. 写群成员;addSource / inviterUserId 沿用申请记录上的来源信息 groupMemberService.addGroupMember(request.getGroupId(), request.getUserId(), ImGroupMemberRoleEnum.NORMAL.getRole(), request.getAddSource(), request.getInviterUserId()); // 5.1 1505 私聊推送给申请人 + 群主 + 全部管理员(每端单推) - GroupRequestApprovedNotification payload = new GroupRequestApprovedNotification(); - // TODO @AI:能不能改成链式调用; - payload.setRequestId(request.getId()); - payload.setGroupId(request.getGroupId()); - payload.setUserId(request.getUserId()); - payload.setOperatorUserId(userId); + // TODO @AI:同类属性,放在一行,减少行占用;其它位置也是! + GroupRequestApprovedNotification payload = (GroupRequestApprovedNotification) + new GroupRequestApprovedNotification() + .setRequestId(request.getId()) + .setGroupId(request.getGroupId()) + .setUserId(request.getUserId()) + .setOperatorUserId(userId); broadcastToOwnerAdminsAndApplicant(request.getGroupId(), request.getUserId(), payload, ImMessageTypeEnum.GROUP_REQUEST_APPROVED.getType(), userId); // 5.2 群事件:主动申请 → 1510 自由进群;被邀请 → 1509 成员加入 @@ -187,32 +189,40 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { } // 3. 1506 私聊推送给申请人 + 群主 + 全部管理员 - GroupRequestRejectedNotification payload = new GroupRequestRejectedNotification(); - payload.setRequestId(request.getId()); - payload.setGroupId(request.getGroupId()); - payload.setUserId(request.getUserId()); - payload.setHandleContent(handleContent); - payload.setOperatorUserId(userId); + GroupRequestRejectedNotification payload = (GroupRequestRejectedNotification) + new GroupRequestRejectedNotification() + .setRequestId(request.getId()) + .setGroupId(request.getGroupId()) + .setUserId(request.getUserId()) + .setHandleContent(handleContent) + .setOperatorUserId(userId); broadcastToOwnerAdminsAndApplicant(request.getGroupId(), request.getUserId(), payload, ImMessageTypeEnum.GROUP_REQUEST_REJECTED.getType(), userId); } - // TODO @AI:createInviteRequestList;习惯性命名; @Override @Transactional(rollbackFor = Exception.class) - public void createInviteRequests(Long groupId, Long inviterUserId, Collection invitedUserIds) { + public void createInviteRequestList(Long groupId, Long inviterUserId, Collection invitedUserIds) { if (CollUtil.isEmpty(invitedUserIds)) { return; } ImGroupDO group = groupService.validateGroupExists(groupId); - // TODO @AI:= 两侧,最好有空格;看看有没别的地方,也要改的; - // 1. 复用或新建 inviter_user_id=inviterUserId 的待审批记录 + // 1. 每个被邀请人 upsert 一条 inviter_user_id=邀请人 的记录 + // 已有记录(同一对 group_id, user_id 唯一):覆盖 inviter_user_id + 重置 handle_result + 清空处理痕迹 List requests = new ArrayList<>(invitedUserIds.size()); for (Long invitedUserId : invitedUserIds) { - ImGroupRequestDO existing = groupRequestMapper.selectLatestPendingByGroupIdAndUserIdAndInviter( - groupId, invitedUserId, inviterUserId); + ImGroupRequestDO existing = groupRequestMapper.selectByGroupIdAndUserId(groupId, invitedUserId); if (existing != null) { + ImGroupRequestDO updateObj = new ImGroupRequestDO() + .setInviterUserId(inviterUserId) + .setHandleResult(ImGroupRequestHandleResultEnum.UNHANDLED.getResult()) + .setHandleUserId(null).setHandleContent(null).setHandleTime(null); + updateObj.setId(existing.getId()); + groupRequestMapper.updateById(updateObj); + existing.setInviterUserId(inviterUserId) + .setHandleResult(ImGroupRequestHandleResultEnum.UNHANDLED.getResult()) + .setHandleUserId(null).setHandleContent(null).setHandleTime(null); requests.add(existing); continue; } @@ -238,19 +248,23 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { } @Override - public List getMyGroupRequestList(Long userId, Long lastRequestId, Integer limit) { - return groupRequestMapper.selectMyList(userId, lastRequestId, limit); + public List getGroupRequestListByUserId(Long userId, Long lastRequestId, Integer limit) { + return groupRequestMapper.selectListByUserId(userId, lastRequestId, limit); } @Override - public List getPendingGroupRequestList(Long userId, Long groupId, - Long lastRequestId, Integer limit) { - // 仅群主 / 管理员可拉 - ImGroupMemberDO member = groupMemberService.validateMemberInGroup(groupId, userId); - if (!ImGroupMemberRoleEnum.isOwnerOrAdmin(member.getRole())) { - throw exception(GROUP_REQUEST_NOT_TO_ME); + public List getUnhandledRequestListByOwnerOrAdmin(Long userId) { + // 1. 找出当前用户作为 OWNER / ADMIN 的所有群 + List myMembers = groupMemberService.getActiveGroupMemberListByUserId(userId); + Set ownerOrAdminGroupIds = convertSet(myMembers, + ImGroupMemberDO::getGroupId, + m -> ImGroupMemberRoleEnum.isOwnerOrAdmin(m.getRole())); + if (CollUtil.isEmpty(ownerOrAdminGroupIds)) { + return Collections.emptyList(); } - return groupRequestMapper.selectPendingListByGroupId(groupId, lastRequestId, limit); + // 2. 一次拉所有群的未处理申请 + return groupRequestMapper.selectListByGroupIdsAndHandleResult( + ownerOrAdminGroupIds, ImGroupRequestHandleResultEnum.UNHANDLED.getResult()); } @Override @@ -258,19 +272,6 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { return groupRequestMapper.selectById(id); } - // TODO @AI:【后面点改】应该批量查询;不然这样性能太差了。如果群太多; - @Override - public Map getPendingCountMap(Collection groupIds) { - if (CollUtil.isEmpty(groupIds)) { - return Collections.emptyMap(); - } - Map result = new HashMap<>(groupIds.size()); - for (Long groupId : groupIds) { - result.put(groupId, groupRequestMapper.selectPendingCountByGroupId(groupId)); - } - return result; - } - @Override public PageResult getGroupRequestPage(ImGroupRequestManagerPageReqVO reqVO) { return groupRequestMapper.selectPage(reqVO); @@ -299,18 +300,18 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { */ private GroupRequestNotification buildRequestNotification(ImGroupDO group, ImGroupRequestDO request, AdminUserRespDTO applyUser) { - // TODO @AI:看看能不能链式调用; - GroupRequestNotification payload = new GroupRequestNotification(); - payload.setRequestId(request.getId()); - payload.setGroupId(group.getId()); - payload.setUserId(request.getUserId()); - payload.setInviterUserId(request.getInviterUserId()); - payload.setApplyContent(request.getApplyContent()); - payload.setAddSource(request.getAddSource()); - payload.setOperatorUserId(request.getInviterUserId() != null ? request.getInviterUserId() : request.getUserId()); + // TODO @AI:看看能不能链式调用;同类属性,相同行; + GroupRequestNotification payload = (GroupRequestNotification) new GroupRequestNotification() + .setRequestId(request.getId()) + .setGroupId(group.getId()) + .setUserId(request.getUserId()) + .setInviterUserId(request.getInviterUserId()) + .setApplyContent(request.getApplyContent()) + .setAddSource(request.getAddSource()) + .setOperatorUserId(request.getInviterUserId() != null + ? request.getInviterUserId() : request.getUserId()); if (applyUser != null) { - payload.setUserNickname(applyUser.getNickname()); - payload.setUserAvatar(applyUser.getAvatar()); + payload.setUserNickname(applyUser.getNickname()).setUserAvatar(applyUser.getAvatar()); } return payload; } @@ -326,25 +327,21 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { } Set receivers = new LinkedHashSet<>(listOwnerAndAdminUserIds(group)); receivers.add(applicantUserId); - // TODO @AI:看看能不能用群的通道; for (Long receiverUserId : receivers) { websocketService.sendPrivateMessageAsync(receiverUserId, ImPrivateMessageDTO.ofGroupNotification( messageType, operatorUserId, receiverUserId, payload)); } } - // TODO @AI:这个方法,搞到到 ImGroupMemberService 里去;getGroupMemberListByOwnerAndAdmin;返回 list《do》; /** - * 列出群主 + 全部管理员的用户编号;activeMembers 通常 ≤ 500 + admin 上限 3,过滤成本可接受 + * 列出群主 + 全部管理员的用户编号;以 group.ownerUserId 兜底,避免 group_member 表里 owner 角色记录异常缺失 */ + // TODO @AI:有点过度设计了,直接 convertset 群成员即可! private List listOwnerAndAdminUserIds(ImGroupDO group) { - List activeMembers = groupMemberService.getActiveGroupMemberListByGroupId(group.getId()); Set set = new LinkedHashSet<>(); set.add(group.getOwnerUserId()); - for (ImGroupMemberDO member : activeMembers) { - if (ImGroupMemberRoleEnum.isOwnerOrAdmin(member.getRole())) { - set.add(member.getUserId()); - } + for (ImGroupMemberDO member : groupMemberService.getGroupMemberListByOwnerAndAdmin(group.getId())) { + set.add(member.getUserId()); } return new ArrayList<>(set); } 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 20decf7f32..deb70b1385 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 @@ -28,7 +28,6 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper; import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum; -import cn.iocoder.yudao.module.im.enums.group.ImGroupJoinTypeEnum; import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; @@ -194,7 +193,7 @@ public class ImGroupServiceImpl implements ImGroupService { Long groupId = inviteReqVO.getGroupId(); // 1.1 校验群存在 + 当前用户是群成员 ImGroupDO group = validateGroupExists(groupId); - ImGroupMemberDO operator = groupMemberService.validateMemberInGroup(groupId, userId); + groupMemberService.validateMemberInGroup(groupId, userId); // 1.2 排除已在群中的用户 List activeMembers = groupMemberService.getActiveGroupMemberListByGroupId(groupId); activeMembers.forEach(member -> inviteReqVO.getMemberUserIds().remove(member.getUserId())); @@ -214,17 +213,13 @@ public class ImGroupServiceImpl implements ImGroupService { throw exception(GROUP_MEMBER_EXCEED, MAX_GROUP_MEMBER); } - // TODO @AI:是不是注释的风格,是情况一、情况二,这样搞? - // 2. 按群 joinType + 操作人角色分流:APPLY_AND_NORMAL_INVITE 模式下,普通成员邀请走审批,落 group_request;其余直进 - // TODO @AI:是不是可以放到 isInviteNeedApproval 判断,是否要审批,这样更简洁一点? - boolean needApproval = ImGroupJoinTypeEnum.isInviteNeedApproval(group.getJoinType()) - && !ImGroupMemberRoleEnum.isOwnerOrAdmin(operator.getRole()); - if (needApproval) { - groupRequestService.createInviteRequests(groupId, userId, memberUserIds); + // 2. 情况一:群开启了审批,邀请也走审批,落 group_request 等群主 / 管理员处理 + if (Boolean.TRUE.equals(group.getJoinApproval())) { + 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/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupRequestApprovedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupRequestApprovedNotification.java index e1f0ac0aae..4bb5892ce9 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupRequestApprovedNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupRequestApprovedNotification.java @@ -7,7 +7,7 @@ import lombok.EqualsAndHashCode; * 入群申请被同意通知 *

    * 走私聊通道定向推送给申请人 + 群主 + 全部管理员; - * 申请人侧据此弹 toast、列表项更新;admin 侧据此把 pendingRequestCount 减 1 并从未处理列表移除 + * 申请人侧据此弹 toast、列表项更新;admin 侧据此把 unhandledCount 减 1 并从未处理列表移除 */ @EqualsAndHashCode(callSuper = true) @Data diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupRequestRejectedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupRequestRejectedNotification.java index 7ca56baf15..e302085d61 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupRequestRejectedNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupRequestRejectedNotification.java @@ -6,7 +6,7 @@ import lombok.EqualsAndHashCode; /** * 入群申请被拒绝通知 *

    - * 走私聊通道定向推送给申请人 + 群主 + 全部管理员;admin 侧据此把 pendingRequestCount 减 1 并从未处理列表移除 + * 走私聊通道定向推送给申请人 + 群主 + 全部管理员;admin 侧据此把 unhandledCount 减 1 并从未处理列表移除 */ @EqualsAndHashCode(callSuper = true) @Data diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImplTest.java index a81d101e2e..09f95f5326 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImplTest.java +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImplTest.java @@ -9,7 +9,6 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupRequestDO; import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupRequestMapper; import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum; -import cn.iocoder.yudao.module.im.enums.group.ImGroupJoinTypeEnum; import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; import cn.iocoder.yudao.module.im.enums.group.ImGroupRequestHandleResultEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; @@ -60,7 +59,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { public void testApplyJoinGroup_freeMode_directJoin() { // 准备:群是 FREE 模式 ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) - .joinType(ImGroupJoinTypeEnum.FREE.getType()) + .joinApproval(false) .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupService.validateGroupExists(10L)).thenReturn(group); @@ -88,11 +87,11 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { public void testApplyJoinGroup_approvalMode_createsRequest() { // 准备:群是 APPLY 模式 ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) - .joinType(ImGroupJoinTypeEnum.APPLY.getType()) + .joinApproval(true) .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupService.validateGroupExists(10L)).thenReturn(group); // 群里有 owner + 一个 admin,作为 1503 推送目标 - when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(List.of( + when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(List.of( ImGroupMemberDO.builder().groupId(10L).userId(99L) .role(ImGroupMemberRoleEnum.OWNER.getRole()) .status(CommonStatusEnum.ENABLE.getStatus()).build(), @@ -120,7 +119,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { @Test public void testApplyJoinGroup_alreadyMember_throws() { ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) - .joinType(ImGroupJoinTypeEnum.APPLY.getType()) + .joinApproval(true) .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupService.validateGroupExists(10L)).thenReturn(group); when(groupMemberService.getGroupMember(10L, 1L)).thenReturn( @@ -156,7 +155,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupService.getGroup(10L)).thenReturn(group); - when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(List.of( + when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(List.of( ImGroupMemberDO.builder().groupId(10L).userId(99L) .role(ImGroupMemberRoleEnum.OWNER.getRole()) .status(CommonStatusEnum.ENABLE.getStatus()).build())); @@ -234,7 +233,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupService.getGroup(10L)).thenReturn(group); - when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(List.of( + when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(List.of( ImGroupMemberDO.builder().groupId(10L).userId(1L) .role(ImGroupMemberRoleEnum.OWNER.getRole()) .status(CommonStatusEnum.ENABLE.getStatus()).build())); @@ -246,15 +245,15 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { verify(websocketService, times(2)).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); } - // ==================== createInviteRequests ==================== + // ==================== createInviteRequestList ==================== @Test - public void testCreateInviteRequests_success() { + public void testCreateInviteRequestList_success() { ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) - .joinType(ImGroupJoinTypeEnum.APPLY_AND_NORMAL_INVITE.getType()) + .joinApproval(true) .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupService.validateGroupExists(10L)).thenReturn(group); - when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(List.of( + when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(List.of( ImGroupMemberDO.builder().groupId(10L).userId(99L) .role(ImGroupMemberRoleEnum.OWNER.getRole()) .status(CommonStatusEnum.ENABLE.getStatus()).build())); @@ -263,7 +262,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { 3L, buildUser(3L, "用户B"))); // 调用:邀请人 1L 邀请 2L、3L - groupRequestService.createInviteRequests(10L, 1L, List.of(2L, 3L)); + groupRequestService.createInviteRequestList(10L, 1L, List.of(2L, 3L)); // 断言:插入 2 条申请记录 + 推 1503 给 owner(每条 1 帧)共 2 帧 verify(groupRequestMapper, times(2)).insert(any(ImGroupRequestDO.class)); 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 bf5e775259..60e95883f4 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 @@ -16,7 +16,6 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper; import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum; -import cn.iocoder.yudao.module.im.enums.group.ImGroupJoinTypeEnum; import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; import cn.iocoder.yudao.module.im.service.friend.ImFriendService; @@ -257,9 +256,9 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) .thenReturn(groupService); - // 准备:群存在 + joinType 默认 FREE + 当前用户是群主(操作人必走「直进」分支) + // 准备:群存在 + joinApproval=false(自由进群) + 当前用户是群主 ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) - .joinType(ImGroupJoinTypeEnum.FREE.getType()) + .joinApproval(false) .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupMapper.selectById(10L)).thenReturn(group); when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( @@ -293,7 +292,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { verify(groupMemberService).validateMemberInGroup(10L, 1L); verify(groupMemberService).addGroupMembers(eq(10L), anyCollection(), eq(ImGroupAddSourceEnum.INVITE.getSource()), eq(1L)); - verify(groupRequestService, never()).createInviteRequests(anyLong(), anyLong(), anyCollection()); + verify(groupRequestService, never()).createInviteRequestList(anyLong(), anyLong(), anyCollection()); verify(webSocketService, never()).sendGroupMessageAsync(anyCollection(), any(ImGroupMessageDTO.class)); ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture()); @@ -302,20 +301,16 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { } @Test - public void testInviteGroupMember_strictMode_normalMember_routesToApproval() { + public void testInviteGroupMember_approval_routesToApproval() { try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) .thenReturn(groupService); - // 准备:joinType=APPLY_AND_NORMAL_INVITE 且操作人是普通成员;应走审批分支,落 group_request + // 准备:joinApproval=true,开启审批;任何邀请都需审批,落 group_request ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) - .joinType(ImGroupJoinTypeEnum.APPLY_AND_NORMAL_INVITE.getType()) + .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()) @@ -336,50 +331,13 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { // 调用 groupService.inviteGroupMember(1L, reqVO); - // 断言:走审批分支:调 createInviteRequests;不写群成员、不推 1509 - verify(groupRequestService).createInviteRequests(eq(10L), eq(1L), anyCollection()); + // 断言:走审批分支:调 createInviteRequestList;不写群成员、不推 1509 + verify(groupRequestService).createInviteRequestList(eq(10L), eq(1L), anyCollection()); verify(groupMemberService, never()).addGroupMembers(anyLong(), anyCollection(), any(), any()); verify(groupMessageService, never()).sendGroupMessage(anyLong(), any(ImGroupMessageSendDTO.class)); } } - @Test - public void testInviteGroupMember_strictMode_owner_directJoin() { - try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { - springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) - .thenReturn(groupService); - - // 准备:joinType=APPLY_AND_NORMAL_INVITE 但操作人是群主;走「直进」分支 - ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) - .joinType(ImGroupJoinTypeEnum.APPLY_AND_NORMAL_INVITE.getType()) - .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))); - when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(List.of( - ImFriendDO.builder().userId(1L).friendUserId(2L) - .status(CommonStatusEnum.ENABLE.getStatus()).build())); - - // 调用 - groupService.inviteGroupMember(1L, reqVO); - - // 断言:走直进分支 - verify(groupMemberService).addGroupMembers(eq(10L), anyCollection(), - eq(ImGroupAddSourceEnum.INVITE.getSource()), eq(1L)); - verify(groupRequestService, never()).createInviteRequests(anyLong(), anyLong(), anyCollection()); - } - } - @Test public void testInviteGroupMember_notFriend() { try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { diff --git a/yudao-module-im/src/test/resources/sql/create_tables.sql b/yudao-module-im/src/test/resources/sql/create_tables.sql index 6a12199ed6..2eff3ed25e 100644 --- a/yudao-module-im/src/test/resources/sql/create_tables.sql +++ b/yudao-module-im/src/test/resources/sql/create_tables.sql @@ -49,7 +49,7 @@ CREATE TABLE IF NOT EXISTS "im_group" ( "status" tinyint NOT NULL COMMENT '群状态', "dissolved_time" timestamp DEFAULT NULL COMMENT '解散时间', "muted_all" bit DEFAULT FALSE COMMENT '是否全群禁言', - "join_type" tinyint NOT NULL DEFAULT 0 COMMENT '加群方式;0自由进群 1申请需审批 2申请和普通成员邀请均需审批', + "join_approval" bit NOT NULL DEFAULT FALSE COMMENT '进群是否需群主 / 管理员审批;false 自由进群,true 需审批', "pinned_message_ids" varchar(128) DEFAULT NULL COMMENT '群置顶消息编号列表,逗号分隔', "creator" varchar(64) DEFAULT '', "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -121,7 +121,8 @@ CREATE TABLE IF NOT EXISTS "im_friend_request" ( "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "deleted" bit NOT NULL DEFAULT FALSE, "tenant_id" bigint NOT NULL DEFAULT 0, - PRIMARY KEY ("id") + PRIMARY KEY ("id"), + CONSTRAINT "uk_im_friend_request" UNIQUE ("from_user_id", "to_user_id", "tenant_id") ) COMMENT 'IM 好友申请记录表'; CREATE TABLE IF NOT EXISTS "im_group_request" ( @@ -141,7 +142,8 @@ CREATE TABLE IF NOT EXISTS "im_group_request" ( "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "deleted" bit NOT NULL DEFAULT FALSE, "tenant_id" bigint NOT NULL DEFAULT 0, - PRIMARY KEY ("id") + PRIMARY KEY ("id"), + CONSTRAINT "uk_im_group_request" UNIQUE ("group_id", "user_id", "tenant_id") ) COMMENT 'IM 加群申请记录表'; CREATE TABLE IF NOT EXISTS "im_face_pack" (