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

This commit is contained in:
YunaiV
2022-06-17 19:44:28 +08:00
parent d1271f8bff
commit d3200910db
18 changed files with 184 additions and 289 deletions

View File

@ -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 芋艿:可能要思考下,未来要不要做成可配置
}
}

View File

@ -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>

View File

@ -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();
}

View File

@ -1,33 +0,0 @@
package cn.iocoder.yudao.framework.dict.core.dto;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import lombok.Data;
/**
* 字典数据 Response DTO
*
* @author 芋道源码
*/
@Data
public class DictDataRespDTO {
/**
* 字典标签
*/
private String label;
/**
* 字典值
*/
private String value;
/**
* 字典类型
*/
private String dictType;
/**
* 状态
*
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
}

View File

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

View File

@ -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);
}

View File

@ -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;
public static void init(DictDataFrameworkService service) {
DictFrameworkUtils.service = service;
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(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();
}
}

View File

@ -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) {