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 群加群方式枚举
- *
- * 控制「外部用户主动申请」和「群成员邀请」两条入群路径是否需要群主 / 管理员审批
- *
- * - {@link #FREE}:用户搜到群可直接进群;任何成员邀请也直进
- * - {@link #APPLY}:用户申请进群需群主 / 管理员审批;任何成员邀请仍直进
- * - {@link #APPLY_AND_NORMAL_INVITE}:用户申请需审批;普通成员邀请也需审批;群主 / 管理员邀请直进
- *
- *
- * @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" (