From 3d8b8792fdeff6156c37d73edf682205bf0bcae2 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 8 May 2026 17:42:13 +0800 Subject: [PATCH] ♻️ refactor(im): 业务策略数值从 ImCommonConstants 上移到 ImProperties(按 group / message 子模块分组),常量类仅保留 AT_USER_ID_ALL 协议契约值 --- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImCommonConstants.java | 44 ++------------------------------------------ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/config/ImProperties.java | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java | 6 +++--- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java | 36 ++++++++++++++++++++++-------------- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java | 19 ++++++++++++------- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java | 19 ++++++++++++------- 6 files changed, 114 insertions(+), 74 deletions(-) diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImCommonConstants.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImCommonConstants.java index 10e9134..404d2fb 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImCommonConstants.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImCommonConstants.java @@ -8,50 +8,10 @@ package cn.iocoder.yudao.module.im.enums; public interface ImCommonConstants { /** - * pull 最大拉取数量 - */ - int MESSAGE_MAX_PULL_SIZE = 1000; - - /** - * 群最大成员人数 - */ - int MAX_GROUP_MEMBER = 500; - - /** - * 单群管理员人数上限 - */ - int GROUP_ADMIN_MAX_COUNT = 3; - - /** - * 单群置顶消息条数上限 - */ - int GROUP_PIN_MAX_COUNT = 5; - - /** * 群消息 @ 所有人的特殊用户编号 - */ - long AT_USER_ID_ALL = -1L; - - /** - * 消息撤回时间限制(分钟) - */ - int MESSAGE_RECALL_TIMEOUT_MINUTES = 5; - - /** - * 私聊离线消息最大拉取天数 - *

- * 客户端通过 pull 接口增量拉取私聊离线消息时,仅返回最近 N 天内产生的消息, - * 超过该窗口的老消息不再主动推送(可通过历史消息接口按需倒翻)。 - */ - int MESSAGE_PRIVATE_PULL_MAX_DAYS = 30; - - /** - * 群聊离线消息最大拉取天数 *

- * 客户端通过 pull 接口增量拉取群聊离线消息时,仅返回最近 N 天内产生的消息; - * 退群前消息的补齐也以该窗口为基准(早于窗口的退群群不再扫描),避免老用户首次 - * 拉取时对历史退群群做大量查询。 + * 前后端协议契约值;atUserIds 数组里出现该值表示 @ 全体成员 */ - int MESSAGE_GROUP_PULL_MAX_DAYS = 30; + long AT_USER_ID_ALL = -1L; } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/config/ImProperties.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/config/ImProperties.java index 574ebb9..63f332e 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/config/ImProperties.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/config/ImProperties.java @@ -7,7 +7,7 @@ import org.springframework.stereotype.Component; /** * IM 模块全局配置 *

- * 各子模块用嵌套 inner class 区分(friend / 后续 group / sensitive 等), + * 各子模块用嵌套 inner class 区分(friend / group / message / 后续 sensitive 等), * yaml 路径保持 yudao.im.{module}.{key} 与原有部署保持兼容 * * @author 芋道源码 @@ -19,6 +19,10 @@ public class ImProperties { private Friend friend = new Friend(); + private Group group = new Group(); + + private Message message = new Message(); + /** * 好友模块配置 */ @@ -35,4 +39,62 @@ public class ImProperties { } + /** + * 群模块配置 + */ + @Data + public static class Group { + + /** + * 群最大成员人数 + */ + private int maxMember = 500; + + /** + * 单群管理员人数上限 + */ + private int adminMaxCount = 3; + + /** + * 单群置顶消息条数上限 + */ + private int pinMaxCount = 5; + + } + + /** + * 消息模块配置 + */ + @Data + public static class Message { + + /** + * pull 最大拉取数量 + */ + private int maxPullSize = 1000; + + /** + * 消息撤回时间限制(分钟) + */ + private int recallTimeoutMinutes = 5; + + /** + * 私聊离线消息最大拉取天数 + *

+ * 客户端通过 pull 接口增量拉取私聊离线消息时,仅返回最近 N 天内产生的消息, + * 超过该窗口的老消息不再主动推送(可通过历史消息接口按需倒翻)。 + */ + private int privatePullMaxDays = 30; + + /** + * 群聊离线消息最大拉取天数 + *

+ * 客户端通过 pull 接口增量拉取群聊离线消息时,仅返回最近 N 天内产生的消息; + * 退群前消息的补齐也以该窗口为基准(早于窗口的退群群不再扫描),避免老用户首次 + * 拉取时对历史退群群做大量查询。 + */ + private int groupPullMaxDays = 30; + + } + } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java index 089ed73..8cc1273 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java @@ -99,8 +99,8 @@ public interface ImGroupService { /** * 获取指定用户的群列表 *

- * 返回用户当前仍有效的群,以及最近 {@link cn.iocoder.yudao.module.im.enums.ImCommonConstants#MESSAGE_GROUP_PULL_MAX_DAYS} - * 天内退群的群 —— 退群前可能还有离线消息需要展示,前端需要把这些群信息作为缓存。 + * 返回用户当前仍有效的群,以及最近 yudao.im.message.group-pull-max-days 天内退群的群 + * —— 退群前可能还有离线消息需要展示,前端需要把这些群信息作为缓存。 * * @param userId 用户编号 * @return 群列表 @@ -171,7 +171,7 @@ public interface ImGroupService { /** * 置顶群消息(仅群主或管理员可执行) *

- * 上限 {@link cn.iocoder.yudao.module.im.enums.ImCommonConstants#GROUP_PIN_MAX_COUNT};幂等失败时抛业务异常 + * 上限由 yudao.im.group.pin-max-count 控制;幂等失败时抛业务异常 * * @param userId 当前登录用户编号 * @param groupId 群编号 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 2220f3a..863f19a 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,6 +28,7 @@ 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.framework.config.ImProperties; 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; @@ -53,7 +54,6 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP; import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; -import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.*; /** * 用户群 Service 实现类 @@ -83,6 +83,9 @@ public class ImGroupServiceImpl implements ImGroupService { @Resource private AdminUserApi adminUserApi; + @Resource + private ImProperties imProperties; + // ==================== 群的写操作 ==================== @Override @@ -101,9 +104,10 @@ public class ImGroupServiceImpl implements ImGroupService { throw exception(GROUP_INVITE_NOT_FRIEND, getUserNicknames(notFriendUserIds)); } } - // 1.3 校验群人数上限(创建者 + 初始成员 ≤ MAX_GROUP_MEMBER) - if (initialMemberUserIds.size() + 1 > MAX_GROUP_MEMBER) { - throw exception(GROUP_MEMBER_EXCEED, MAX_GROUP_MEMBER); + // 1.3 校验群人数上限(创建者 + 初始成员 ≤ 群成员上限) + int maxMember = imProperties.getGroup().getMaxMember(); + if (initialMemberUserIds.size() + 1 > maxMember) { + throw exception(GROUP_MEMBER_EXCEED, maxMember); } // 2.1 插入群记录 @@ -210,8 +214,9 @@ public class ImGroupServiceImpl implements ImGroupService { throw exception(GROUP_INVITE_NOT_FRIEND, getUserNicknames(notFriendUserIds)); } // 1.4 校验群人数上限 - if (activeMembers.size() + memberUserIds.size() > MAX_GROUP_MEMBER) { - throw exception(GROUP_MEMBER_EXCEED, MAX_GROUP_MEMBER); + int maxMember = imProperties.getGroup().getMaxMember(); + if (activeMembers.size() + memberUserIds.size() > maxMember) { + throw exception(GROUP_MEMBER_EXCEED, maxMember); } // 2. 情况一:群开启审批 + 邀请人是普通成员,落 group_request 等群主 / 管理员处理 @@ -304,8 +309,9 @@ public class ImGroupServiceImpl implements ImGroupService { // 1.4 校验上限 Long existAdminCount = groupMemberService.getGroupMemberCountByRole( groupId, ImGroupMemberRoleEnum.ADMIN.getRole()); - if (existAdminCount + changedUserIds.size() > GROUP_ADMIN_MAX_COUNT) { - throw exception(GROUP_ADMIN_MAX_LIMIT, GROUP_ADMIN_MAX_COUNT); + int adminMaxCount = imProperties.getGroup().getAdminMaxCount(); + if (existAdminCount + changedUserIds.size() > adminMaxCount) { + throw exception(GROUP_ADMIN_MAX_LIMIT, adminMaxCount); } // 2. 批量更新角色 @@ -404,8 +410,9 @@ public class ImGroupServiceImpl implements ImGroupService { if (pinned.contains(messageId)) { throw exception(GROUP_MESSAGE_ALREADY_PINNED); } - if (pinned.size() >= GROUP_PIN_MAX_COUNT) { - throw exception(GROUP_MESSAGE_PIN_MAX_LIMIT, GROUP_PIN_MAX_COUNT); + int pinMaxCount = imProperties.getGroup().getPinMaxCount(); + if (pinned.size() >= pinMaxCount) { + throw exception(GROUP_MESSAGE_PIN_MAX_LIMIT, pinMaxCount); } pinned.add(messageId); groupMapper.updateById(new ImGroupDO().setId(groupId).setPinnedMessageIds(pinned)); @@ -465,8 +472,9 @@ public class ImGroupServiceImpl implements ImGroupService { @Override public void validateMemberCountLimit(Long groupId, int addCount) { int activeCount = groupMemberService.getActiveGroupMemberUserIdsByGroupId(groupId).size(); - if (activeCount + addCount > MAX_GROUP_MEMBER) { - throw exception(GROUP_MEMBER_EXCEED, MAX_GROUP_MEMBER); + int maxMember = imProperties.getGroup().getMaxMember(); + if (activeCount + addCount > maxMember) { + throw exception(GROUP_MEMBER_EXCEED, maxMember); } } @@ -512,8 +520,8 @@ public class ImGroupServiceImpl implements ImGroupService { public List getMyGroupList(Long userId) { // 1.1 查用户所在的、仍有效的群成员记录(仅 ENABLE 状态) List members = groupMemberService.getActiveGroupMemberListByUserId(userId); - // 1.2 再查最近 MESSAGE_GROUP_PULL_MAX_DAYS 天内退群的成员记录(退群前可能有离线消息需要展示,一并返回作为前端缓存) - LocalDateTime minQuitTime = LocalDateTime.now().minusDays(MESSAGE_GROUP_PULL_MAX_DAYS); + // 1.2 再查最近 N 天(与群消息离线拉取窗口一致)内退群的成员记录(退群前可能有离线消息需要展示,一并返回作为前端缓存) + LocalDateTime minQuitTime = LocalDateTime.now().minusDays(imProperties.getMessage().getGroupPullMaxDays()); members.addAll(groupMemberService.getQuitGroupMemberListByUserId(userId, minQuitTime)); if (CollUtil.isEmpty(members)) { return Collections.emptyList(); diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java index 5483d9b..bd8527c 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java @@ -21,6 +21,7 @@ import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.framework.config.ImProperties; import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; import cn.iocoder.yudao.module.im.service.group.ImGroupService; import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; @@ -43,7 +44,6 @@ import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; -import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.*; /** * IM 群聊消息 Service 实现类 @@ -77,6 +77,9 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @Resource private ImWebSocketService imWebSocketService; + @Resource + private ImProperties imProperties; + @Override public ImGroupMessageDO sendGroupMessage(Long senderId, ImGroupMessageSendReqVO reqVO) { // 1.1 幂等校验:根据 senderId + clientMessageId 查重 @@ -161,8 +164,9 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { throw exception(MESSAGE_ALREADY_RECALLED); } // 1.4 只允许撤回限定时间内的消息 - if (message.getSendTime().plusMinutes(MESSAGE_RECALL_TIMEOUT_MINUTES).isBefore(LocalDateTime.now())) { - throw exception(MESSAGE_RECALL_TIMEOUT, MESSAGE_RECALL_TIMEOUT_MINUTES); + int recallTimeoutMinutes = imProperties.getMessage().getRecallTimeoutMinutes(); + if (message.getSendTime().plusMinutes(recallTimeoutMinutes).isBefore(LocalDateTime.now())) { + throw exception(MESSAGE_RECALL_TIMEOUT, recallTimeoutMinutes); } // 1.5 校验撤回人仍在群中 groupMemberService.validateMemberInGroup(message.getGroupId(), userId); @@ -178,12 +182,13 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @Override public List pullGroupMessageList(Long userId, Long minId, Integer size) { - if (size > MESSAGE_MAX_PULL_SIZE) { - throw exception(MESSAGE_PULL_SIZE_EXCEEDED, MESSAGE_MAX_PULL_SIZE); + int maxPullSize = imProperties.getMessage().getMaxPullSize(); + if (size > maxPullSize) { + throw exception(MESSAGE_PULL_SIZE_EXCEEDED, maxPullSize); } - // 0. 拉取时间窗:超过窗口的老消息不再通过离线通道推送 - LocalDateTime minSendTime = LocalDateTime.now().minusDays(MESSAGE_GROUP_PULL_MAX_DAYS); + // 0. 拉取时间窗;超过窗口的老消息不再通过离线通道推送 + LocalDateTime minSendTime = LocalDateTime.now().minusDays(imProperties.getMessage().getGroupPullMaxDays()); // 1.1 主查询:仅用"当前仍在群"的成员记录驱动;若首批消息过滤后为空,则允许内部重试 List activeMembers = groupMemberService.getActiveGroupMemberListByUserId(userId); diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java index a0ba4f2..ebcc544 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java @@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum; +import cn.iocoder.yudao.module.im.framework.config.ImProperties; import cn.iocoder.yudao.module.im.service.friend.ImFriendService; import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO; import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; @@ -33,7 +34,6 @@ import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; -import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.*; /** * IM 私聊消息 Service 实现类 @@ -56,6 +56,9 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { @Resource private ImWebSocketService imWebSocketService; + @Resource + private ImProperties imProperties; + @Override public ImPrivateMessageDO sendPrivateMessage(Long senderId, ImPrivateMessageSendReqVO reqVO) { // 1.1 幂等校验:根据 senderId + clientMessageId 查重 @@ -139,8 +142,9 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { throw exception(MESSAGE_ALREADY_RECALLED); } // 1.4 只允许撤回限定时间内的消息 - if (message.getSendTime().plusMinutes(MESSAGE_RECALL_TIMEOUT_MINUTES).isBefore(LocalDateTime.now())) { - throw exception(MESSAGE_RECALL_TIMEOUT, MESSAGE_RECALL_TIMEOUT_MINUTES); + int recallTimeoutMinutes = imProperties.getMessage().getRecallTimeoutMinutes(); + if (message.getSendTime().plusMinutes(recallTimeoutMinutes).isBefore(LocalDateTime.now())) { + throw exception(MESSAGE_RECALL_TIMEOUT, recallTimeoutMinutes); } // 2. 更新原消息状态为撤回 @@ -190,11 +194,12 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { @Override public List pullPrivateMessageList(Long userId, Long minId, Integer size) { - if (size > MESSAGE_MAX_PULL_SIZE) { - throw exception(MESSAGE_PULL_SIZE_EXCEEDED, MESSAGE_MAX_PULL_SIZE); + int maxPullSize = imProperties.getMessage().getMaxPullSize(); + if (size > maxPullSize) { + throw exception(MESSAGE_PULL_SIZE_EXCEEDED, maxPullSize); } - // 0. 拉取时间窗:超过窗口的老消息不再通过离线通道推送 - LocalDateTime minSendTime = LocalDateTime.now().minusDays(MESSAGE_PRIVATE_PULL_MAX_DAYS); + // 0. 拉取时间窗;超过窗口的老消息不再通过离线通道推送 + LocalDateTime minSendTime = LocalDateTime.now().minusDays(imProperties.getMessage().getPrivatePullMaxDays()); // 根据 minId 和 minSendTime 拉取消息,避免 minId 恰好被发出后才拉取,导致漏消息 List messages = privateMessageMapper.selectListByMinId(userId, minId, minSendTime, size); -- libgit2 0.21.4