Commit 3d8b8792fdeff6156c37d73edf682205bf0bcae2

Authored by YunaiV
1 parent 1db02564

♻️ refactor(im): 业务策略数值从 ImCommonConstants 上移到 ImProperties(按 group / message 子模…

…块分组),常量类仅保留 AT_USER_ID_ALL 协议契约值

♻️ refactor(im): 抽出 utils/config.ts 集中数值常量,按业务域统一前缀(GROUP_ / MESSAGE_ / FRIEND_ / CONVERSATION_ / FORWARD_),constants.ts 只留协议枚举与契约值
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;
8 8 public interface ImCommonConstants {
9 9  
10 10 /**
11   - * pull 最大拉取数量
12   - */
13   - int MESSAGE_MAX_PULL_SIZE = 1000;
14   -
15   - /**
16   - * 群最大成员人数
17   - */
18   - int MAX_GROUP_MEMBER = 500;
19   -
20   - /**
21   - * 单群管理员人数上限
22   - */
23   - int GROUP_ADMIN_MAX_COUNT = 3;
24   -
25   - /**
26   - * 单群置顶消息条数上限
27   - */
28   - int GROUP_PIN_MAX_COUNT = 5;
29   -
30   - /**
31 11 * 群消息 @ 所有人的特殊用户编号
32   - */
33   - long AT_USER_ID_ALL = -1L;
34   -
35   - /**
36   - * 消息撤回时间限制(分钟)
37   - */
38   - int MESSAGE_RECALL_TIMEOUT_MINUTES = 5;
39   -
40   - /**
41   - * 私聊离线消息最大拉取天数
42   - * <p>
43   - * 客户端通过 pull 接口增量拉取私聊离线消息时,仅返回最近 N 天内产生的消息,
44   - * 超过该窗口的老消息不再主动推送(可通过历史消息接口按需倒翻)。
45   - */
46   - int MESSAGE_PRIVATE_PULL_MAX_DAYS = 30;
47   -
48   - /**
49   - * 群聊离线消息最大拉取天数
50 12 * <p>
51   - * 客户端通过 pull 接口增量拉取群聊离线消息时,仅返回最近 N 天内产生的消息;
52   - * 退群前消息的补齐也以该窗口为基准(早于窗口的退群群不再扫描),避免老用户首次
53   - * 拉取时对历史退群群做大量查询。
  13 + * 前后端协议契约值;atUserIds 数组里出现该值表示 @ 全体成员
54 14 */
55   - int MESSAGE_GROUP_PULL_MAX_DAYS = 30;
  15 + long AT_USER_ID_ALL = -1L;
56 16  
57 17 }
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/config/ImProperties.java
... ... @@ -7,7 +7,7 @@ import org.springframework.stereotype.Component;
7 7 /**
8 8 * IM 模块全局配置
9 9 * <p>
10   - * 各子模块用嵌套 inner class 区分(friend / 后续 group / sensitive 等),
  10 + * 各子模块用嵌套 inner class 区分(friend / group / message / 后续 sensitive 等),
11 11 * yaml 路径保持 yudao.im.{module}.{key} 与原有部署保持兼容
12 12 *
13 13 * @author 芋道源码
... ... @@ -19,6 +19,10 @@ public class ImProperties {
19 19  
20 20 private Friend friend = new Friend();
21 21  
  22 + private Group group = new Group();
  23 +
  24 + private Message message = new Message();
  25 +
22 26 /**
23 27 * 好友模块配置
24 28 */
... ... @@ -35,4 +39,62 @@ public class ImProperties {
35 39  
36 40 }
37 41  
  42 + /**
  43 + * 群模块配置
  44 + */
  45 + @Data
  46 + public static class Group {
  47 +
  48 + /**
  49 + * 群最大成员人数
  50 + */
  51 + private int maxMember = 500;
  52 +
  53 + /**
  54 + * 单群管理员人数上限
  55 + */
  56 + private int adminMaxCount = 3;
  57 +
  58 + /**
  59 + * 单群置顶消息条数上限
  60 + */
  61 + private int pinMaxCount = 5;
  62 +
  63 + }
  64 +
  65 + /**
  66 + * 消息模块配置
  67 + */
  68 + @Data
  69 + public static class Message {
  70 +
  71 + /**
  72 + * pull 最大拉取数量
  73 + */
  74 + private int maxPullSize = 1000;
  75 +
  76 + /**
  77 + * 消息撤回时间限制(分钟)
  78 + */
  79 + private int recallTimeoutMinutes = 5;
  80 +
  81 + /**
  82 + * 私聊离线消息最大拉取天数
  83 + * <p>
  84 + * 客户端通过 pull 接口增量拉取私聊离线消息时,仅返回最近 N 天内产生的消息,
  85 + * 超过该窗口的老消息不再主动推送(可通过历史消息接口按需倒翻)。
  86 + */
  87 + private int privatePullMaxDays = 30;
  88 +
  89 + /**
  90 + * 群聊离线消息最大拉取天数
  91 + * <p>
  92 + * 客户端通过 pull 接口增量拉取群聊离线消息时,仅返回最近 N 天内产生的消息;
  93 + * 退群前消息的补齐也以该窗口为基准(早于窗口的退群群不再扫描),避免老用户首次
  94 + * 拉取时对历史退群群做大量查询。
  95 + */
  96 + private int groupPullMaxDays = 30;
  97 +
  98 + }
  99 +
38 100 }
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java
... ... @@ -99,8 +99,8 @@ public interface ImGroupService {
99 99 /**
100 100 * 获取指定用户的群列表
101 101 * <p>
102   - * 返回用户当前仍有效的群,以及最近 {@link cn.iocoder.yudao.module.im.enums.ImCommonConstants#MESSAGE_GROUP_PULL_MAX_DAYS}
103   - * 天内退群的群 —— 退群前可能还有离线消息需要展示,前端需要把这些群信息作为缓存。
  102 + * 返回用户当前仍有效的群,以及最近 yudao.im.message.group-pull-max-days 天内退群的群
  103 + * —— 退群前可能还有离线消息需要展示,前端需要把这些群信息作为缓存。
104 104 *
105 105 * @param userId 用户编号
106 106 * @return 群列表
... ... @@ -171,7 +171,7 @@ public interface ImGroupService {
171 171 /**
172 172 * 置顶群消息(仅群主或管理员可执行)
173 173 * <p>
174   - * 上限 {@link cn.iocoder.yudao.module.im.enums.ImCommonConstants#GROUP_PIN_MAX_COUNT};幂等失败时抛业务异常
  174 + * 上限由 yudao.im.group.pin-max-count 控制;幂等失败时抛业务异常
175 175 *
176 176 * @param userId 当前登录用户编号
177 177 * @param groupId 群编号
... ...
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;
28 28 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
29 29 import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper;
30 30 import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum;
  31 +import cn.iocoder.yudao.module.im.framework.config.ImProperties;
31 32 import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum;
32 33 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
33 34 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
... ... @@ -53,7 +54,6 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
53 54 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
54 55 import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP;
55 56 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
56   -import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.*;
57 57  
58 58 /**
59 59 * 用户群 Service 实现类
... ... @@ -83,6 +83,9 @@ public class ImGroupServiceImpl implements ImGroupService {
83 83 @Resource
84 84 private AdminUserApi adminUserApi;
85 85  
  86 + @Resource
  87 + private ImProperties imProperties;
  88 +
86 89 // ==================== 群的写操作 ====================
87 90  
88 91 @Override
... ... @@ -101,9 +104,10 @@ public class ImGroupServiceImpl implements ImGroupService {
101 104 throw exception(GROUP_INVITE_NOT_FRIEND, getUserNicknames(notFriendUserIds));
102 105 }
103 106 }
104   - // 1.3 校验群人数上限(创建者 + 初始成员 ≤ MAX_GROUP_MEMBER)
105   - if (initialMemberUserIds.size() + 1 > MAX_GROUP_MEMBER) {
106   - throw exception(GROUP_MEMBER_EXCEED, MAX_GROUP_MEMBER);
  107 + // 1.3 校验群人数上限(创建者 + 初始成员 ≤ 群成员上限)
  108 + int maxMember = imProperties.getGroup().getMaxMember();
  109 + if (initialMemberUserIds.size() + 1 > maxMember) {
  110 + throw exception(GROUP_MEMBER_EXCEED, maxMember);
107 111 }
108 112  
109 113 // 2.1 插入群记录
... ... @@ -210,8 +214,9 @@ public class ImGroupServiceImpl implements ImGroupService {
210 214 throw exception(GROUP_INVITE_NOT_FRIEND, getUserNicknames(notFriendUserIds));
211 215 }
212 216 // 1.4 校验群人数上限
213   - if (activeMembers.size() + memberUserIds.size() > MAX_GROUP_MEMBER) {
214   - throw exception(GROUP_MEMBER_EXCEED, MAX_GROUP_MEMBER);
  217 + int maxMember = imProperties.getGroup().getMaxMember();
  218 + if (activeMembers.size() + memberUserIds.size() > maxMember) {
  219 + throw exception(GROUP_MEMBER_EXCEED, maxMember);
215 220 }
216 221  
217 222 // 2. 情况一:群开启审批 + 邀请人是普通成员,落 group_request 等群主 / 管理员处理
... ... @@ -304,8 +309,9 @@ public class ImGroupServiceImpl implements ImGroupService {
304 309 // 1.4 校验上限
305 310 Long existAdminCount = groupMemberService.getGroupMemberCountByRole(
306 311 groupId, ImGroupMemberRoleEnum.ADMIN.getRole());
307   - if (existAdminCount + changedUserIds.size() > GROUP_ADMIN_MAX_COUNT) {
308   - throw exception(GROUP_ADMIN_MAX_LIMIT, GROUP_ADMIN_MAX_COUNT);
  312 + int adminMaxCount = imProperties.getGroup().getAdminMaxCount();
  313 + if (existAdminCount + changedUserIds.size() > adminMaxCount) {
  314 + throw exception(GROUP_ADMIN_MAX_LIMIT, adminMaxCount);
309 315 }
310 316  
311 317 // 2. 批量更新角色
... ... @@ -404,8 +410,9 @@ public class ImGroupServiceImpl implements ImGroupService {
404 410 if (pinned.contains(messageId)) {
405 411 throw exception(GROUP_MESSAGE_ALREADY_PINNED);
406 412 }
407   - if (pinned.size() >= GROUP_PIN_MAX_COUNT) {
408   - throw exception(GROUP_MESSAGE_PIN_MAX_LIMIT, GROUP_PIN_MAX_COUNT);
  413 + int pinMaxCount = imProperties.getGroup().getPinMaxCount();
  414 + if (pinned.size() >= pinMaxCount) {
  415 + throw exception(GROUP_MESSAGE_PIN_MAX_LIMIT, pinMaxCount);
409 416 }
410 417 pinned.add(messageId);
411 418 groupMapper.updateById(new ImGroupDO().setId(groupId).setPinnedMessageIds(pinned));
... ... @@ -465,8 +472,9 @@ public class ImGroupServiceImpl implements ImGroupService {
465 472 @Override
466 473 public void validateMemberCountLimit(Long groupId, int addCount) {
467 474 int activeCount = groupMemberService.getActiveGroupMemberUserIdsByGroupId(groupId).size();
468   - if (activeCount + addCount > MAX_GROUP_MEMBER) {
469   - throw exception(GROUP_MEMBER_EXCEED, MAX_GROUP_MEMBER);
  475 + int maxMember = imProperties.getGroup().getMaxMember();
  476 + if (activeCount + addCount > maxMember) {
  477 + throw exception(GROUP_MEMBER_EXCEED, maxMember);
470 478 }
471 479 }
472 480  
... ... @@ -512,8 +520,8 @@ public class ImGroupServiceImpl implements ImGroupService {
512 520 public List<ImGroupDO> getMyGroupList(Long userId) {
513 521 // 1.1 查用户所在的、仍有效的群成员记录(仅 ENABLE 状态)
514 522 List<ImGroupMemberDO> members = groupMemberService.getActiveGroupMemberListByUserId(userId);
515   - // 1.2 再查最近 MESSAGE_GROUP_PULL_MAX_DAYS 天内退群的成员记录(退群前可能有离线消息需要展示,一并返回作为前端缓存)
516   - LocalDateTime minQuitTime = LocalDateTime.now().minusDays(MESSAGE_GROUP_PULL_MAX_DAYS);
  523 + // 1.2 再查最近 N 天(与群消息离线拉取窗口一致)内退群的成员记录(退群前可能有离线消息需要展示,一并返回作为前端缓存)
  524 + LocalDateTime minQuitTime = LocalDateTime.now().minusDays(imProperties.getMessage().getGroupPullMaxDays());
517 525 members.addAll(groupMemberService.getQuitGroupMemberListByUserId(userId, minQuitTime));
518 526 if (CollUtil.isEmpty(members)) {
519 527 return Collections.emptyList();
... ...
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;
21 21 import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum;
22 22 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
23 23 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
  24 +import cn.iocoder.yudao.module.im.framework.config.ImProperties;
24 25 import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService;
25 26 import cn.iocoder.yudao.module.im.service.group.ImGroupService;
26 27 import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO;
... ... @@ -43,7 +44,6 @@ import java.util.*;
43 44 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
44 45 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
45 46 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
46   -import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.*;
47 47  
48 48 /**
49 49 * IM 群聊消息 Service 实现类
... ... @@ -77,6 +77,9 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
77 77 @Resource
78 78 private ImWebSocketService imWebSocketService;
79 79  
  80 + @Resource
  81 + private ImProperties imProperties;
  82 +
80 83 @Override
81 84 public ImGroupMessageDO sendGroupMessage(Long senderId, ImGroupMessageSendReqVO reqVO) {
82 85 // 1.1 幂等校验:根据 senderId + clientMessageId 查重
... ... @@ -161,8 +164,9 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
161 164 throw exception(MESSAGE_ALREADY_RECALLED);
162 165 }
163 166 // 1.4 只允许撤回限定时间内的消息
164   - if (message.getSendTime().plusMinutes(MESSAGE_RECALL_TIMEOUT_MINUTES).isBefore(LocalDateTime.now())) {
165   - throw exception(MESSAGE_RECALL_TIMEOUT, MESSAGE_RECALL_TIMEOUT_MINUTES);
  167 + int recallTimeoutMinutes = imProperties.getMessage().getRecallTimeoutMinutes();
  168 + if (message.getSendTime().plusMinutes(recallTimeoutMinutes).isBefore(LocalDateTime.now())) {
  169 + throw exception(MESSAGE_RECALL_TIMEOUT, recallTimeoutMinutes);
166 170 }
167 171 // 1.5 校验撤回人仍在群中
168 172 groupMemberService.validateMemberInGroup(message.getGroupId(), userId);
... ... @@ -178,12 +182,13 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
178 182  
179 183 @Override
180 184 public List<ImGroupMessageDO> pullGroupMessageList(Long userId, Long minId, Integer size) {
181   - if (size > MESSAGE_MAX_PULL_SIZE) {
182   - throw exception(MESSAGE_PULL_SIZE_EXCEEDED, MESSAGE_MAX_PULL_SIZE);
  185 + int maxPullSize = imProperties.getMessage().getMaxPullSize();
  186 + if (size > maxPullSize) {
  187 + throw exception(MESSAGE_PULL_SIZE_EXCEEDED, maxPullSize);
183 188 }
184 189  
185   - // 0. 拉取时间窗:超过窗口的老消息不再通过离线通道推送
186   - LocalDateTime minSendTime = LocalDateTime.now().minusDays(MESSAGE_GROUP_PULL_MAX_DAYS);
  190 + // 0. 拉取时间窗;超过窗口的老消息不再通过离线通道推送
  191 + LocalDateTime minSendTime = LocalDateTime.now().minusDays(imProperties.getMessage().getGroupPullMaxDays());
187 192  
188 193 // 1.1 主查询:仅用"当前仍在群"的成员记录驱动;若首批消息过滤后为空,则允许内部重试
189 194 List<ImGroupMemberDO> activeMembers = groupMemberService.getActiveGroupMemberListByUserId(userId);
... ...
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;
14 14 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
15 15 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
16 16 import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum;
  17 +import cn.iocoder.yudao.module.im.framework.config.ImProperties;
17 18 import cn.iocoder.yudao.module.im.service.friend.ImFriendService;
18 19 import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO;
19 20 import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService;
... ... @@ -33,7 +34,6 @@ import java.util.List;
33 34  
34 35 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
35 36 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
36   -import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.*;
37 37  
38 38 /**
39 39 * IM 私聊消息 Service 实现类
... ... @@ -56,6 +56,9 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
56 56 @Resource
57 57 private ImWebSocketService imWebSocketService;
58 58  
  59 + @Resource
  60 + private ImProperties imProperties;
  61 +
59 62 @Override
60 63 public ImPrivateMessageDO sendPrivateMessage(Long senderId, ImPrivateMessageSendReqVO reqVO) {
61 64 // 1.1 幂等校验:根据 senderId + clientMessageId 查重
... ... @@ -139,8 +142,9 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
139 142 throw exception(MESSAGE_ALREADY_RECALLED);
140 143 }
141 144 // 1.4 只允许撤回限定时间内的消息
142   - if (message.getSendTime().plusMinutes(MESSAGE_RECALL_TIMEOUT_MINUTES).isBefore(LocalDateTime.now())) {
143   - throw exception(MESSAGE_RECALL_TIMEOUT, MESSAGE_RECALL_TIMEOUT_MINUTES);
  145 + int recallTimeoutMinutes = imProperties.getMessage().getRecallTimeoutMinutes();
  146 + if (message.getSendTime().plusMinutes(recallTimeoutMinutes).isBefore(LocalDateTime.now())) {
  147 + throw exception(MESSAGE_RECALL_TIMEOUT, recallTimeoutMinutes);
144 148 }
145 149  
146 150 // 2. 更新原消息状态为撤回
... ... @@ -190,11 +194,12 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
190 194  
191 195 @Override
192 196 public List<ImPrivateMessageDO> pullPrivateMessageList(Long userId, Long minId, Integer size) {
193   - if (size > MESSAGE_MAX_PULL_SIZE) {
194   - throw exception(MESSAGE_PULL_SIZE_EXCEEDED, MESSAGE_MAX_PULL_SIZE);
  197 + int maxPullSize = imProperties.getMessage().getMaxPullSize();
  198 + if (size > maxPullSize) {
  199 + throw exception(MESSAGE_PULL_SIZE_EXCEEDED, maxPullSize);
195 200 }
196   - // 0. 拉取时间窗:超过窗口的老消息不再通过离线通道推送
197   - LocalDateTime minSendTime = LocalDateTime.now().minusDays(MESSAGE_PRIVATE_PULL_MAX_DAYS);
  201 + // 0. 拉取时间窗;超过窗口的老消息不再通过离线通道推送
  202 + LocalDateTime minSendTime = LocalDateTime.now().minusDays(imProperties.getMessage().getPrivatePullMaxDays());
198 203  
199 204 // 根据 minId 和 minSendTime 拉取消息,避免 minId 恰好被发出后才拉取,导致漏消息
200 205 List<ImPrivateMessageDO> messages = privateMessageMapper.selectListByMinId(userId, minId, minSendTime, size);
... ...