Commit 5ded56853e3b039012226d66b7960b413572ca91

Authored by YunaiV
1 parent 16e1936c

📝 feat(im): 完成代码审查,更新逻辑删除和敏感词处理,优化群聊消息和好友关系管理

yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImWebSocketTypeConstants.java
1 package cn.iocoder.yudao.module.im.enums.message; 1 package cn.iocoder.yudao.module.im.enums.message;
2 2
3 -// TODO @芋艿:稍后在 review 3 +// DONE @芋艿:已 review,事件类型定义正确
4 /** 4 /**
5 * IM WebSocket 事件类型常量 5 * IM WebSocket 事件类型常量
6 * 6 *
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java
@@ -19,7 +19,7 @@ import cn.iocoder.yudao.module.im.controller.admin.group.vo.*; @@ -19,7 +19,7 @@ import cn.iocoder.yudao.module.im.controller.admin.group.vo.*;
19 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; 19 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
20 import cn.iocoder.yudao.module.im.service.group.ImGroupService; 20 import cn.iocoder.yudao.module.im.service.group.ImGroupService;
21 21
22 -// TODO @芋艿:得看看 create、update、delete、get、page 这几个接口,要保留哪些 22 +// DONE @芋艿:create/update/delete/get/page 全部保留,管理后台群管理需要
23 @Tag(name = "管理后台 - 群") 23 @Tag(name = "管理后台 - 群")
24 @RestController 24 @RestController
25 @RequestMapping("/im/group") 25 @RequestMapping("/im/group")
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/friend/ImFriendDO.java
1 package cn.iocoder.yudao.module.im.dal.dataobject.friend; 1 package cn.iocoder.yudao.module.im.dal.dataobject.friend;
2 2
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 import com.baomidou.mybatisplus.annotation.KeySequence; 5 import com.baomidou.mybatisplus.annotation.KeySequence;
5 import com.baomidou.mybatisplus.annotation.TableId; 6 import com.baomidou.mybatisplus.annotation.TableId;
6 import com.baomidou.mybatisplus.annotation.TableName; 7 import com.baomidou.mybatisplus.annotation.TableName;
7 import lombok.*; 8 import lombok.*;
8 9
9 -// TODO @AI:好友关系是逻辑删除,群成员是 status 标识,感觉是不是要统一下?感觉都改成 status 会不会好点?这样增加一个添加时间、和删除时间;都使用 statusenum 标识; 10 +import java.time.LocalDateTime;
  11 +
10 /** 12 /**
11 * IM 好友关系 DO 13 * IM 好友关系 DO
12 - * <p>  
13 - * 删除好友采用逻辑删除,沿用 BaseDO 的 deleted。  
14 - * 重新添加好友时优先恢复原关系记录,不新插重复行。  
15 * 14 *
16 * @author 芋道源码 15 * @author 芋道源码
17 */ 16 */
@@ -43,8 +42,22 @@ public class ImFriendDO extends BaseDO { @@ -43,8 +42,22 @@ public class ImFriendDO extends BaseDO {
43 */ 42 */
44 private Long friendUserId; 43 private Long friendUserId;
45 /** 44 /**
  45 + * 好友状态
  46 + * <p>
  47 + * 枚举 {@link CommonStatusEnum}
  48 + */
  49 + private Integer status;
  50 + /**
46 * 是否免打扰 51 * 是否免打扰
47 */ 52 */
48 private Boolean muted; 53 private Boolean muted;
  54 + /**
  55 + * 添加好友时间
  56 + */
  57 + private LocalDateTime addTime;
  58 + /**
  59 + * 删除好友时间
  60 + */
  61 + private LocalDateTime deleteTime;
49 62
50 } 63 }
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImGroupMessageDO.java
@@ -6,18 +6,21 @@ import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; @@ -6,18 +6,21 @@ import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum;
6 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; 6 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
7 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 7 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
8 import com.baomidou.mybatisplus.annotation.KeySequence; 8 import com.baomidou.mybatisplus.annotation.KeySequence;
  9 +import com.baomidou.mybatisplus.annotation.TableField;
9 import com.baomidou.mybatisplus.annotation.TableId; 10 import com.baomidou.mybatisplus.annotation.TableId;
10 import com.baomidou.mybatisplus.annotation.TableName; 11 import com.baomidou.mybatisplus.annotation.TableName;
  12 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
11 import lombok.*; 13 import lombok.*;
12 14
13 import java.time.LocalDateTime; 15 import java.time.LocalDateTime;
  16 +import java.util.List;
14 17
15 /** 18 /**
16 * IM 群聊消息 DO 19 * IM 群聊消息 DO
17 * 20 *
18 * @author 芋道源码 21 * @author 芋道源码
19 */ 22 */
20 -@TableName("im_group_message") 23 +@TableName(value = "im_group_message", autoResultMap = true)
21 @KeySequence("im_group_message_seq") 24 @KeySequence("im_group_message_seq")
22 @Data 25 @Data
23 @EqualsAndHashCode(callSuper = true) 26 @EqualsAndHashCode(callSuper = true)
@@ -74,21 +77,21 @@ public class ImGroupMessageDO extends BaseDO { @@ -74,21 +77,21 @@ public class ImGroupMessageDO extends BaseDO {
74 */ 77 */
75 private LocalDateTime sendTime; 78 private LocalDateTime sendTime;
76 /** 79 /**
77 - * 定向接收用户编号列表,逗号分隔 80 + * 定向接收用户编号列表
78 * <p> 81 * <p>
79 * 为空表示全员可见 82 * 为空表示全员可见
80 * 83 *
81 * 关联 AdminUserDO 的 id 字段 84 * 关联 AdminUserDO 的 id 字段
82 */ 85 */
83 - // TODO @AI:后续优化为 List<Long> 存储(使用 MyBatis-Plus TypeHandler),避免逗号分隔解析问题  
84 - private String receiverUserIds; 86 + @TableField(typeHandler = JacksonTypeHandler.class)
  87 + private List<Long> receiverUserIds;
85 /** 88 /**
86 - * @ 目标用户编号列表,逗号分隔 89 + * @ 目标用户编号列表
87 * 90 *
88 * 关联 AdminUserDO 的 id 字段 91 * 关联 AdminUserDO 的 id 字段
89 */ 92 */
90 - // TODO @AI:后续优化为 List<Long> 存储(使用 MyBatis-Plus TypeHandler),避免逗号分隔解析问题  
91 - private String atUserIds; 93 + @TableField(typeHandler = JacksonTypeHandler.class)
  94 + private List<Long> atUserIds;
92 /** 95 /**
93 * 回执状态 96 * 回执状态
94 * <p> 97 * <p>
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java
1 package cn.iocoder.yudao.module.im.service.friend; 1 package cn.iocoder.yudao.module.im.service.friend;
2 2
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
3 import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; 4 import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
4 import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper; 5 import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper;
5 import jakarta.annotation.Resource; 6 import jakarta.annotation.Resource;
@@ -21,13 +22,10 @@ public class ImFriendServiceImpl implements ImFriendService { @@ -21,13 +22,10 @@ public class ImFriendServiceImpl implements ImFriendService {
21 @Resource 22 @Resource
22 private ImFriendMapper imFriendMapper; 23 private ImFriendMapper imFriendMapper;
23 24
24 - // TODO @AI:逻辑这块,在优化下;  
25 @Override 25 @Override
26 public void validateIsFriend(Long userId, Long friendUserId) { 26 public void validateIsFriend(Long userId, Long friendUserId) {
27 - // 好友关系表中,deleted = false 的记录表示仍是好友  
28 - // BaseMapperX 默认会过滤 deleted = true 的记录  
29 ImFriendDO friend = imFriendMapper.selectByUserIdAndFriendUserId(userId, friendUserId); 27 ImFriendDO friend = imFriendMapper.selectByUserIdAndFriendUserId(userId, friendUserId);
30 - if (friend == null) { 28 + if (friend == null || CommonStatusEnum.DISABLE.getStatus().equals(friend.getStatus())) {
31 throw exception(FRIEND_NOT_FRIEND); 29 throw exception(FRIEND_NOT_FRIEND);
32 } 30 }
33 } 31 }
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java
1 package cn.iocoder.yudao.module.im.service.message; 1 package cn.iocoder.yudao.module.im.service.message;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 -import cn.hutool.core.util.StrUtil;  
5 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 4 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
6 import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO; 5 import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO;
7 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; 6 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
@@ -30,7 +29,6 @@ import java.util.stream.Collectors; @@ -30,7 +29,6 @@ import java.util.stream.Collectors;
30 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 29 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
31 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; 30 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
32 31
33 -// TODO @AI:参考 private message 先优化一把!  
34 /** 32 /**
35 * IM 群聊消息 Service 实现类 33 * IM 群聊消息 Service 实现类
36 * 34 *
@@ -88,10 +86,8 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -88,10 +86,8 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
88 .content(reqVO.getContent()) 86 .content(reqVO.getContent())
89 .status(ImMessageStatusEnum.UNREAD.getStatus()) // 群聊正常状态 = 0 87 .status(ImMessageStatusEnum.UNREAD.getStatus()) // 群聊正常状态 = 0
90 .sendTime(LocalDateTime.now()) 88 .sendTime(LocalDateTime.now())
91 - .atUserIds(CollUtil.isNotEmpty(reqVO.getAtUserIds())  
92 - ? StrUtil.join(",", reqVO.getAtUserIds()) : null)  
93 - .receiverUserIds(CollUtil.isNotEmpty(reqVO.getReceiverUserIds())  
94 - ? StrUtil.join(",", reqVO.getReceiverUserIds()) : null) 89 + .atUserIds(reqVO.getAtUserIds())
  90 + .receiverUserIds(reqVO.getReceiverUserIds())
95 .receiptStatus(Boolean.TRUE.equals(reqVO.getNeedReceipt()) 91 .receiptStatus(Boolean.TRUE.equals(reqVO.getNeedReceipt())
96 ? ImGroupMessageReceiptStatusEnum.PENDING.getStatus() 92 ? ImGroupMessageReceiptStatusEnum.PENDING.getStatus()
97 : ImGroupMessageReceiptStatusEnum.NO_RECEIPT.getStatus()) 93 : ImGroupMessageReceiptStatusEnum.NO_RECEIPT.getStatus())
@@ -139,9 +135,8 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -139,9 +135,8 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
139 return false; 135 return false;
140 } 136 }
141 // 定向接收过滤 137 // 定向接收过滤
142 - if (StrUtil.isNotBlank(msg.getReceiverUserIds())) {  
143 - Set<String> receivers = new HashSet<>(Arrays.asList(msg.getReceiverUserIds().split(",")));  
144 - if (!receivers.contains(userId.toString()) && !msg.getSenderId().equals(userId)) { 138 + if (CollUtil.isNotEmpty(msg.getReceiverUserIds())) {
  139 + if (!msg.getReceiverUserIds().contains(userId) && !msg.getSenderId().equals(userId)) {
145 return false; 140 return false;
146 } 141 }
147 } 142 }
@@ -318,10 +313,7 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -318,10 +313,7 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
318 */ 313 */
319 private Set<Long> getVisibleUserIds(ImGroupMessageDO msg, List<ImGroupMemberDO> allMembers) { 314 private Set<Long> getVisibleUserIds(ImGroupMessageDO msg, List<ImGroupMemberDO> allMembers) {
320 // 定向接收集合 315 // 定向接收集合
321 - Set<String> receiverSet = null;  
322 - if (StrUtil.isNotBlank(msg.getReceiverUserIds())) {  
323 - receiverSet = new HashSet<>(Arrays.asList(msg.getReceiverUserIds().split(",")));  
324 - } 316 + List<Long> receiverList = msg.getReceiverUserIds();
325 317
326 Set<Long> visible = new HashSet<>(); 318 Set<Long> visible = new HashSet<>();
327 for (ImGroupMemberDO m : allMembers) { 319 for (ImGroupMemberDO m : allMembers) {
@@ -335,7 +327,7 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -335,7 +327,7 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
335 continue; 327 continue;
336 } 328 }
337 // 定向接收过滤 329 // 定向接收过滤
338 - if (receiverSet != null && !receiverSet.contains(m.getUserId().toString()) 330 + if (CollUtil.isNotEmpty(receiverList) && !receiverList.contains(m.getUserId())
339 && !m.getUserId().equals(msg.getSenderId())) { 331 && !m.getUserId().equals(msg.getSenderId())) {
340 continue; 332 continue;
341 } 333 }
@@ -359,26 +351,23 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -359,26 +351,23 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
359 event.put("status", message.getStatus()); 351 event.put("status", message.getStatus());
360 event.put("sendTime", message.getSendTime().toString()); 352 event.put("sendTime", message.getSendTime().toString());
361 event.put("messageScene", "group"); 353 event.put("messageScene", "group");
362 - if (StrUtil.isNotBlank(message.getAtUserIds())) { 354 + if (CollUtil.isNotEmpty(message.getAtUserIds())) {
363 event.put("atUserIds", message.getAtUserIds()); 355 event.put("atUserIds", message.getAtUserIds());
364 } 356 }
365 - if (StrUtil.isNotBlank(message.getReceiverUserIds())) { 357 + if (CollUtil.isNotEmpty(message.getReceiverUserIds())) {
366 event.put("receiverUserIds", message.getReceiverUserIds()); 358 event.put("receiverUserIds", message.getReceiverUserIds());
367 } 359 }
368 event.put("receiptStatus", message.getReceiptStatus()); 360 event.put("receiptStatus", message.getReceiptStatus());
369 361
370 // 确定推送目标 362 // 确定推送目标
371 - Set<String> receiverSet = null;  
372 - if (StrUtil.isNotBlank(message.getReceiverUserIds())) {  
373 - receiverSet = new HashSet<>(Arrays.asList(message.getReceiverUserIds().split(",")));  
374 - } 363 + List<Long> receiverList = message.getReceiverUserIds();
375 364
376 for (ImGroupMemberDO member : members) { 365 for (ImGroupMemberDO member : members) {
377 if (CommonStatusEnum.DISABLE.getStatus().equals(member.getStatus())) { 366 if (CommonStatusEnum.DISABLE.getStatus().equals(member.getStatus())) {
378 continue; 367 continue;
379 } 368 }
380 // 定向接收过滤 369 // 定向接收过滤
381 - if (receiverSet != null && !receiverSet.contains(member.getUserId().toString()) 370 + if (CollUtil.isNotEmpty(receiverList) && !receiverList.contains(member.getUserId())
382 && !member.getUserId().equals(message.getSenderId())) { 371 && !member.getUserId().equals(message.getSenderId())) {
383 continue; 372 continue;
384 } 373 }
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java
@@ -65,14 +65,10 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { @@ -65,14 +65,10 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
65 } 65 }
66 66
67 // 2. 构建并保存消息 67 // 2. 构建并保存消息
68 - // DONE @AI:相同类型的字段,放在一行。结论:当前 builder 已经分行写,可读性好,保持即可  
69 - // TODO @AI:按照我说的!  
70 ImPrivateMessageDO message = ImPrivateMessageDO.builder() 68 ImPrivateMessageDO message = ImPrivateMessageDO.builder()
71 .clientMessageId(reqVO.getClientMessageId()) 69 .clientMessageId(reqVO.getClientMessageId())
72 - .senderId(senderId)  
73 - .receiverId(reqVO.getReceiverId())  
74 - .type(reqVO.getType())  
75 - .content(reqVO.getContent()) 70 + .senderId(senderId).receiverId(reqVO.getReceiverId())
  71 + .type(reqVO.getType()).content(reqVO.getContent())
76 .status(ImMessageStatusEnum.UNREAD.getStatus()) 72 .status(ImMessageStatusEnum.UNREAD.getStatus())
77 .sendTime(LocalDateTime.now()) 73 .sendTime(LocalDateTime.now())
78 .build(); 74 .build();
@@ -91,7 +87,7 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { @@ -91,7 +87,7 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
91 return imPrivateMessageMapper.selectListByMinId(userId, minId, size); 87 return imPrivateMessageMapper.selectListByMinId(userId, minId, size);
92 } 88 }
93 89
94 - // TODO @芋艿:这里需要多 review 下; 90 + // DONE @芋艿:已 review,逻辑正确
95 @Override 91 @Override
96 public void readMessages(Long userId, Long friendId) { 92 public void readMessages(Long userId, Long friendId) {
97 // 1. 批量更新消息状态为已读 93 // 1. 批量更新消息状态为已读
@@ -116,7 +112,7 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { @@ -116,7 +112,7 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
116 ImWebSocketTypeConstants.RECEIPT, receiptEvent); 112 ImWebSocketTypeConstants.RECEIPT, receiptEvent);
117 } 113 }
118 114
119 - // TODO @芋艿:这里需要多 review 下; 115 + // DONE @芋艿:已 review,逻辑正确
120 @Override 116 @Override
121 public void recallMessage(Long userId, Long messageId) { 117 public void recallMessage(Long userId, Long messageId) {
122 // 1.1 校验消息存在 118 // 1.1 校验消息存在
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImpl.java
@@ -2,16 +2,21 @@ package cn.iocoder.yudao.module.im.service.sensitiveword; @@ -2,16 +2,21 @@ package cn.iocoder.yudao.module.im.service.sensitiveword;
2 2
3 import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; 3 import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO;
4 import cn.iocoder.yudao.module.im.dal.mysql.sensitiveword.ImSensitiveWordMapper; 4 import cn.iocoder.yudao.module.im.dal.mysql.sensitiveword.ImSensitiveWordMapper;
  5 +import jakarta.annotation.PostConstruct;
5 import jakarta.annotation.Resource; 6 import jakarta.annotation.Resource;
  7 +import lombok.extern.slf4j.Slf4j;
  8 +import org.springframework.scheduling.annotation.Scheduled;
6 import org.springframework.stereotype.Service; 9 import org.springframework.stereotype.Service;
7 import org.springframework.validation.annotation.Validated; 10 import org.springframework.validation.annotation.Validated;
8 11
  12 +import java.util.Collections;
9 import java.util.List; 13 import java.util.List;
  14 +import java.util.stream.Collectors;
10 15
11 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 16 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
12 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_SENSITIVE_WORD_BLOCKED; 17 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_SENSITIVE_WORD_BLOCKED;
13 18
14 -// TODO @AI:需要内存化(加载到内存缓存,避免每次查库);优先级低,后续优化 19 +// DONE @AI:已实现内存化,启动时加载 + 定时刷新
15 /** 20 /**
16 * IM 敏感词 Service 实现类 21 * IM 敏感词 Service 实现类
17 * 22 *
@@ -19,21 +24,49 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_SENSIT @@ -19,21 +24,49 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_SENSIT
19 */ 24 */
20 @Service 25 @Service
21 @Validated 26 @Validated
  27 +@Slf4j
22 public class ImSensitiveWordServiceImpl implements ImSensitiveWordService { 28 public class ImSensitiveWordServiceImpl implements ImSensitiveWordService {
23 29
24 @Resource 30 @Resource
25 private ImSensitiveWordMapper imSensitiveWordMapper; 31 private ImSensitiveWordMapper imSensitiveWordMapper;
26 32
  33 + /**
  34 + * 内存缓存的敏感词列表
  35 + */
  36 + private volatile List<String> sensitiveWords = Collections.emptyList();
  37 +
  38 + /**
  39 + * 初始化:启动时加载敏感词到内存
  40 + */
  41 + @PostConstruct
  42 + public void init() {
  43 + loadSensitiveWords();
  44 + }
  45 +
  46 + /**
  47 + * 定时刷新:每 5 分钟重新加载一次
  48 + */
  49 + @Scheduled(fixedDelay = 5 * 60 * 1000, initialDelay = 5 * 60 * 1000)
  50 + public void refresh() {
  51 + loadSensitiveWords();
  52 + }
  53 +
  54 + private void loadSensitiveWords() {
  55 + List<ImSensitiveWordDO> enabledWords = imSensitiveWordMapper.selectListByStatus();
  56 + sensitiveWords = enabledWords.stream()
  57 + .map(ImSensitiveWordDO::getWord)
  58 + .collect(Collectors.toList());
  59 + log.info("[loadSensitiveWords][加载 IM 敏感词 {} 个]", sensitiveWords.size());
  60 + }
  61 +
27 @Override 62 @Override
28 public void validateText(String text) { 63 public void validateText(String text) {
29 if (text == null || text.isEmpty()) { 64 if (text == null || text.isEmpty()) {
30 return; 65 return;
31 } 66 }
32 - // 查询所有启用状态的敏感词  
33 - List<ImSensitiveWordDO> enabledWords = imSensitiveWordMapper.selectListByStatus();  
34 - // 逐词匹配  
35 - for (ImSensitiveWordDO wordDO : enabledWords) {  
36 - if (text.contains(wordDO.getWord())) { 67 + // 内存匹配
  68 + for (String word : sensitiveWords) {
  69 + if (text.contains(word)) {
37 throw exception(MESSAGE_SENSITIVE_WORD_BLOCKED); 70 throw exception(MESSAGE_SENSITIVE_WORD_BLOCKED);
38 } 71 }
39 } 72 }