Browse Source

基于 Guava 实现 dict 字典数据的本地缓存

YunaiV 2 years ago
parent
commit
d3200910db
18 changed files with 184 additions and 289 deletions
  1. 25 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/cache/CacheUtils.java
  2. 13 0
      yudao-framework/yudao-spring-boot-starter-biz-dict/pom.xml
  3. 3 3
      yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/config/YudaoDictAutoConfiguration.java
  4. 4 0
      yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/package-info.java
  5. 0 25
      yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/service/DictDataFrameworkService.java
  6. 51 9
      yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/util/DictFrameworkUtils.java
  7. 7 8
      yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/DictConvert.java
  8. 20 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApi.java
  9. 1 1
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dict/dto/DictDataRespDTO.java
  10. 15 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApiImpl.java
  11. 2 4
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/dict/DictDataConvert.java
  12. 5 5
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/dict/DictDataMapper.java
  13. 0 29
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/dict/DictDataRefreshConsumer.java
  14. 0 19
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/dict/DictDataRefreshMessage.java
  15. 0 26
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/dict/DictDataProducer.java
  16. 19 8
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataService.java
  17. 13 103
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImpl.java
  18. 6 49
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceTest.java

+ 25 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/cache/CacheUtils.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.framework.common.util.cache;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+import java.time.Duration;
+import java.util.concurrent.Executors;
+
+/**
+ * Cache 工具类
+ *
+ * @author 芋道源码
+ */
+public class CacheUtils {
+
+    public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> loader) {
+        return CacheBuilder.newBuilder()
+                // 只阻塞当前数据加载线程,其他线程返回旧值
+                .refreshAfterWrite(duration)
+                // 通过 asyncReloading 实现全异步加载,包括 refreshAfterWrite 被阻塞的加载线程
+                .build(CacheLoader.asyncReloading(loader, Executors.newCachedThreadPool())); // TODO 芋艿:可能要思考下,未来要不要做成可配置
+    }
+
+}

+ 13 - 0
yudao-framework/yudao-spring-boot-starter-biz-dict/pom.xml

@@ -26,5 +26,18 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter</artifactId>
         </dependency>
+
+        <!-- 业务组件 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-system-api</artifactId> <!-- 需要使用它,进行 Token 的校验 -->
+            <version>${revision}</version>
+        </dependency>
+
+        <!-- 工具类相关 -->
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
     </dependencies>
 </project>

+ 3 - 3
yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/config/YudaoDictAutoConfiguration.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.framework.dict.config;
 
-import cn.iocoder.yudao.framework.dict.core.service.DictDataFrameworkService;
 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
+import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
@@ -10,8 +10,8 @@ public class YudaoDictAutoConfiguration {
 
     @Bean
     @SuppressWarnings("InstantiationOfUtilityClass")
-    public DictFrameworkUtils dictUtils(DictDataFrameworkService service) {
-        DictFrameworkUtils.init(service);
+    public DictFrameworkUtils dictUtils(DictDataApi dictDataApi) {
+        DictFrameworkUtils.init(dictDataApi);
         return new DictFrameworkUtils();
     }
 

+ 4 - 0
yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 占位
+ */
+package cn.iocoder.yudao.framework.dict.core;

+ 0 - 25
yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/service/DictDataFrameworkService.java

@@ -1,25 +0,0 @@
-package cn.iocoder.yudao.framework.dict.core.service;
-
-import cn.iocoder.yudao.framework.dict.core.dto.DictDataRespDTO;
-
-public interface DictDataFrameworkService {
-
-    /**
-     * 获得指定的字典数据,从缓存中
-     *
-     * @param type 字典类型
-     * @param value 字典数据值
-     * @return 字典数据
-     */
-    DictDataRespDTO getDictDataFromCache(String type, String value);
-
-    /**
-     * 解析获得指定的字典数据,从缓存中
-     *
-     * @param type 字典类型
-     * @param label 字典数据标签
-     * @return 字典数据
-     */
-    DictDataRespDTO parseDictDataFromCache(String type, String label);
-
-}

+ 51 - 9
yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/util/DictFrameworkUtils.java

@@ -1,28 +1,70 @@
 package cn.iocoder.yudao.framework.dict.core.util;
 
-import cn.iocoder.yudao.framework.dict.core.dto.DictDataRespDTO;
-import cn.iocoder.yudao.framework.dict.core.service.DictDataFrameworkService;
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.framework.common.core.KeyValue;
+import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
+import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
+import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 
+import java.time.Duration;
+
 /**
  * 字典工具类
+ *
+ * @author 芋道源码
  */
 @Slf4j
 public class DictFrameworkUtils {
 
-    private static DictDataFrameworkService service;
+    private static DictDataApi dictDataApi;
+
+    private static final DictDataRespDTO DICT_DATA_NULL = new DictDataRespDTO();
+
+    /**
+     * 针对 {@link #getDictDataLabel(String, String)} 的缓存
+     */
+    private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> getDictDataCache = CacheUtils.buildAsyncReloadingCache(
+            Duration.ofMinutes(1L), // 过期时间 1 分钟
+            new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() {
+
+                @Override
+                public DictDataRespDTO load(KeyValue<String, String> key) {
+                    return ObjectUtil.defaultIfNull(dictDataApi.getDictData(key.getKey(), key.getValue()), DICT_DATA_NULL);
+                }
+
+            });
+
+    /**
+     * 针对 {@link #parseDictDataValue(String, String)} 的缓存
+     */
+    private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> parseDictDataCache = CacheUtils.buildAsyncReloadingCache(
+            Duration.ofMinutes(1L), // 过期时间 1 分钟
+            new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() {
+
+                @Override
+                public DictDataRespDTO load(KeyValue<String, String> key) {
+                    return ObjectUtil.defaultIfNull(dictDataApi.parseDictData(key.getKey(), key.getValue()), DICT_DATA_NULL);
+                }
+
+            });
 
-    public static void init(DictDataFrameworkService service) {
-        DictFrameworkUtils.service = service;
+    public static void init(DictDataApi dictDataApi) {
+        DictFrameworkUtils.dictDataApi = dictDataApi;
         log.info("[init][初始化 DictFrameworkUtils 成功]");
     }
 
-    public static DictDataRespDTO getDictDataFromCache(String type, String value) {
-        return service.getDictDataFromCache(type, value);
+    @SneakyThrows
+    public static String getDictDataLabel(String dictType, String value) {
+        return getDictDataCache.get(new KeyValue<>(dictType, value)).getLabel();
     }
 
-    public static DictDataRespDTO parseDictDataFromCache(String type, String label) {
-        return service.parseDictDataFromCache(type, label);
+    @SneakyThrows
+    public static String parseDictDataValue(String dictType, String label) {
+        return parseDictDataCache.get(new KeyValue<>(dictType, label)).getValue();
     }
 
 }

+ 7 - 8
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/DictConvert.java

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.framework.excel.core.convert;
 
 import cn.hutool.core.convert.Convert;
-import cn.iocoder.yudao.framework.dict.core.dto.DictDataRespDTO;
 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
 import com.alibaba.excel.converters.Converter;
@@ -12,7 +11,7 @@ import com.alibaba.excel.metadata.property.ExcelContentProperty;
 import lombok.extern.slf4j.Slf4j;
 
 /**
- * Excel {@link DictDataRespDTO} 数据字典转换器
+ * Excel 数据字典转换器
  *
  * @author 芋道源码
  */
@@ -35,14 +34,14 @@ public class DictConvert implements Converter<Object> {
         // 使用字典解析
         String type = getType(contentProperty);
         String label = cellData.getStringValue();
-        DictDataRespDTO dictData = DictFrameworkUtils.parseDictDataFromCache(type, label);
-        if (dictData == null) {
+        String value = DictFrameworkUtils.parseDictDataValue(type, label);
+        if (value == null) {
             log.error("[convertToJavaData][type({}) 解析不掉 label({})]", type, label);
             return null;
         }
         // 将 String 的 value 转换成对应的属性
         Class<?> fieldClazz = contentProperty.getField().getType();
-        return Convert.convert(fieldClazz, dictData.getValue());
+        return Convert.convert(fieldClazz, value);
     }
 
     @Override
@@ -56,13 +55,13 @@ public class DictConvert implements Converter<Object> {
         // 使用字典格式化
         String type = getType(contentProperty);
         String value = String.valueOf(object);
-        DictDataRespDTO dictData = DictFrameworkUtils.getDictDataFromCache(type, value);
-        if (dictData == null) {
+        String label = DictFrameworkUtils.getDictDataLabel(type, value);
+        if (label == null) {
             log.error("[convertToExcelData][type({}) 转换不了 label({})]", type, value);
             return new CellData<>("");
         }
         // 生成 Excel 小表格
-        return new CellData<>(dictData.getLabel());
+        return new CellData<>(label);
     }
 
     private static String getType(ExcelContentProperty contentProperty) {

+ 20 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApi.java

@@ -1,5 +1,7 @@
 package cn.iocoder.yudao.module.system.api.dict;
 
+import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
+
 import java.util.Collection;
 
 /**
@@ -19,4 +21,22 @@ public interface DictDataApi {
      */
     void validDictDatas(String dictType, Collection<String> values);
 
+    /**
+     * 获得指定的字典数据,从缓存中
+     *
+     * @param type 字典类型
+     * @param value 字典数据值
+     * @return 字典数据
+     */
+    DictDataRespDTO getDictData(String type, String value);
+
+    /**
+     * 解析获得指定的字典数据,从缓存中
+     *
+     * @param type 字典类型
+     * @param label 字典数据标签
+     * @return 字典数据
+     */
+    DictDataRespDTO parseDictData(String type, String label);
+
 }

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/dto/DictDataRespDTO.java → yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dict/dto/DictDataRespDTO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.framework.dict.core.dto;
+package cn.iocoder.yudao.module.system.api.dict.dto;
 
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import lombok.Data;

+ 15 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApiImpl.java

@@ -1,5 +1,8 @@
 package cn.iocoder.yudao.module.system.api.dict;
 
+import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
+import cn.iocoder.yudao.module.system.convert.dict.DictDataConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
 import cn.iocoder.yudao.module.system.service.dict.DictDataService;
 import org.springframework.stereotype.Service;
 
@@ -22,4 +25,16 @@ public class DictDataApiImpl implements DictDataApi {
         dictDataService.validDictDatas(dictType, values);
     }
 
+    @Override
+    public DictDataRespDTO getDictData(String dictType, String value) {
+        DictDataDO dictData = dictDataService.getDictData(dictType, value);
+        return DictDataConvert.INSTANCE.convert02(dictData);
+    }
+
+    @Override
+    public DictDataRespDTO parseDictData(String dictType, String label) {
+        DictDataDO dictData = dictDataService.parseDictData(dictType, label);
+        return DictDataConvert.INSTANCE.convert02(dictData);
+    }
+
 }

+ 2 - 4
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/dict/DictDataConvert.java

@@ -1,9 +1,9 @@
 package cn.iocoder.yudao.module.system.convert.dict;
 
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
 import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.*;
 import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.dict.core.dto.DictDataRespDTO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
 
@@ -29,6 +29,4 @@ public interface DictDataConvert {
 
     DictDataRespDTO convert02(DictDataDO bean);
 
-    List<DictDataRespDTO> convertList03(Collection<DictDataDO> list);
-
 }

+ 5 - 5
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/dict/DictDataMapper.java

@@ -8,11 +8,9 @@ import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataPage
 import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Select;
 
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Date;
 import java.util.List;
 
 @Mapper
@@ -23,6 +21,11 @@ public interface DictDataMapper extends BaseMapperX<DictDataDO> {
                 .eq(DictDataDO::getValue, value));
     }
 
+    default DictDataDO selectByDictTypeAndLabel(String dictType, String label) {
+        return selectOne(new LambdaQueryWrapper<DictDataDO>().eq(DictDataDO::getDictType, dictType)
+                .eq(DictDataDO::getLabel, label));
+    }
+
     default List<DictDataDO> selectByDictTypeAndValues(String dictType, Collection<String> values) {
         return selectList(new LambdaQueryWrapper<DictDataDO>().eq(DictDataDO::getDictType, dictType)
                 .in(DictDataDO::getValue, values));
@@ -46,7 +49,4 @@ public interface DictDataMapper extends BaseMapperX<DictDataDO> {
                 .eqIfPresent(DictDataDO::getStatus, reqVO.getStatus()));
     }
 
-    @Select("SELECT COUNT(*) FROM system_dict_data WHERE update_time > #{maxUpdateTime}")
-    Long selectCountByUpdateTimeGt(Date maxUpdateTime);
-
 }

+ 0 - 29
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/dict/DictDataRefreshConsumer.java

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

+ 0 - 19
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/dict/DictDataRefreshMessage.java

@@ -1,19 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.message.dict;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-/**
- * 字典数据数据刷新 Message
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class DictDataRefreshMessage extends AbstractChannelMessage {
-
-    @Override
-    public String getChannel() {
-        return "system.dict-data.refresh";
-    }
-
-}

+ 0 - 26
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/dict/DictDataProducer.java

@@ -1,26 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.producer.dict;
-
-import cn.iocoder.yudao.module.system.mq.message.dict.DictDataRefreshMessage;
-import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * DictData 字典数据相关消息的 Producer
- */
-@Component
-public class DictDataProducer {
-
-    @Resource
-    private RedisMQTemplate redisMQTemplate;
-
-    /**
-     * 发送 {@link DictDataRefreshMessage} 消息
-     */
-    public void sendDictDataRefreshMessage() {
-        DictDataRefreshMessage message = new DictDataRefreshMessage();
-        redisMQTemplate.send(message);
-    }
-
-}

+ 19 - 8
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataService.java

@@ -1,12 +1,11 @@
 package cn.iocoder.yudao.module.system.service.dict;
 
-import cn.iocoder.yudao.framework.dict.core.service.DictDataFrameworkService;
-import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataExportReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataPageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataUpdateReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
 
 import java.util.Collection;
 import java.util.List;
@@ -16,12 +15,7 @@ import java.util.List;
  *
  * @author ruoyi
  */
-public interface DictDataService extends DictDataFrameworkService {
-
-    /**
-     * 初始化字典数据的本地缓存
-     */
-    void initLocalCache();
+public interface DictDataService {
 
     /**
      * 创建字典数据
@@ -94,4 +88,21 @@ public interface DictDataService extends DictDataFrameworkService {
      */
     void validDictDatas(String dictType, Collection<String> values);
 
+    /**
+     * 获得指定的字典数据
+     *
+     * @param dictType 字典类型
+     * @param value 字典数据值
+     * @return 字典数据
+     */
+    DictDataDO getDictData(String dictType, String value);
+
+    /**
+     * 解析获得指定的字典数据,从缓存中
+     *
+     * @param dictType 字典类型
+     * @param label 字典数据标签
+     * @return 字典数据
+     */
+    DictDataDO parseDictData(String dictType, String label);
 }

+ 13 - 103
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImpl.java

@@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.framework.dict.core.dto.DictDataRespDTO;
 import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataExportReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataPageReqVO;
@@ -13,20 +12,15 @@ import cn.iocoder.yudao.module.system.convert.dict.DictDataConvert;
 import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictTypeDO;
 import cn.iocoder.yudao.module.system.dal.mysql.dict.DictDataMapper;
-import cn.iocoder.yudao.module.system.mq.producer.dict.DictDataProducer;
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableTable;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 
-import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import java.util.Collection;
 import java.util.Comparator;
-import java.util.Date;
 import java.util.List;
+import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
@@ -47,88 +41,12 @@ public class DictDataServiceImpl implements DictDataService {
             .comparing(DictDataDO::getDictType)
             .thenComparingInt(DictDataDO::getSort);
 
-    /**
-     * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
-     * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
-     */
-    private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
-
     @Resource
     private DictTypeService dictTypeService;
 
     @Resource
     private DictDataMapper dictDataMapper;
 
-    @Resource
-    private DictDataProducer dictDataProducer;
-
-    /**
-     * 字典数据缓存,第二个 key 使用 label
-     *
-     * key1:字典类型 dictType
-     * key2:字典标签 label
-     */
-    private ImmutableTable<String, String, DictDataDO> labelDictDataCache;
-    /**
-     * 字典数据缓存,第二个 key 使用 value
-     *
-     * key1:字典类型 dictType
-     * key2:字典值 value
-     */
-    private ImmutableTable<String, String, DictDataDO> valueDictDataCache;
-    /**
-     * 缓存字典数据的最大更新时间,用于后续的增量轮询,判断是否有更新
-     */
-    private volatile Date maxUpdateTime;
-
-    @Override
-    @PostConstruct
-    public synchronized void initLocalCache() {
-        // 获取字典数据列表,如果有更新
-        List<DictDataDO> dataList = loadDictDataIfUpdate(maxUpdateTime);
-        if (CollUtil.isEmpty(dataList)) {
-            return;
-        }
-
-        // 构建缓存
-        ImmutableTable.Builder<String, String, DictDataDO> labelDictDataBuilder = ImmutableTable.builder();
-        ImmutableTable.Builder<String, String, DictDataDO> valueDictDataBuilder = ImmutableTable.builder();
-        dataList.forEach(dictData -> {
-            labelDictDataBuilder.put(dictData.getDictType(), dictData.getLabel(), dictData);
-            valueDictDataBuilder.put(dictData.getDictType(), dictData.getValue(), dictData);
-        });
-        labelDictDataCache = labelDictDataBuilder.build();
-        valueDictDataCache = valueDictDataBuilder.build();
-        maxUpdateTime = CollectionUtils.getMaxValue(dataList, DictDataDO::getUpdateTime);
-        log.info("[initLocalCache][缓存字典数据,数量为:{}]", dataList.size());
-    }
-
-    /**
-     * 如果字典数据发生变化,从数据库中获取最新的全量字典数据。
-     * 如果未发生变化,则返回空
-     *
-     * @param maxUpdateTime 当前字典数据的最大更新时间
-     * @return 字典数据列表
-     */
-    private List<DictDataDO> loadDictDataIfUpdate(Date maxUpdateTime) {
-        // 第一步,判断是否要更新。
-        if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
-            log.info("[loadDictDataIfUpdate][首次加载全量字典数据]");
-        } else { // 判断数据库中是否有更新的字典数据
-            if (dictDataMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
-                return null;
-            }
-            log.info("[loadDictDataIfUpdate][增量加载全量字典数据]");
-        }
-        // 第二步,如果有更新,则从数据库加载所有字典数据
-        return dictDataMapper.selectList();
-    }
-
-    @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
-    public void schedulePeriodicRefresh() {
-        initLocalCache();
-    }
-
     @Override
     public List<DictDataDO> getDictDatas() {
         List<DictDataDO> list = dictDataMapper.selectList();
@@ -153,16 +71,6 @@ public class DictDataServiceImpl implements DictDataService {
         return dictDataMapper.selectById(id);
     }
 
-    @Override
-    public DictDataRespDTO getDictDataFromCache(String type, String value) {
-        return DictDataConvert.INSTANCE.convert02(valueDictDataCache.get(type, value));
-    }
-
-    @Override
-    public DictDataRespDTO parseDictDataFromCache(String type, String label) {
-        return DictDataConvert.INSTANCE.convert02(labelDictDataCache.get(type, label));
-    }
-
     @Override
     public Long createDictData(DictDataCreateReqVO reqVO) {
         // 校验正确性
@@ -171,9 +79,6 @@ public class DictDataServiceImpl implements DictDataService {
         // 插入字典类型
         DictDataDO dictData = DictDataConvert.INSTANCE.convert(reqVO);
         dictDataMapper.insert(dictData);
-
-        // 发送刷新消息
-        dictDataProducer.sendDictDataRefreshMessage();
         return dictData.getId();
     }
 
@@ -185,9 +90,6 @@ public class DictDataServiceImpl implements DictDataService {
         // 更新字典类型
         DictDataDO updateObj = DictDataConvert.INSTANCE.convert(reqVO);
         dictDataMapper.updateById(updateObj);
-
-        // 发送刷新消息
-        dictDataProducer.sendDictDataRefreshMessage();
     }
 
     @Override
@@ -197,9 +99,6 @@ public class DictDataServiceImpl implements DictDataService {
 
         // 删除字典数据
         dictDataMapper.deleteById(id);
-
-        // 发送刷新消息
-        dictDataProducer.sendDictDataRefreshMessage();
     }
 
     @Override
@@ -259,7 +158,8 @@ public class DictDataServiceImpl implements DictDataService {
         if (CollUtil.isEmpty(values)) {
             return;
         }
-        ImmutableMap<String, DictDataDO> dictDataMap = valueDictDataCache.row(dictType);
+        Map<String, DictDataDO> dictDataMap = CollectionUtils.convertMap(
+                dictDataMapper.selectByDictTypeAndValues(dictType, values), DictDataDO::getValue);
         // 校验
         values.forEach(value -> {
             DictDataDO dictData = dictDataMap.get(value);
@@ -272,4 +172,14 @@ public class DictDataServiceImpl implements DictDataService {
         });
     }
 
+    @Override
+    public DictDataDO getDictData(String dictType, String value) {
+        return dictDataMapper.selectByDictTypeAndValue(dictType, value);
+    }
+
+    @Override
+    public DictDataDO parseDictData(String dictType, String label) {
+        return dictDataMapper.selectByDictTypeAndLabel(dictType, label);
+    }
+
 }

+ 6 - 49
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceTest.java

@@ -1,36 +1,32 @@
 package cn.iocoder.yudao.module.system.service.dict;
 
-import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
+import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataExportReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataPageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataUpdateReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictTypeDO;
 import cn.iocoder.yudao.module.system.dal.mysql.dict.DictDataMapper;
-import cn.iocoder.yudao.module.system.mq.producer.dict.DictDataProducer;
-import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
-import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
-import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
-import com.google.common.collect.ImmutableTable;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
-import java.util.Date;
 import java.util.List;
 import java.util.function.Consumer;
 
-import static cn.hutool.core.bean.BeanUtil.getFieldValue;
-import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.when;
 
 @Import(DictDataServiceImpl.class)
 public class DictDataServiceTest extends BaseDbUnitTest {
@@ -42,39 +38,6 @@ public class DictDataServiceTest extends BaseDbUnitTest {
     private DictDataMapper dictDataMapper;
     @MockBean
     private DictTypeService dictTypeService;
-    @MockBean
-    private DictDataProducer dictDataProducer;
-
-    /**
-     * 测试加载到新的字典数据的情况
-     */
-    @Test
-    @SuppressWarnings("unchecked")
-    public void testInitLocalCache() {
-        // mock 数据
-        DictDataDO dictData01 = randomDictDataDO();
-        dictDataMapper.insert(dictData01);
-        DictDataDO dictData02 = randomDictDataDO();
-        dictDataMapper.insert(dictData02);
-
-        // 调用
-        dictDataService.initLocalCache();
-        // 断言 labelDictDataCache 缓存
-        ImmutableTable<String, String, DictDataDO> labelDictDataCache =
-                (ImmutableTable<String, String, DictDataDO>) getFieldValue(dictDataService, "labelDictDataCache");
-        assertEquals(2, labelDictDataCache.size());
-        assertPojoEquals(dictData01, labelDictDataCache.get(dictData01.getDictType(), dictData01.getLabel()));
-        assertPojoEquals(dictData02, labelDictDataCache.get(dictData02.getDictType(), dictData02.getLabel()));
-        // 断言 valueDictDataCache 缓存
-        ImmutableTable<String, String, DictDataDO> valueDictDataCache =
-                (ImmutableTable<String, String, DictDataDO>) getFieldValue(dictDataService, "valueDictDataCache");
-        assertEquals(2, valueDictDataCache.size());
-        assertPojoEquals(dictData01, valueDictDataCache.get(dictData01.getDictType(), dictData01.getValue()));
-        assertPojoEquals(dictData02, valueDictDataCache.get(dictData02.getDictType(), dictData02.getValue()));
-        // 断言 maxUpdateTime 缓存
-        Date maxUpdateTime = (Date) getFieldValue(dictDataService, "maxUpdateTime");
-        assertEquals(ObjectUtils.max(dictData01.getUpdateTime(), dictData02.getUpdateTime()), maxUpdateTime);
-    }
 
     @Test
     public void testGetDictDataPage() {
@@ -148,8 +111,6 @@ public class DictDataServiceTest extends BaseDbUnitTest {
         // 校验记录的属性是否正确
         DictDataDO dictData = dictDataMapper.selectById(dictDataId);
         assertPojoEquals(reqVO, dictData);
-        // 校验调用
-        verify(dictDataProducer, times(1)).sendDictDataRefreshMessage();
     }
 
     @Test
@@ -170,8 +131,6 @@ public class DictDataServiceTest extends BaseDbUnitTest {
         // 校验是否更新正确
         DictDataDO dictData = dictDataMapper.selectById(reqVO.getId()); // 获取最新的
         assertPojoEquals(reqVO, dictData);
-        // 校验调用
-        verify(dictDataProducer, times(1)).sendDictDataRefreshMessage();
     }
 
     @Test
@@ -186,8 +145,6 @@ public class DictDataServiceTest extends BaseDbUnitTest {
         dictDataService.deleteDictData(id);
         // 校验数据不存在了
         assertNull(dictDataMapper.selectById(id));
-        // 校验调用
-        verify(dictDataProducer, times(1)).sendDictDataRefreshMessage();
     }
 
     @Test