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

This commit is contained in:
YunaiV 2023-07-29 07:31:53 +08:00
parent 5e7e3d816c
commit 27e70e73a3
8 changed files with 48 additions and 103 deletions

View File

@ -7,7 +7,9 @@ import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.Sensitiv
import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO; import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
/** /**
@ -40,4 +42,7 @@ public interface SensitiveWordMapper extends BaseMapperX<SensitiveWordDO> {
return selectOne(SensitiveWordDO::getName, name); return selectOne(SensitiveWordDO::getName, name);
} }
@Select("SELECT COUNT(*) FROM system_sensitive_word WHERE update_time > #{maxUpdateTime}")
Long selectCountByUpdateTimeGt(LocalDateTime maxTime);
} }

View File

@ -1,29 +0,0 @@
package cn.iocoder.yudao.module.system.mq.consumer.sensitiveword;
import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
import cn.iocoder.yudao.module.system.mq.message.sensitiveword.SensitiveWordRefreshMessage;
import cn.iocoder.yudao.module.system.service.sensitiveword.SensitiveWordService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 针对 {@link SensitiveWordRefreshMessage} 的消费者
*
* @author 芋道源码
*/
@Component
@Slf4j
public class SensitiveWordRefreshConsumer extends AbstractChannelMessageListener<SensitiveWordRefreshMessage> {
@Resource
private SensitiveWordService sensitiveWordService;
@Override
public void onMessage(SensitiveWordRefreshMessage message) {
log.info("[onMessage][收到 SensitiveWord 刷新消息]");
sensitiveWordService.initLocalCache();
}
}

View File

@ -1,19 +0,0 @@
package cn.iocoder.yudao.module.system.mq.message.sensitiveword;
import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 敏感词的刷新 Message
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SensitiveWordRefreshMessage extends AbstractChannelMessage {
@Override
public String getChannel() {
return "system.sensitive-word.refresh";
}
}

View File

@ -1,4 +0,0 @@
/**
* 占位
*/
package cn.iocoder.yudao.module.system.mq.producer;

View File

@ -1,26 +0,0 @@
package cn.iocoder.yudao.module.system.mq.producer.sensitiveword;
import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
import cn.iocoder.yudao.module.system.mq.message.sensitiveword.SensitiveWordRefreshMessage;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 敏感词相关的 Producer
*/
@Component
public class SensitiveWordProducer {
@Resource
private RedisMQTemplate redisMQTemplate;
/**
* 发送 {@link SensitiveWordRefreshMessage} 消息
*/
public void sendSensitiveWordRefreshMessage() {
SensitiveWordRefreshMessage message = new SensitiveWordRefreshMessage();
redisMQTemplate.send(message);
}
}

View File

@ -18,11 +18,6 @@ import java.util.Set;
*/ */
public interface SensitiveWordService { public interface SensitiveWordService {
/**
* 初始化本地缓存
*/
void initLocalCache();
/** /**
* 创建敏感词 * 创建敏感词
* *

View File

@ -11,21 +11,24 @@ import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.Sensitiv
import cn.iocoder.yudao.module.system.convert.sensitiveword.SensitiveWordConvert; import cn.iocoder.yudao.module.system.convert.sensitiveword.SensitiveWordConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO; import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO;
import cn.iocoder.yudao.module.system.dal.mysql.sensitiveword.SensitiveWordMapper; import cn.iocoder.yudao.module.system.dal.mysql.sensitiveword.SensitiveWordMapper;
import cn.iocoder.yudao.module.system.mq.producer.sensitiveword.SensitiveWordProducer;
import cn.iocoder.yudao.module.system.util.collection.SimpleTrie; import cn.iocoder.yudao.module.system.util.collection.SimpleTrie;
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_WORD_EXISTS; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_WORD_EXISTS;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_WORD_NOT_EXISTS; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_WORD_NOT_EXISTS;
@ -39,6 +42,11 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_
@Validated @Validated
public class SensitiveWordServiceImpl implements SensitiveWordService { public class SensitiveWordServiceImpl implements SensitiveWordService {
/**
* 敏感词列表缓存
*/
@Getter
private volatile List<SensitiveWordDO> sensitiveWordCache = Collections.emptyList();
/** /**
* 敏感词标签缓存 * 敏感词标签缓存
* key敏感词编号 {@link SensitiveWordDO#getId()} * key敏感词编号 {@link SensitiveWordDO#getId()}
@ -51,9 +59,6 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
@Resource @Resource
private SensitiveWordMapper sensitiveWordMapper; private SensitiveWordMapper sensitiveWordMapper;
@Resource
private SensitiveWordProducer sensitiveWordProducer;
/** /**
* 默认的敏感词的字典树包含所有敏感词 * 默认的敏感词的字典树包含所有敏感词
*/ */
@ -68,7 +73,6 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
/** /**
* 初始化缓存 * 初始化缓存
*/ */
@Override
@PostConstruct @PostConstruct
public void initLocalCache() { public void initLocalCache() {
// 第一步查询数据 // 第一步查询数据
@ -80,6 +84,7 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
Set<String> tags = new HashSet<>(); Set<String> tags = new HashSet<>();
sensitiveWords.forEach(word -> tags.addAll(word.getTags())); sensitiveWords.forEach(word -> tags.addAll(word.getTags()));
sensitiveWordTagsCache = tags; sensitiveWordTagsCache = tags;
sensitiveWordCache = sensitiveWords;
// 写入 defaultSensitiveWordTrietagSensitiveWordTries 缓存 // 写入 defaultSensitiveWordTrietagSensitiveWordTries 缓存
initSensitiveWordTrie(sensitiveWords); initSensitiveWordTrie(sensitiveWords);
} }
@ -105,6 +110,26 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
this.tagSensitiveWordTries = tagSensitiveWordTries; this.tagSensitiveWordTries = tagSensitiveWordTries;
} }
/**
* 通过定时任务轮询刷新缓存
*
* 目的多节点部署时通过轮询通知所有节点进行刷新
*/
@Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
public void refreshLocalCache() {
// 情况一如果缓存里没有数据则直接刷新缓存
if (CollUtil.isEmpty(sensitiveWordCache)) {
initLocalCache();
return;
}
// 情况二如果缓存里数据则通过 updateTime 判断是否有数据变更有变更则刷新缓存
LocalDateTime maxTime = getMaxValue(sensitiveWordCache, SensitiveWordDO::getUpdateTime);
if (sensitiveWordMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
initLocalCache();
}
}
@Override @Override
public Long createSensitiveWord(SensitiveWordCreateReqVO createReqVO) { public Long createSensitiveWord(SensitiveWordCreateReqVO createReqVO) {
// 校验唯一性 // 校验唯一性
@ -113,8 +138,9 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
// 插入 // 插入
SensitiveWordDO sensitiveWord = SensitiveWordConvert.INSTANCE.convert(createReqVO); SensitiveWordDO sensitiveWord = SensitiveWordConvert.INSTANCE.convert(createReqVO);
sensitiveWordMapper.insert(sensitiveWord); sensitiveWordMapper.insert(sensitiveWord);
// 发送消息刷新缓存
sensitiveWordProducer.sendSensitiveWordRefreshMessage(); // 刷新缓存
initLocalCache();
return sensitiveWord.getId(); return sensitiveWord.getId();
} }
@ -127,8 +153,9 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
// 更新 // 更新
SensitiveWordDO updateObj = SensitiveWordConvert.INSTANCE.convert(updateReqVO); SensitiveWordDO updateObj = SensitiveWordConvert.INSTANCE.convert(updateReqVO);
sensitiveWordMapper.updateById(updateObj); sensitiveWordMapper.updateById(updateObj);
// 发送消息刷新缓存
sensitiveWordProducer.sendSensitiveWordRefreshMessage(); // 刷新缓存
initLocalCache();
} }
@Override @Override
@ -137,8 +164,9 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
validateSensitiveWordExists(id); validateSensitiveWordExists(id);
// 删除 // 删除
sensitiveWordMapper.deleteById(id); sensitiveWordMapper.deleteById(id);
// 发送消息刷新缓存
sensitiveWordProducer.sendSensitiveWordRefreshMessage(); // 刷新缓存
initLocalCache();
} }
private void validateSensitiveWordNameUnique(Long id, String name) { private void validateSensitiveWordNameUnique(Long id, String name) {

View File

@ -10,9 +10,7 @@ import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.Sensitiv
import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordUpdateReqVO; import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordUpdateReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO; import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO;
import cn.iocoder.yudao.module.system.dal.mysql.sensitiveword.SensitiveWordMapper; import cn.iocoder.yudao.module.system.dal.mysql.sensitiveword.SensitiveWordMapper;
import cn.iocoder.yudao.module.system.mq.producer.sensitiveword.SensitiveWordProducer;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -29,7 +27,6 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_WORD_NOT_EXISTS; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_WORD_NOT_EXISTS;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.verify;
/** /**
* {@link SensitiveWordServiceImpl} 的单元测试类 * {@link SensitiveWordServiceImpl} 的单元测试类
@ -45,9 +42,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
@Resource @Resource
private SensitiveWordMapper sensitiveWordMapper; private SensitiveWordMapper sensitiveWordMapper;
@MockBean
private SensitiveWordProducer sensitiveWordProducer;
@Test @Test
public void testInitLocalCache() { public void testInitLocalCache() {
SensitiveWordDO wordDO1 = randomPojo(SensitiveWordDO.class, o -> o.setName("傻瓜") SensitiveWordDO wordDO1 = randomPojo(SensitiveWordDO.class, o -> o.setName("傻瓜")
@ -61,6 +55,10 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
sensitiveWordService.initLocalCache(); sensitiveWordService.initLocalCache();
// 断言 sensitiveWordTagsCache 缓存 // 断言 sensitiveWordTagsCache 缓存
assertEquals(SetUtils.asSet("论坛", "蔬菜"), sensitiveWordService.getSensitiveWordTagSet()); assertEquals(SetUtils.asSet("论坛", "蔬菜"), sensitiveWordService.getSensitiveWordTagSet());
// 断言 sensitiveWordCache
assertEquals(2, sensitiveWordService.getSensitiveWordCache().size());
assertPojoEquals(wordDO1, sensitiveWordService.getSensitiveWordCache().get(0));
assertPojoEquals(wordDO2, sensitiveWordService.getSensitiveWordCache().get(1));
// 断言 tagSensitiveWordTries 缓存 // 断言 tagSensitiveWordTries 缓存
assertNotNull(sensitiveWordService.getDefaultSensitiveWordTrie()); assertNotNull(sensitiveWordService.getDefaultSensitiveWordTrie());
assertEquals(2, sensitiveWordService.getTagSensitiveWordTries().size()); assertEquals(2, sensitiveWordService.getTagSensitiveWordTries().size());
@ -80,7 +78,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
// 校验记录的属性是否正确 // 校验记录的属性是否正确
SensitiveWordDO sensitiveWord = sensitiveWordMapper.selectById(sensitiveWordId); SensitiveWordDO sensitiveWord = sensitiveWordMapper.selectById(sensitiveWordId);
assertPojoEquals(reqVO, sensitiveWord); assertPojoEquals(reqVO, sensitiveWord);
verify(sensitiveWordProducer).sendSensitiveWordRefreshMessage();
} }
@Test @Test
@ -98,7 +95,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
// 校验是否更新正确 // 校验是否更新正确
SensitiveWordDO sensitiveWord = sensitiveWordMapper.selectById(reqVO.getId()); // 获取最新的 SensitiveWordDO sensitiveWord = sensitiveWordMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, sensitiveWord); assertPojoEquals(reqVO, sensitiveWord);
verify(sensitiveWordProducer).sendSensitiveWordRefreshMessage();
} }
@Test @Test
@ -122,7 +118,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
sensitiveWordService.deleteSensitiveWord(id); sensitiveWordService.deleteSensitiveWord(id);
// 校验数据不存在了 // 校验数据不存在了
assertNull(sensitiveWordMapper.selectById(id)); assertNull(sensitiveWordMapper.selectById(id));
verify(sensitiveWordProducer).sendSensitiveWordRefreshMessage();
} }
@Test @Test