Commit 27e70e73a3a1f0f7e43fc8bff55b4ca6353c6321

Authored by YunaiV
1 parent 5e7e3d81

敏感词的本地缓存,使用 Job 轮询,替代 MQ 广播

yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sensitiveword/SensitiveWordMapper.java
... ... @@ -7,7 +7,9 @@ import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.Sensitiv
7 7 import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordPageReqVO;
8 8 import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO;
9 9 import org.apache.ibatis.annotations.Mapper;
  10 +import org.apache.ibatis.annotations.Select;
10 11  
  12 +import java.time.LocalDateTime;
11 13 import java.util.List;
12 14  
13 15 /**
... ... @@ -40,4 +42,7 @@ public interface SensitiveWordMapper extends BaseMapperX<SensitiveWordDO> {
40 42 return selectOne(SensitiveWordDO::getName, name);
41 43 }
42 44  
  45 + @Select("SELECT COUNT(*) FROM system_sensitive_word WHERE update_time > #{maxUpdateTime}")
  46 + Long selectCountByUpdateTimeGt(LocalDateTime maxTime);
  47 +
43 48 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/sensitiveword/SensitiveWordRefreshConsumer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.consumer.sensitiveword;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.yudao.module.system.mq.message.sensitiveword.SensitiveWordRefreshMessage;
5   -import cn.iocoder.yudao.module.system.service.sensitiveword.SensitiveWordService;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.stereotype.Component;
8   -
9   -import javax.annotation.Resource;
10   -
11   -/**
12   - * 针对 {@link SensitiveWordRefreshMessage} 的消费者
13   - *
14   - * @author 芋道源码
15   - */
16   -@Component
17   -@Slf4j
18   -public class SensitiveWordRefreshConsumer extends AbstractChannelMessageListener<SensitiveWordRefreshMessage> {
19   -
20   - @Resource
21   - private SensitiveWordService sensitiveWordService;
22   -
23   - @Override
24   - public void onMessage(SensitiveWordRefreshMessage message) {
25   - log.info("[onMessage][收到 SensitiveWord 刷新消息]");
26   - sensitiveWordService.initLocalCache();
27   - }
28   -
29   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/sensitiveword/SensitiveWordRefreshMessage.java deleted
1   -package cn.iocoder.yudao.module.system.mq.message.sensitiveword;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
4   -import lombok.Data;
5   -import lombok.EqualsAndHashCode;
6   -
7   -/**
8   - * 敏感词的刷新 Message
9   - */
10   -@Data
11   -@EqualsAndHashCode(callSuper = true)
12   -public class SensitiveWordRefreshMessage extends AbstractChannelMessage {
13   -
14   - @Override
15   - public String getChannel() {
16   - return "system.sensitive-word.refresh";
17   - }
18   -
19   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/package-info.java deleted
1   -/**
2   - * 占位
3   - */
4   -package cn.iocoder.yudao.module.system.mq.producer;
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/sensitiveword/SensitiveWordProducer.java deleted
1   -package cn.iocoder.yudao.module.system.mq.producer.sensitiveword;
2   -
3   -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
4   -import cn.iocoder.yudao.module.system.mq.message.sensitiveword.SensitiveWordRefreshMessage;
5   -import org.springframework.stereotype.Component;
6   -
7   -import javax.annotation.Resource;
8   -
9   -/**
10   - * 敏感词相关的 Producer
11   - */
12   -@Component
13   -public class SensitiveWordProducer {
14   -
15   - @Resource
16   - private RedisMQTemplate redisMQTemplate;
17   -
18   - /**
19   - * 发送 {@link SensitiveWordRefreshMessage} 消息
20   - */
21   - public void sendSensitiveWordRefreshMessage() {
22   - SensitiveWordRefreshMessage message = new SensitiveWordRefreshMessage();
23   - redisMQTemplate.send(message);
24   - }
25   -
26   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordService.java
... ... @@ -19,11 +19,6 @@ import java.util.Set;
19 19 public interface SensitiveWordService {
20 20  
21 21 /**
22   - * 初始化本地缓存
23   - */
24   - void initLocalCache();
25   -
26   - /**
27 22 * 创建敏感词
28 23 *
29 24 * @param createReqVO 创建信息
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImpl.java
... ... @@ -11,21 +11,24 @@ import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.Sensitiv
11 11 import cn.iocoder.yudao.module.system.convert.sensitiveword.SensitiveWordConvert;
12 12 import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO;
13 13 import cn.iocoder.yudao.module.system.dal.mysql.sensitiveword.SensitiveWordMapper;
14   -import cn.iocoder.yudao.module.system.mq.producer.sensitiveword.SensitiveWordProducer;
15 14 import cn.iocoder.yudao.module.system.util.collection.SimpleTrie;
16 15 import com.google.common.collect.HashMultimap;
17 16 import com.google.common.collect.Multimap;
18 17 import lombok.Getter;
19 18 import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.scheduling.annotation.Scheduled;
20 20 import org.springframework.stereotype.Service;
21 21 import org.springframework.validation.annotation.Validated;
22 22  
23 23 import javax.annotation.PostConstruct;
24 24 import javax.annotation.Resource;
  25 +import java.time.LocalDateTime;
25 26 import java.util.*;
  27 +import java.util.concurrent.TimeUnit;
26 28  
27 29 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
28 30 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
  31 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
29 32 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_WORD_EXISTS;
30 33 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_WORD_NOT_EXISTS;
31 34  
... ... @@ -40,6 +43,11 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_
40 43 public class SensitiveWordServiceImpl implements SensitiveWordService {
41 44  
42 45 /**
  46 + * 敏感词列表缓存
  47 + */
  48 + @Getter
  49 + private volatile List<SensitiveWordDO> sensitiveWordCache = Collections.emptyList();
  50 + /**
43 51 * 敏感词标签缓存
44 52 * key:敏感词编号 {@link SensitiveWordDO#getId()}
45 53 * <p>
... ... @@ -51,9 +59,6 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
51 59 @Resource
52 60 private SensitiveWordMapper sensitiveWordMapper;
53 61  
54   - @Resource
55   - private SensitiveWordProducer sensitiveWordProducer;
56   -
57 62 /**
58 63 * 默认的敏感词的字典树,包含所有敏感词
59 64 */
... ... @@ -68,7 +73,6 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
68 73 /**
69 74 * 初始化缓存
70 75 */
71   - @Override
72 76 @PostConstruct
73 77 public void initLocalCache() {
74 78 // 第一步:查询数据
... ... @@ -80,6 +84,7 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
80 84 Set<String> tags = new HashSet<>();
81 85 sensitiveWords.forEach(word -> tags.addAll(word.getTags()));
82 86 sensitiveWordTagsCache = tags;
  87 + sensitiveWordCache = sensitiveWords;
83 88 // 写入 defaultSensitiveWordTrie、tagSensitiveWordTries 缓存
84 89 initSensitiveWordTrie(sensitiveWords);
85 90 }
... ... @@ -105,6 +110,26 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
105 110 this.tagSensitiveWordTries = tagSensitiveWordTries;
106 111 }
107 112  
  113 + /**
  114 + * 通过定时任务轮询,刷新缓存
  115 + *
  116 + * 目的:多节点部署时,通过轮询”通知“所有节点,进行刷新
  117 + */
  118 + @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
  119 + public void refreshLocalCache() {
  120 + // 情况一:如果缓存里没有数据,则直接刷新缓存
  121 + if (CollUtil.isEmpty(sensitiveWordCache)) {
  122 + initLocalCache();
  123 + return;
  124 + }
  125 +
  126 + // 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存
  127 + LocalDateTime maxTime = getMaxValue(sensitiveWordCache, SensitiveWordDO::getUpdateTime);
  128 + if (sensitiveWordMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
  129 + initLocalCache();
  130 + }
  131 + }
  132 +
108 133 @Override
109 134 public Long createSensitiveWord(SensitiveWordCreateReqVO createReqVO) {
110 135 // 校验唯一性
... ... @@ -113,8 +138,9 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
113 138 // 插入
114 139 SensitiveWordDO sensitiveWord = SensitiveWordConvert.INSTANCE.convert(createReqVO);
115 140 sensitiveWordMapper.insert(sensitiveWord);
116   - // 发送消息,刷新缓存
117   - sensitiveWordProducer.sendSensitiveWordRefreshMessage();
  141 +
  142 + // 刷新缓存
  143 + initLocalCache();
118 144 return sensitiveWord.getId();
119 145 }
120 146  
... ... @@ -127,8 +153,9 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
127 153 // 更新
128 154 SensitiveWordDO updateObj = SensitiveWordConvert.INSTANCE.convert(updateReqVO);
129 155 sensitiveWordMapper.updateById(updateObj);
130   - // 发送消息,刷新缓存
131   - sensitiveWordProducer.sendSensitiveWordRefreshMessage();
  156 +
  157 + // 刷新缓存
  158 + initLocalCache();
132 159 }
133 160  
134 161 @Override
... ... @@ -137,8 +164,9 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
137 164 validateSensitiveWordExists(id);
138 165 // 删除
139 166 sensitiveWordMapper.deleteById(id);
140   - // 发送消息,刷新缓存
141   - sensitiveWordProducer.sendSensitiveWordRefreshMessage();
  167 +
  168 + // 刷新缓存
  169 + initLocalCache();
142 170 }
143 171  
144 172 private void validateSensitiveWordNameUnique(Long id, String name) {
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImplTest.java
... ... @@ -10,9 +10,7 @@ import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.Sensitiv
10 10 import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordUpdateReqVO;
11 11 import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO;
12 12 import cn.iocoder.yudao.module.system.dal.mysql.sensitiveword.SensitiveWordMapper;
13   -import cn.iocoder.yudao.module.system.mq.producer.sensitiveword.SensitiveWordProducer;
14 13 import org.junit.jupiter.api.Test;
15   -import org.springframework.boot.test.mock.mockito.MockBean;
16 14 import org.springframework.context.annotation.Import;
17 15  
18 16 import javax.annotation.Resource;
... ... @@ -29,7 +27,6 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
29 27 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_WORD_NOT_EXISTS;
30 28 import static java.util.Collections.singletonList;
31 29 import static org.junit.jupiter.api.Assertions.*;
32   -import static org.mockito.Mockito.verify;
33 30  
34 31 /**
35 32 * {@link SensitiveWordServiceImpl} 的单元测试类
... ... @@ -45,9 +42,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
45 42 @Resource
46 43 private SensitiveWordMapper sensitiveWordMapper;
47 44  
48   - @MockBean
49   - private SensitiveWordProducer sensitiveWordProducer;
50   -
51 45 @Test
52 46 public void testInitLocalCache() {
53 47 SensitiveWordDO wordDO1 = randomPojo(SensitiveWordDO.class, o -> o.setName("傻瓜")
... ... @@ -61,6 +55,10 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
61 55 sensitiveWordService.initLocalCache();
62 56 // 断言 sensitiveWordTagsCache 缓存
63 57 assertEquals(SetUtils.asSet("论坛", "蔬菜"), sensitiveWordService.getSensitiveWordTagSet());
  58 + // 断言 sensitiveWordCache
  59 + assertEquals(2, sensitiveWordService.getSensitiveWordCache().size());
  60 + assertPojoEquals(wordDO1, sensitiveWordService.getSensitiveWordCache().get(0));
  61 + assertPojoEquals(wordDO2, sensitiveWordService.getSensitiveWordCache().get(1));
64 62 // 断言 tagSensitiveWordTries 缓存
65 63 assertNotNull(sensitiveWordService.getDefaultSensitiveWordTrie());
66 64 assertEquals(2, sensitiveWordService.getTagSensitiveWordTries().size());
... ... @@ -80,7 +78,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
80 78 // 校验记录的属性是否正确
81 79 SensitiveWordDO sensitiveWord = sensitiveWordMapper.selectById(sensitiveWordId);
82 80 assertPojoEquals(reqVO, sensitiveWord);
83   - verify(sensitiveWordProducer).sendSensitiveWordRefreshMessage();
84 81 }
85 82  
86 83 @Test
... ... @@ -98,7 +95,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
98 95 // 校验是否更新正确
99 96 SensitiveWordDO sensitiveWord = sensitiveWordMapper.selectById(reqVO.getId()); // 获取最新的
100 97 assertPojoEquals(reqVO, sensitiveWord);
101   - verify(sensitiveWordProducer).sendSensitiveWordRefreshMessage();
102 98 }
103 99  
104 100 @Test
... ... @@ -122,7 +118,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
122 118 sensitiveWordService.deleteSensitiveWord(id);
123 119 // 校验数据不存在了
124 120 assertNull(sensitiveWordMapper.selectById(id));
125   - verify(sensitiveWordProducer).sendSensitiveWordRefreshMessage();
126 121 }
127 122  
128 123 @Test
... ...