mirror of
https://gitee.com/zhijiantianya/ruoyi-vue-pro.git
synced 2026-07-06 21:45:37 +08:00
✨ feat(im): 初始化群申请 v0.1:第二把 review
This commit is contained in:
@ -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
|
||||
* <p>
|
||||
* pinnedMessages 仅当前用户是该群有效成员时回填;pendingRequestCount 仅当前用户是该群 owner / admin 时回填
|
||||
* 仅当登录用户是某群的有效成员时才回填该群的 pinnedMessages,避免非成员 / 已退群用户越权拿到置顶消息内容
|
||||
*/
|
||||
private List<ImGroupRespVO> buildGroupRespVOList(List<ImGroupDO> groups, Long loginUserId) {
|
||||
if (CollUtil.isEmpty(groups)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 1.1 当前用户的活跃群成员记录;用于 pinnedMessages 回填的「成员」可见性 + pendingRequestCount 回填的「owner / admin」可见性
|
||||
List<ImGroupMemberDO> myMembers = groupMemberService.getActiveGroupMemberListByUserId(loginUserId);
|
||||
Set<Long> activeGroupIds = convertSet(myMembers, ImGroupMemberDO::getGroupId);
|
||||
Set<Long> ownerOrAdminGroupIds = convertSet(myMembers,
|
||||
ImGroupMemberDO::getGroupId,
|
||||
member -> ImGroupMemberRoleEnum.isOwnerOrAdmin(member.getRole()));
|
||||
// 1.2 批量查置顶消息
|
||||
// 仅当前用户是有效成员的群才允许回填置顶消息
|
||||
Set<Long> activeGroupIds = convertSet(
|
||||
groupMemberService.getActiveGroupMemberListByUserId(loginUserId), ImGroupMemberDO::getGroupId);
|
||||
Set<Long> allMessageIds = convertSetByFlatMap(groups, group -> activeGroupIds.contains(group.getId())
|
||||
? CollUtil.emptyIfNull(group.getPinnedMessageIds()).stream() : Stream.empty());
|
||||
Map<Long, ImGroupMessageDO> messageMap = groupMessageService.getGroupMessageMap(allMessageIds);
|
||||
// 1.3 仅 owner / admin 群批量统计未处理申请数
|
||||
// TODO @AI:这块讨论中。。。
|
||||
Map<Long, Long> 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;
|
||||
}
|
||||
|
||||
@ -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<List<ImGroupRequestRespVO>> getMyGroupRequestList(
|
||||
public CommonResult<List<ImGroupRequestRespVO>> getGroupRequestList(
|
||||
@Parameter(description = "当前列表最旧记录的 id;首页不传")
|
||||
@RequestParam(value = "lastRequestId", required = false) Long lastRequestId,
|
||||
@Parameter(description = "单次拉取条数", required = true)
|
||||
@RequestParam("limit") @Min(1) @Max(200) Integer limit) {
|
||||
List<ImGroupRequestDO> list = groupRequestService.getMyGroupRequestList(getLoginUserId(), lastRequestId, limit);
|
||||
return success(buildList(list));
|
||||
List<ImGroupRequestDO> list = groupRequestService.getGroupRequestListByUserId(
|
||||
getLoginUserId(), lastRequestId, limit);
|
||||
return success(buildVOList(list));
|
||||
}
|
||||
|
||||
// TODO @AI:pending-list;
|
||||
@GetMapping("/list-pending")
|
||||
@Operation(summary = "查询指定群的未处理加群申请列表(仅群主或管理员可调);游标分页")
|
||||
public CommonResult<List<ImGroupRequestRespVO>> 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<ImGroupRequestDO> list = groupRequestService.getPendingGroupRequestList(
|
||||
getLoginUserId(), groupId, lastRequestId, limit);
|
||||
return success(buildList(list));
|
||||
@GetMapping("/unhandled-list")
|
||||
@Operation(summary = "查询「我管理的所有群」下的未处理加群申请列表(不分页);前端 store 据此派生横幅红点 + Drawer 列表")
|
||||
public CommonResult<List<ImGroupRequestRespVO>> getUnhandledRequestList() {
|
||||
List<ImGroupRequestDO> 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<ImGroupRequestRespVO> getMyGroupRequest(@RequestParam("id") Long id) {
|
||||
public CommonResult<ImGroupRequestRespVO> 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<ImGroupRequestRespVO> buildList(List<ImGroupRequestDO> list) {
|
||||
private List<ImGroupRequestRespVO> buildVOList(List<ImGroupRequestDO> list) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 1. 聚合 user / inviter 用户信息
|
||||
// TODO @AI:.filter(Objects::nonNull) 内部已经过滤了;
|
||||
// 1. 聚合 user / inviter 用户信息;convertSetByFlatMap 内部已过滤 null
|
||||
Set<Long> userIds = convertSetByFlatMap(list,
|
||||
request -> Stream.of(request.getUserId(), request.getInviterUserId())
|
||||
.filter(Objects::nonNull));
|
||||
request -> Stream.of(request.getUserId(), request.getInviterUserId()));
|
||||
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
|
||||
// 2. 聚合群信息(封禁 / 解散群也要回填,便于前端展示历史)
|
||||
Set<Long> groupIds = convertSet(list, ImGroupRequestDO::getGroupId);
|
||||
|
||||
@ -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<Long> memberUserIds;
|
||||
|
||||
@Schema(description = "加群方式;不传默认 0 自由进群", example = "0")
|
||||
@InEnum(ImGroupJoinTypeEnum.class)
|
||||
private Integer joinType; // 参见 ImGroupJoinTypeEnum 枚举类
|
||||
@Schema(description = "进群是否需群主 / 管理员审批;不传默认 false 自由进群", example = "false")
|
||||
private Boolean joinApproval;
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
/**
|
||||
* 加群方式
|
||||
* 进群是否需群主 / 管理员审批
|
||||
* <p>
|
||||
* 枚举 {@link ImGroupJoinTypeEnum}
|
||||
* false 自由进群(默认);true 需审批,所有「申请」「邀请」路径都需群主 / 管理员同意
|
||||
*/
|
||||
private Integer joinType;
|
||||
private Boolean joinApproval;
|
||||
/**
|
||||
* 是否封禁
|
||||
*/
|
||||
|
||||
@ -18,12 +18,10 @@ import java.util.List;
|
||||
@Mapper
|
||||
public interface ImFriendRequestMapper extends BaseMapperX<ImFriendRequestDO> {
|
||||
|
||||
default ImFriendRequestDO selectLatestByFromUserIdAndToUserId(Long fromUserId, Long toUserId) {
|
||||
default ImFriendRequestDO selectByFromUserIdAndToUserId(Long fromUserId, Long toUserId) {
|
||||
return selectOne(new LambdaQueryWrapperX<ImFriendRequestDO>()
|
||||
.eq(ImFriendRequestDO::getFromUserId, fromUserId)
|
||||
.eq(ImFriendRequestDO::getToUserId, toUserId)
|
||||
.orderByDesc(ImFriendRequestDO::getId)
|
||||
.last("LIMIT 1"));
|
||||
.eq(ImFriendRequestDO::getToUserId, toUserId));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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<ImGroupRequestDO> {
|
||||
|
||||
/**
|
||||
* 取最新一条未处理申请;inviterUserId 为 null 表示主动申请
|
||||
* <p>
|
||||
* 主动申请、不同邀请人发起的邀请申请彼此互不覆盖,复用键含 inviterUserId
|
||||
*/
|
||||
// TODO @AI:一般合理的,是不是可以覆盖????
|
||||
default ImGroupRequestDO selectLatestPendingByGroupIdAndUserIdAndInviter(
|
||||
Long groupId, Long userId, Long inviterUserId) {
|
||||
LambdaQueryWrapperX<ImGroupRequestDO> 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<ImGroupRequestDO>()
|
||||
.eq(ImGroupRequestDO::getGroupId, groupId)
|
||||
.eq(ImGroupRequestDO::getUserId, userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 拉取「我相关」的加群申请列表(含我主动申请、我被邀请待审);游标分页
|
||||
*/
|
||||
// TODO @AI:selectListByMy;保持风格一致;
|
||||
default List<ImGroupRequestDO> selectMyList(Long userId, Long lastRequestId, int limit) {
|
||||
default List<ImGroupRequestDO> selectListByUserId(Long userId, Long lastRequestId, int limit) {
|
||||
return selectList(new LambdaQueryWrapperX<ImGroupRequestDO>()
|
||||
.eq(ImGroupRequestDO::getUserId, userId)
|
||||
.ltIfPresent(ImGroupRequestDO::getId, lastRequestId)
|
||||
@ -51,26 +33,16 @@ public interface ImGroupRequestMapper extends BaseMapperX<ImGroupRequestDO> {
|
||||
.last("LIMIT " + limit));
|
||||
}
|
||||
|
||||
// TODO @AI:类似下面的注释,减少 mapper 的业务型;
|
||||
default List<ImGroupRequestDO> selectPendingListByGroupId(Long groupId, Long lastRequestId, int limit) {
|
||||
default List<ImGroupRequestDO> selectListByGroupIdsAndHandleResult(Collection<Long> groupIds, Integer handleResult) {
|
||||
return selectList(new LambdaQueryWrapperX<ImGroupRequestDO>()
|
||||
.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<ImGroupRequestDO>()
|
||||
.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<ImGroupRequestDO>()
|
||||
.eq(ImGroupRequestDO::getId, id).eq(ImGroupRequestDO::getHandleResult, handleResult));
|
||||
.eq(ImGroupRequestDO::getId, id).eq(ImGroupRequestDO::getHandleResult, expectedHandleResult));
|
||||
}
|
||||
|
||||
default PageResult<ImGroupRequestDO> selectPage(ImGroupRequestManagerPageReqVO reqVO) {
|
||||
|
||||
@ -17,11 +17,10 @@ import java.util.Arrays;
|
||||
@Getter
|
||||
public enum ImGroupAddSourceEnum implements ArrayValuable<Integer> {
|
||||
|
||||
// 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);
|
||||
|
||||
|
||||
@ -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 群加群方式枚举
|
||||
* <p>
|
||||
* 控制「外部用户主动申请」和「群成员邀请」两条入群路径是否需要群主 / 管理员审批
|
||||
* <ul>
|
||||
* <li>{@link #FREE}:用户搜到群可直接进群;任何成员邀请也直进</li>
|
||||
* <li>{@link #APPLY}:用户申请进群需群主 / 管理员审批;任何成员邀请仍直进</li>
|
||||
* <li>{@link #APPLY_AND_NORMAL_INVITE}:用户申请需审批;普通成员邀请也需审批;群主 / 管理员邀请直进</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum ImGroupJoinTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -58,6 +58,14 @@ public interface ImGroupMemberService {
|
||||
*/
|
||||
List<ImGroupMemberDO> getActiveGroupMemberListByGroupId(Long groupId);
|
||||
|
||||
/**
|
||||
* 获取群里活跃的群主 + 管理员;用于审批通知定向推送
|
||||
*
|
||||
* @param groupId 群编号
|
||||
* @return 群主 / 管理员的成员记录列表(按入群时间倒序的天然成员表顺序)
|
||||
*/
|
||||
List<ImGroupMemberDO> getGroupMemberListByOwnerAndAdmin(Long groupId);
|
||||
|
||||
/**
|
||||
* 根据群编号查询有效成员的 userId 列表(仅 ENABLE 状态)
|
||||
* <p>
|
||||
|
||||
@ -73,6 +73,18 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
|
||||
return groupMemberMapper.selectListByGroupIdAndStatus(groupId, CommonStatusEnum.ENABLE.getStatus());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ImGroupMemberDO> getGroupMemberListByOwnerAndAdmin(Long groupId) {
|
||||
// TODO @AI:把条件往下传;这样减少加载数据量!
|
||||
List<ImGroupMemberDO> members = getActiveGroupMemberListByGroupId(groupId);
|
||||
if (CollUtil.isEmpty(members)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return members.stream()
|
||||
.filter(member -> ImGroupMemberRoleEnum.isOwnerOrAdmin(member.getRole()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 只缓存 userId 列表而非整个 {@link ImGroupMemberDO},理由:
|
||||
* <ul>
|
||||
|
||||
@ -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 {
|
||||
/**
|
||||
* 用户主动申请加群
|
||||
* <p>
|
||||
* 群 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);
|
||||
|
||||
/**
|
||||
* 同意加群申请(群主或管理员)
|
||||
* <p>
|
||||
* 处理前校验入群人数上限;通过后写 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 且邀请人是普通成员时调用)
|
||||
* <p>
|
||||
* 每个被邀请人创建一条 inviter_user_id=操作人 的待审批记录;通知群主 / 全部管理员
|
||||
* 邀请创建审批申请;inviteGroupMember 在群开启审批时调用,每个被邀请人 upsert 一条 inviterUserId=操作人 的记录
|
||||
*
|
||||
* @param groupId 群编号
|
||||
* @param inviterUserId 邀请人用户编号
|
||||
* @param invitedUserIds 被邀请人用户编号集合
|
||||
*/
|
||||
void createInviteRequests(Long groupId, Long inviterUserId, Collection<Long> invitedUserIds);
|
||||
void createInviteRequestList(Long groupId, Long inviterUserId, Collection<Long> invitedUserIds);
|
||||
|
||||
/**
|
||||
* 拉取「我相关」加群申请列表(含我主动申请、我被邀请待审);游标分页
|
||||
@ -67,18 +61,17 @@ public interface ImGroupRequestService {
|
||||
* @param limit 单次拉取条数
|
||||
* @return 申请记录列表,按 id 倒序
|
||||
*/
|
||||
List<ImGroupRequestDO> getMyGroupRequestList(Long userId, Long lastRequestId, Integer limit);
|
||||
List<ImGroupRequestDO> getGroupRequestListByUserId(Long userId, Long lastRequestId, Integer limit);
|
||||
|
||||
/**
|
||||
* 拉取指定群下未处理申请;仅群主 / 管理员可调
|
||||
* 拉取「我管理的所有群」下的未处理申请列表
|
||||
* <p>
|
||||
* 前端 store 据此派生:每个群的未处理总数(用于群顶部横幅红点)+ 列表内容(用于 Drawer)
|
||||
*
|
||||
* @param userId 操作人用户编号
|
||||
* @param groupId 群编号
|
||||
* @param lastRequestId 游标 id
|
||||
* @param limit 单次拉取条数
|
||||
* @return 未处理申请列表
|
||||
* @param userId 当前用户编号;后端按 ImGroupMember.role 过滤出我作为 OWNER / ADMIN 的群
|
||||
* @return 未处理申请列表(不分页)
|
||||
*/
|
||||
List<ImGroupRequestDO> getPendingGroupRequestList(Long userId, Long groupId, Long lastRequestId, Integer limit);
|
||||
List<ImGroupRequestDO> getUnhandledRequestListByOwnerOrAdmin(Long userId);
|
||||
|
||||
/**
|
||||
* 按 id 单查申请记录;通用读接口,越权过滤交由调用方
|
||||
@ -88,14 +81,6 @@ public interface ImGroupRequestService {
|
||||
*/
|
||||
ImGroupRequestDO getGroupRequest(Long id);
|
||||
|
||||
/**
|
||||
* 批量统计指定群的未处理申请数量;用于 ImGroupRespVO.pendingRequestCount 回填
|
||||
*
|
||||
* @param groupIds 群编号集合
|
||||
* @return 群编号 → 未处理申请数 Map
|
||||
*/
|
||||
Map<Long, Long> getPendingCountMap(Collection<Long> groupIds);
|
||||
|
||||
// ==================== 管理后台 ====================
|
||||
|
||||
/**
|
||||
|
||||
@ -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<Long> invitedUserIds) {
|
||||
public void createInviteRequestList(Long groupId, Long inviterUserId, Collection<Long> 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<ImGroupRequestDO> 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<ImGroupRequestDO> getMyGroupRequestList(Long userId, Long lastRequestId, Integer limit) {
|
||||
return groupRequestMapper.selectMyList(userId, lastRequestId, limit);
|
||||
public List<ImGroupRequestDO> getGroupRequestListByUserId(Long userId, Long lastRequestId, Integer limit) {
|
||||
return groupRequestMapper.selectListByUserId(userId, lastRequestId, limit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ImGroupRequestDO> 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<ImGroupRequestDO> getUnhandledRequestListByOwnerOrAdmin(Long userId) {
|
||||
// 1. 找出当前用户作为 OWNER / ADMIN 的所有群
|
||||
List<ImGroupMemberDO> myMembers = groupMemberService.getActiveGroupMemberListByUserId(userId);
|
||||
Set<Long> 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<Long, Long> getPendingCountMap(Collection<Long> groupIds) {
|
||||
if (CollUtil.isEmpty(groupIds)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<Long, Long> result = new HashMap<>(groupIds.size());
|
||||
for (Long groupId : groupIds) {
|
||||
result.put(groupId, groupRequestMapper.selectPendingCountByGroupId(groupId));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<ImGroupRequestDO> 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<Long> 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<Long> listOwnerAndAdminUserIds(ImGroupDO group) {
|
||||
List<ImGroupMemberDO> activeMembers = groupMemberService.getActiveGroupMemberListByGroupId(group.getId());
|
||||
Set<Long> 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);
|
||||
}
|
||||
|
||||
@ -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<ImGroupMemberDO> 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);
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import lombok.EqualsAndHashCode;
|
||||
* 入群申请被同意通知
|
||||
* <p>
|
||||
* 走私聊通道定向推送给申请人 + 群主 + 全部管理员;
|
||||
* 申请人侧据此弹 toast、列表项更新;admin 侧据此把 pendingRequestCount 减 1 并从未处理列表移除
|
||||
* 申请人侧据此弹 toast、列表项更新;admin 侧据此把 unhandledCount 减 1 并从未处理列表移除
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
|
||||
@ -6,7 +6,7 @@ import lombok.EqualsAndHashCode;
|
||||
/**
|
||||
* 入群申请被拒绝通知
|
||||
* <p>
|
||||
* 走私聊通道定向推送给申请人 + 群主 + 全部管理员;admin 侧据此把 pendingRequestCount 减 1 并从未处理列表移除
|
||||
* 走私聊通道定向推送给申请人 + 群主 + 全部管理员;admin 侧据此把 unhandledCount 减 1 并从未处理列表移除
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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<ImGroupMessageSendDTO> 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<SpringUtil> 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<SpringUtil> 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<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
|
||||
@ -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" (
|
||||
|
||||
Reference in New Issue
Block a user