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 +7,9 @@ import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.Sensitiv
7 import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordPageReqVO; 7 import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordPageReqVO;
8 import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO; 8 import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO;
9 import org.apache.ibatis.annotations.Mapper; 9 import org.apache.ibatis.annotations.Mapper;
  10 +import org.apache.ibatis.annotations.Select;
10 11
  12 +import java.time.LocalDateTime;
11 import java.util.List; 13 import java.util.List;
12 14
13 /** 15 /**
@@ -40,4 +42,7 @@ public interface SensitiveWordMapper extends BaseMapperX<SensitiveWordDO> { @@ -40,4 +42,7 @@ public interface SensitiveWordMapper extends BaseMapperX<SensitiveWordDO> {
40 return selectOne(SensitiveWordDO::getName, name); 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,11 +19,6 @@ import java.util.Set;
19 public interface SensitiveWordService { 19 public interface SensitiveWordService {
20 20
21 /** 21 /**
22 - * 初始化本地缓存  
23 - */  
24 - void initLocalCache();  
25 -  
26 - /**  
27 * 创建敏感词 22 * 创建敏感词
28 * 23 *
29 * @param createReqVO 创建信息 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,21 +11,24 @@ import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.Sensitiv
11 import cn.iocoder.yudao.module.system.convert.sensitiveword.SensitiveWordConvert; 11 import cn.iocoder.yudao.module.system.convert.sensitiveword.SensitiveWordConvert;
12 import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO; 12 import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO;
13 import cn.iocoder.yudao.module.system.dal.mysql.sensitiveword.SensitiveWordMapper; 13 import cn.iocoder.yudao.module.system.dal.mysql.sensitiveword.SensitiveWordMapper;
14 -import cn.iocoder.yudao.module.system.mq.producer.sensitiveword.SensitiveWordProducer;  
15 import cn.iocoder.yudao.module.system.util.collection.SimpleTrie; 14 import cn.iocoder.yudao.module.system.util.collection.SimpleTrie;
16 import com.google.common.collect.HashMultimap; 15 import com.google.common.collect.HashMultimap;
17 import com.google.common.collect.Multimap; 16 import com.google.common.collect.Multimap;
18 import lombok.Getter; 17 import lombok.Getter;
19 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.scheduling.annotation.Scheduled;
20 import org.springframework.stereotype.Service; 20 import org.springframework.stereotype.Service;
21 import org.springframework.validation.annotation.Validated; 21 import org.springframework.validation.annotation.Validated;
22 22
23 import javax.annotation.PostConstruct; 23 import javax.annotation.PostConstruct;
24 import javax.annotation.Resource; 24 import javax.annotation.Resource;
  25 +import java.time.LocalDateTime;
25 import java.util.*; 26 import java.util.*;
  27 +import java.util.concurrent.TimeUnit;
26 28
27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 29 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
28 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; 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 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_WORD_EXISTS; 32 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_WORD_EXISTS;
30 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_WORD_NOT_EXISTS; 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,6 +43,11 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_
40 public class SensitiveWordServiceImpl implements SensitiveWordService { 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 * key:敏感词编号 {@link SensitiveWordDO#getId()} 52 * key:敏感词编号 {@link SensitiveWordDO#getId()}
45 * <p> 53 * <p>
@@ -51,9 +59,6 @@ public class SensitiveWordServiceImpl implements SensitiveWordService { @@ -51,9 +59,6 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
51 @Resource 59 @Resource
52 private SensitiveWordMapper sensitiveWordMapper; 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,7 +73,6 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
68 /** 73 /**
69 * 初始化缓存 74 * 初始化缓存
70 */ 75 */
71 - @Override  
72 @PostConstruct 76 @PostConstruct
73 public void initLocalCache() { 77 public void initLocalCache() {
74 // 第一步:查询数据 78 // 第一步:查询数据
@@ -80,6 +84,7 @@ public class SensitiveWordServiceImpl implements SensitiveWordService { @@ -80,6 +84,7 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
80 Set<String> tags = new HashSet<>(); 84 Set<String> tags = new HashSet<>();
81 sensitiveWords.forEach(word -> tags.addAll(word.getTags())); 85 sensitiveWords.forEach(word -> tags.addAll(word.getTags()));
82 sensitiveWordTagsCache = tags; 86 sensitiveWordTagsCache = tags;
  87 + sensitiveWordCache = sensitiveWords;
83 // 写入 defaultSensitiveWordTrie、tagSensitiveWordTries 缓存 88 // 写入 defaultSensitiveWordTrie、tagSensitiveWordTries 缓存
84 initSensitiveWordTrie(sensitiveWords); 89 initSensitiveWordTrie(sensitiveWords);
85 } 90 }
@@ -105,6 +110,26 @@ public class SensitiveWordServiceImpl implements SensitiveWordService { @@ -105,6 +110,26 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
105 this.tagSensitiveWordTries = tagSensitiveWordTries; 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 @Override 133 @Override
109 public Long createSensitiveWord(SensitiveWordCreateReqVO createReqVO) { 134 public Long createSensitiveWord(SensitiveWordCreateReqVO createReqVO) {
110 // 校验唯一性 135 // 校验唯一性
@@ -113,8 +138,9 @@ public class SensitiveWordServiceImpl implements SensitiveWordService { @@ -113,8 +138,9 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
113 // 插入 138 // 插入
114 SensitiveWordDO sensitiveWord = SensitiveWordConvert.INSTANCE.convert(createReqVO); 139 SensitiveWordDO sensitiveWord = SensitiveWordConvert.INSTANCE.convert(createReqVO);
115 sensitiveWordMapper.insert(sensitiveWord); 140 sensitiveWordMapper.insert(sensitiveWord);
116 - // 发送消息,刷新缓存  
117 - sensitiveWordProducer.sendSensitiveWordRefreshMessage(); 141 +
  142 + // 刷新缓存
  143 + initLocalCache();
118 return sensitiveWord.getId(); 144 return sensitiveWord.getId();
119 } 145 }
120 146
@@ -127,8 +153,9 @@ public class SensitiveWordServiceImpl implements SensitiveWordService { @@ -127,8 +153,9 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
127 // 更新 153 // 更新
128 SensitiveWordDO updateObj = SensitiveWordConvert.INSTANCE.convert(updateReqVO); 154 SensitiveWordDO updateObj = SensitiveWordConvert.INSTANCE.convert(updateReqVO);
129 sensitiveWordMapper.updateById(updateObj); 155 sensitiveWordMapper.updateById(updateObj);
130 - // 发送消息,刷新缓存  
131 - sensitiveWordProducer.sendSensitiveWordRefreshMessage(); 156 +
  157 + // 刷新缓存
  158 + initLocalCache();
132 } 159 }
133 160
134 @Override 161 @Override
@@ -137,8 +164,9 @@ public class SensitiveWordServiceImpl implements SensitiveWordService { @@ -137,8 +164,9 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
137 validateSensitiveWordExists(id); 164 validateSensitiveWordExists(id);
138 // 删除 165 // 删除
139 sensitiveWordMapper.deleteById(id); 166 sensitiveWordMapper.deleteById(id);
140 - // 发送消息,刷新缓存  
141 - sensitiveWordProducer.sendSensitiveWordRefreshMessage(); 167 +
  168 + // 刷新缓存
  169 + initLocalCache();
142 } 170 }
143 171
144 private void validateSensitiveWordNameUnique(Long id, String name) { 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,9 +10,7 @@ import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.Sensitiv
10 import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordUpdateReqVO; 10 import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordUpdateReqVO;
11 import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO; 11 import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO;
12 import cn.iocoder.yudao.module.system.dal.mysql.sensitiveword.SensitiveWordMapper; 12 import cn.iocoder.yudao.module.system.dal.mysql.sensitiveword.SensitiveWordMapper;
13 -import cn.iocoder.yudao.module.system.mq.producer.sensitiveword.SensitiveWordProducer;  
14 import org.junit.jupiter.api.Test; 13 import org.junit.jupiter.api.Test;
15 -import org.springframework.boot.test.mock.mockito.MockBean;  
16 import org.springframework.context.annotation.Import; 14 import org.springframework.context.annotation.Import;
17 15
18 import javax.annotation.Resource; 16 import javax.annotation.Resource;
@@ -29,7 +27,6 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; @@ -29,7 +27,6 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
29 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_WORD_NOT_EXISTS; 27 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_WORD_NOT_EXISTS;
30 import static java.util.Collections.singletonList; 28 import static java.util.Collections.singletonList;
31 import static org.junit.jupiter.api.Assertions.*; 29 import static org.junit.jupiter.api.Assertions.*;
32 -import static org.mockito.Mockito.verify;  
33 30
34 /** 31 /**
35 * {@link SensitiveWordServiceImpl} 的单元测试类 32 * {@link SensitiveWordServiceImpl} 的单元测试类
@@ -45,9 +42,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest { @@ -45,9 +42,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
45 @Resource 42 @Resource
46 private SensitiveWordMapper sensitiveWordMapper; 43 private SensitiveWordMapper sensitiveWordMapper;
47 44
48 - @MockBean  
49 - private SensitiveWordProducer sensitiveWordProducer;  
50 -  
51 @Test 45 @Test
52 public void testInitLocalCache() { 46 public void testInitLocalCache() {
53 SensitiveWordDO wordDO1 = randomPojo(SensitiveWordDO.class, o -> o.setName("傻瓜") 47 SensitiveWordDO wordDO1 = randomPojo(SensitiveWordDO.class, o -> o.setName("傻瓜")
@@ -61,6 +55,10 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest { @@ -61,6 +55,10 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
61 sensitiveWordService.initLocalCache(); 55 sensitiveWordService.initLocalCache();
62 // 断言 sensitiveWordTagsCache 缓存 56 // 断言 sensitiveWordTagsCache 缓存
63 assertEquals(SetUtils.asSet("论坛", "蔬菜"), sensitiveWordService.getSensitiveWordTagSet()); 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 // 断言 tagSensitiveWordTries 缓存 62 // 断言 tagSensitiveWordTries 缓存
65 assertNotNull(sensitiveWordService.getDefaultSensitiveWordTrie()); 63 assertNotNull(sensitiveWordService.getDefaultSensitiveWordTrie());
66 assertEquals(2, sensitiveWordService.getTagSensitiveWordTries().size()); 64 assertEquals(2, sensitiveWordService.getTagSensitiveWordTries().size());
@@ -80,7 +78,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest { @@ -80,7 +78,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
80 // 校验记录的属性是否正确 78 // 校验记录的属性是否正确
81 SensitiveWordDO sensitiveWord = sensitiveWordMapper.selectById(sensitiveWordId); 79 SensitiveWordDO sensitiveWord = sensitiveWordMapper.selectById(sensitiveWordId);
82 assertPojoEquals(reqVO, sensitiveWord); 80 assertPojoEquals(reqVO, sensitiveWord);
83 - verify(sensitiveWordProducer).sendSensitiveWordRefreshMessage();  
84 } 81 }
85 82
86 @Test 83 @Test
@@ -98,7 +95,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest { @@ -98,7 +95,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
98 // 校验是否更新正确 95 // 校验是否更新正确
99 SensitiveWordDO sensitiveWord = sensitiveWordMapper.selectById(reqVO.getId()); // 获取最新的 96 SensitiveWordDO sensitiveWord = sensitiveWordMapper.selectById(reqVO.getId()); // 获取最新的
100 assertPojoEquals(reqVO, sensitiveWord); 97 assertPojoEquals(reqVO, sensitiveWord);
101 - verify(sensitiveWordProducer).sendSensitiveWordRefreshMessage();  
102 } 98 }
103 99
104 @Test 100 @Test
@@ -122,7 +118,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest { @@ -122,7 +118,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
122 sensitiveWordService.deleteSensitiveWord(id); 118 sensitiveWordService.deleteSensitiveWord(id);
123 // 校验数据不存在了 119 // 校验数据不存在了
124 assertNull(sensitiveWordMapper.selectById(id)); 120 assertNull(sensitiveWordMapper.selectById(id));
125 - verify(sensitiveWordProducer).sendSensitiveWordRefreshMessage();  
126 } 121 }
127 122
128 @Test 123 @Test