fix: 优化静态字典缓存问题
This commit is contained in:
parent
0f5a172f08
commit
2dcde94d4b
@ -95,6 +95,12 @@
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 缓存库 Caffeine -->
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 测试 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@ -64,6 +64,7 @@ public class SecurityConfig {
|
||||
.requestMatchers("/tempFile/**").permitAll()
|
||||
.requestMatchers("/system/user/auditUser").permitAll()
|
||||
.requestMatchers("/api/oauth2/oauth/token").permitAll()
|
||||
.requestMatchers("/dict/cache/**").permitAll()
|
||||
.requestMatchers("/sys/psbmodulelbb/**").permitAll()
|
||||
.requestMatchers("/base/operationLog/**").permitAll()
|
||||
// .requestMatchers("/eng/**").permitAll()
|
||||
|
||||
@ -141,6 +141,9 @@ public class SdFpssrlRController {
|
||||
if (req.getStcd() == null || req.getStcd().isEmpty()) {
|
||||
return ResponseResult.error("第" + (i + 1) + "条过鱼设施编码(stcd)不能为空");
|
||||
}
|
||||
if (req.getFwdx() == null || req.getFwdx().isEmpty()) {
|
||||
return ResponseResult.error("第" + (i + 1) + "条服务对象(fwdx)不能为空");
|
||||
}
|
||||
if (req.getTm() == null) {
|
||||
return ResponseResult.error("第" + (i + 1) + "条识别时间(tm)不能为空");
|
||||
}
|
||||
|
||||
@ -104,6 +104,9 @@ public class SdAiboxBH implements Serializable {
|
||||
@FieldChinese("是否启用")
|
||||
private Integer usfl;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String usflName;
|
||||
|
||||
/**
|
||||
* 数据是否接入
|
||||
*/
|
||||
|
||||
@ -38,6 +38,11 @@ public class SdFpssrlRAiRequest {
|
||||
*/
|
||||
private String fsz;
|
||||
|
||||
/**
|
||||
* 服务对象
|
||||
*/
|
||||
private String fwdx;
|
||||
|
||||
/**
|
||||
* 鱼长度,单位:cm
|
||||
*/
|
||||
|
||||
@ -56,6 +56,8 @@ public class SdAiboxBHServiceImpl extends ServiceImpl<SdAiboxBHMapper, SdAiboxBH
|
||||
CODE_TO_NAME_META_LIST.add(CodeToNameMetadataBo.builder().codeProperty("baseId").modifyProperty("baseName").dictType("DYNAMIC").dictSource("SD_HYDROBASE").codeColumn("BASEID").nameColumn("BASENAME").filter("NVL(IS_DELETED, 0) = 0 ").build());
|
||||
CODE_TO_NAME_META_LIST.add(CodeToNameMetadataBo.builder().codeProperty("rstcd").modifyProperty("ennm").dictType("DYNAMIC").dictSource("SD_ENGINFO_B_H").codeColumn("STCD").nameColumn("ENNM").filter("NVL(IS_DELETED, 0) = 0 ").build());
|
||||
CODE_TO_NAME_META_LIST.add(CodeToNameMetadataBo.builder().codeProperty("rvcd").modifyProperty("rvnm").dictType("DYNAMIC").dictSource("V_SD_RVCD_DIC_TREE").codeColumn("RVCD").nameColumn("RVNM").build());
|
||||
CODE_TO_NAME_META_LIST.add(CodeToNameMetadataBo.builder().codeProperty("usfl").modifyProperty("usflName").dictType("STATIC").dictSource("TY_SF").build());
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,89 @@
|
||||
package com.yfd.platform.system.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.yfd.platform.config.ResponseResult;
|
||||
import com.yfd.platform.utils.DictCodeToNameConverter;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 字典缓存管理接口
|
||||
*
|
||||
* <p>提供手动刷新字典缓存的功能,用于紧急情况或运维场景。
|
||||
*
|
||||
* <h3>使用方式</h3>
|
||||
* <ul>
|
||||
* <li>清除全部缓存:POST /dict/cache/clearAll</li>
|
||||
* <li>清除指定静态字典缓存:POST /dict/cache/clearStatic?dictCode=xxx</li>
|
||||
* <li>清除指定动态字典缓存:POST /dict/cache/clearDynamic?tableName=xxx&codeColumn=xxx&nameColumn=xxx</li>
|
||||
* <li>查看缓存统计:GET /dict/cache/stats</li>
|
||||
* </ul>
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/dict/cache")
|
||||
@Tag(name = "字典缓存管理")
|
||||
public class DictCacheManageController {
|
||||
|
||||
@Resource
|
||||
private DictCodeToNameConverter dictCodeToNameConverter;
|
||||
|
||||
/**
|
||||
* 清除所有字典缓存
|
||||
*/
|
||||
@PostMapping("/clearAll")
|
||||
@Operation(summary = "清除所有字典缓存")
|
||||
public ResponseResult clearAllCache() {
|
||||
dictCodeToNameConverter.clearCache();
|
||||
return ResponseResult.success("所有字典缓存已清除");
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定静态字典缓存
|
||||
*
|
||||
* @param dictCode 字典编码
|
||||
*/
|
||||
@PostMapping("/clearStatic")
|
||||
@Operation(summary = "清除指定静态字典缓存")
|
||||
public ResponseResult clearStaticCache(@RequestParam String dictCode) {
|
||||
if (StrUtil.isBlank(dictCode)) {
|
||||
return ResponseResult.error("dictCode 不能为空");
|
||||
}
|
||||
dictCodeToNameConverter.clearStaticDictCache(dictCode);
|
||||
return ResponseResult.success("静态字典缓存已清除: " + dictCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定动态字典缓存
|
||||
*
|
||||
* @param tableName 数据库表名
|
||||
* @param codeColumn 编码列名
|
||||
* @param nameColumn 名称列名
|
||||
* @param filter 额外过滤条件(可选)
|
||||
*/
|
||||
@PostMapping("/clearDynamic")
|
||||
@Operation(summary = "清除指定动态字典缓存")
|
||||
public ResponseResult clearDynamicCache(@RequestParam String tableName,
|
||||
@RequestParam String codeColumn,
|
||||
@RequestParam String nameColumn,
|
||||
@RequestParam(required = false) String filter) {
|
||||
if (StrUtil.isBlank(tableName) || StrUtil.isBlank(codeColumn) || StrUtil.isBlank(nameColumn)) {
|
||||
return ResponseResult.error("tableName、codeColumn、nameColumn 不能为空");
|
||||
}
|
||||
dictCodeToNameConverter.clearDynamicDictCache(tableName, codeColumn, nameColumn, filter);
|
||||
return ResponseResult.success("动态字典缓存已清除: " + tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看缓存统计信息
|
||||
*/
|
||||
@GetMapping("/stats")
|
||||
@Operation(summary = "查看字典缓存统计信息")
|
||||
public ResponseResult getCacheStats() {
|
||||
Map<String, Object> stats = dictCodeToNameConverter.getCacheStats();
|
||||
return ResponseResult.successData(stats);
|
||||
}
|
||||
}
|
||||
@ -8,8 +8,10 @@ import com.yfd.platform.system.domain.SysDictionaryItems;
|
||||
import com.yfd.platform.system.mapper.SysDictionaryItemsMapper;
|
||||
import com.yfd.platform.system.service.ISysDictionaryService;
|
||||
import com.yfd.platform.system.service.ISysDictionaryItemsService;
|
||||
import com.yfd.platform.utils.DictCacheInvalidateEvent;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@ -36,6 +38,9 @@ public class SysDictionaryController {
|
||||
@Resource
|
||||
private ISysDictionaryItemsService sysDictionaryItemsService;
|
||||
|
||||
@Resource
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
/**********************************
|
||||
* 用途说明: 获取数据字典列表
|
||||
* 参数说明 dictType 字典类型
|
||||
@ -61,8 +66,15 @@ public class SysDictionaryController {
|
||||
@PostMapping("/deleteById")
|
||||
@Operation(summary = "根据ID删除字典")
|
||||
public ResponseResult deleteDictById(@RequestParam String id) {
|
||||
// 删除前获取 dictCode,用于后续清除缓存
|
||||
SysDictionary dict = sysDictionaryService.getById(id);
|
||||
String dictCode = dict != null ? dict.getDictCode() : null;
|
||||
boolean ok = sysDictionaryService.deleteDictById(id);
|
||||
if (ok) {
|
||||
// 发布静态字典缓存失效事件
|
||||
if (StrUtil.isNotBlank(dictCode)) {
|
||||
eventPublisher.publishEvent(new DictCacheInvalidateEvent(this, dictCode));
|
||||
}
|
||||
return ResponseResult.success();
|
||||
} else {
|
||||
return ResponseResult.error();
|
||||
|
||||
@ -5,11 +5,15 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.yfd.platform.annotation.Log;
|
||||
import com.yfd.platform.config.ResponseResult;
|
||||
import com.yfd.platform.system.domain.SysDictionary;
|
||||
import com.yfd.platform.system.domain.SysDictionaryItems;
|
||||
import com.yfd.platform.system.mapper.SysDictionaryItemsMapper;
|
||||
import com.yfd.platform.system.service.ISysDictionaryItemsService;
|
||||
import com.yfd.platform.system.service.ISysDictionaryService;
|
||||
import com.yfd.platform.utils.DictCacheInvalidateEvent;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
@ -33,6 +37,31 @@ public class SysDictionaryItemsController {
|
||||
@Resource
|
||||
private ISysDictionaryItemsService sysDictionaryItemsService;
|
||||
|
||||
@Resource
|
||||
private ISysDictionaryService sysDictionaryService;
|
||||
|
||||
@Resource
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
/**
|
||||
* 根据 dictId 查询 dictCode,用于发布缓存失效事件
|
||||
*/
|
||||
private String getDictCodeByDictId(String dictId) {
|
||||
if (StrUtil.isBlank(dictId)) return null;
|
||||
SysDictionary dict = sysDictionaryService.getById(dictId);
|
||||
return dict != null ? dict.getDictCode() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布静态字典缓存失效事件
|
||||
*/
|
||||
private void invalidateStaticCache(String dictId) {
|
||||
String dictCode = getDictCodeByDictId(dictId);
|
||||
if (StrUtil.isNotBlank(dictCode)) {
|
||||
eventPublisher.publishEvent(new DictCacheInvalidateEvent(this, dictCode));
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************
|
||||
* 用途说明: 分页查询字典项信息
|
||||
* 参数说明 dictID 字典ID ItemName 字典项名称 pageNum 当前页
|
||||
@ -77,6 +106,8 @@ public class SysDictionaryItemsController {
|
||||
boolean ok =
|
||||
sysDictionaryItemsService.addDictionaryItem(sysDictionaryItems);
|
||||
if (ok) {
|
||||
// 发布静态字典缓存失效事件
|
||||
invalidateStaticCache(sysDictionaryItems.getDictId());
|
||||
return ResponseResult.success();
|
||||
} else {
|
||||
return ResponseResult.error();
|
||||
@ -98,6 +129,8 @@ public class SysDictionaryItemsController {
|
||||
boolean ok =
|
||||
sysDictionaryItemsService.updateById(sysDictionaryItems);
|
||||
if (ok) {
|
||||
// 发布静态字典缓存失效事件
|
||||
invalidateStaticCache(sysDictionaryItems.getDictId());
|
||||
return ResponseResult.success();
|
||||
} else {
|
||||
return ResponseResult.error();
|
||||
@ -132,8 +165,13 @@ public class SysDictionaryItemsController {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return ResponseResult.error("参数为空");
|
||||
}
|
||||
// 删除前获取 dictId,用于清除缓存
|
||||
SysDictionaryItems item = sysDictionaryItemsService.getById(id);
|
||||
String dictId = item != null ? item.getDictId() : null;
|
||||
boolean ok = sysDictionaryItemsService.removeById(id);
|
||||
if (ok) {
|
||||
// 发布静态字典缓存失效事件
|
||||
invalidateStaticCache(dictId);
|
||||
return ResponseResult.success();
|
||||
} else {
|
||||
return ResponseResult.error();
|
||||
@ -155,8 +193,18 @@ public class SysDictionaryItemsController {
|
||||
String[] splitIds = id.split(",");
|
||||
// 数组转集合
|
||||
List<String> ids = Arrays.asList(splitIds);
|
||||
// 批量删除前获取所有相关的 dictId,去重后逐个清除缓存
|
||||
List<SysDictionaryItems> items = sysDictionaryItemsService.listByIds(ids);
|
||||
boolean ok = sysDictionaryItemsService.removeByIds(ids);
|
||||
if (ok) {
|
||||
// 发布静态字典缓存失效事件
|
||||
if (items != null) {
|
||||
items.stream()
|
||||
.map(SysDictionaryItems::getDictId)
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.distinct()
|
||||
.forEach(dictId -> invalidateStaticCache(dictId));
|
||||
}
|
||||
return ResponseResult.success();
|
||||
} else {
|
||||
return ResponseResult.error();
|
||||
|
||||
@ -0,0 +1,58 @@
|
||||
package com.yfd.platform.utils;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* 字典缓存失效事件
|
||||
*
|
||||
* <p>当静态字典或动态字典数据发生变更时发布此事件,通知
|
||||
* {@link DictCodeToNameConverter} 清除对应缓存。
|
||||
*/
|
||||
@Getter
|
||||
public class DictCacheInvalidateEvent extends ApplicationEvent {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 缓存类型:STATIC-静态字典, DYNAMIC-动态字典 */
|
||||
private final String cacheType;
|
||||
|
||||
/** 静态字典:dictCode;动态字典:表名 */
|
||||
private final String dictSource;
|
||||
|
||||
/** 以下仅动态字典需要 */
|
||||
private String codeColumn;
|
||||
private String nameColumn;
|
||||
private String filter;
|
||||
|
||||
/**
|
||||
* 静态字典缓存失效事件
|
||||
*
|
||||
* @param source 事件源
|
||||
* @param dictCode 字典编码
|
||||
*/
|
||||
public DictCacheInvalidateEvent(Object source, String dictCode) {
|
||||
super(source);
|
||||
this.cacheType = "STATIC";
|
||||
this.dictSource = dictCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态字典缓存失效事件
|
||||
*
|
||||
* @param source 事件源
|
||||
* @param tableName 数据库表名
|
||||
* @param codeColumn 编码列名
|
||||
* @param nameColumn 名称列名
|
||||
* @param filter 额外过滤条件
|
||||
*/
|
||||
public DictCacheInvalidateEvent(Object source, String tableName,
|
||||
String codeColumn, String nameColumn, String filter) {
|
||||
super(source);
|
||||
this.cacheType = "DYNAMIC";
|
||||
this.dictSource = tableName;
|
||||
this.codeColumn = codeColumn;
|
||||
this.nameColumn = nameColumn;
|
||||
this.filter = filter;
|
||||
}
|
||||
}
|
||||
@ -2,25 +2,29 @@ package com.yfd.platform.utils;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.yfd.platform.common.MicroservicDynamicSQLMapper;
|
||||
import com.yfd.platform.system.domain.SysDictionary;
|
||||
import com.yfd.platform.system.domain.SysDictionaryItems;
|
||||
import com.yfd.platform.system.mapper.SysDictionaryItemsMapper;
|
||||
import com.yfd.platform.system.mapper.SysDictionaryMapper;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 字典代码转名称转换器
|
||||
* <p>
|
||||
* 支持静态字典(SYS_DICTIONARY / SYS_DICTIONARY_ITEMS)和动态字典(业务表查询),
|
||||
* 将 VO 中的 code 字段值转换为对应的 name 字段值。
|
||||
* 使用 Caffeine Cache 存储字典映射,TTL=1小时自动过期(兜底),
|
||||
* 并通过监听 {@link DictCacheInvalidateEvent} 在字典数据变更时即时清除缓存。
|
||||
* </p>
|
||||
*
|
||||
* <h3>使用方式</h3>
|
||||
@ -56,13 +60,17 @@ import java.util.stream.Collectors;
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Generated
|
||||
* @since 2025-05-18
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DictCodeToNameConverter {
|
||||
|
||||
/** 默认缓存 TTL:1 小时 */
|
||||
private static final long CACHE_TTL_HOURS = 1;
|
||||
/** 最大缓存条目数 */
|
||||
private static final long MAX_CACHE_SIZE = 500;
|
||||
|
||||
@Resource
|
||||
private SysDictionaryMapper sysDictionaryMapper;
|
||||
|
||||
@ -73,14 +81,60 @@ public class DictCodeToNameConverter {
|
||||
private MicroservicDynamicSQLMapper<?> microservicDynamicSQLMapper;
|
||||
|
||||
/**
|
||||
* 静态字典缓存:dictCode -> Map<itemCode, dictName>
|
||||
* 静态字典缓存:dictCode -> Map<itemCode, dictName>
|
||||
* TTL=1小时,最大 500 条目
|
||||
*/
|
||||
private final Map<String, Map<String, String>> staticDictCache = new ConcurrentHashMap<>();
|
||||
private Cache<String, Map<String, String>> staticDictCache;
|
||||
|
||||
/**
|
||||
* 动态字典缓存:缓存key -> Map<code, name>
|
||||
* 动态字典缓存:缓存key -> Map<code, name>
|
||||
* TTL=1小时,最大 500 条目
|
||||
*/
|
||||
private final Map<String, Map<String, String>> dynamicDictCache = new ConcurrentHashMap<>();
|
||||
private Cache<String, Map<String, String>> dynamicDictCache;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
staticDictCache = Caffeine.newBuilder()
|
||||
.expireAfterWrite(CACHE_TTL_HOURS, TimeUnit.HOURS)
|
||||
.maximumSize(MAX_CACHE_SIZE)
|
||||
.removalListener((key, value, cause) ->
|
||||
log.debug("静态字典缓存失效: dictCode={}, cause={}", key, cause))
|
||||
.build();
|
||||
|
||||
dynamicDictCache = Caffeine.newBuilder()
|
||||
.expireAfterWrite(CACHE_TTL_HOURS, TimeUnit.HOURS)
|
||||
.maximumSize(MAX_CACHE_SIZE)
|
||||
.removalListener((key, value, cause) ->
|
||||
log.debug("动态字典缓存失效: key={}, cause={}", key, cause))
|
||||
.build();
|
||||
|
||||
log.info("DictCodeToNameConverter 初始化完成, TTL={}小时, maxSize={}", CACHE_TTL_HOURS, MAX_CACHE_SIZE);
|
||||
}
|
||||
|
||||
// ==================== 缓存失效事件监听 ====================
|
||||
|
||||
/**
|
||||
* 监听字典缓存失效事件,即时清除对应缓存
|
||||
*/
|
||||
@EventListener
|
||||
public void onDictCacheInvalidate(DictCacheInvalidateEvent event) {
|
||||
if ("STATIC".equalsIgnoreCase(event.getCacheType())) {
|
||||
String dictCode = event.getDictSource();
|
||||
if (StrUtil.isNotBlank(dictCode)) {
|
||||
staticDictCache.invalidate(dictCode);
|
||||
log.info("静态字典缓存已清除: dictCode={}", dictCode);
|
||||
}
|
||||
} else if ("DYNAMIC".equalsIgnoreCase(event.getCacheType())) {
|
||||
String cacheKey = event.getDictSource() + "|"
|
||||
+ event.getCodeColumn() + "|"
|
||||
+ event.getNameColumn() + "|"
|
||||
+ StrUtil.nullToDefault(event.getFilter(), "");
|
||||
dynamicDictCache.invalidate(cacheKey);
|
||||
log.info("动态字典缓存已清除: key={}", cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 对外转换方法 ====================
|
||||
|
||||
/**
|
||||
* 批量转换:根据元数据配置,将 VO 列表中的 code 字段转换为对应的 name 字段
|
||||
@ -108,17 +162,7 @@ public class DictCodeToNameConverter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页结果转换:将 Page 中所有记录的 code 字段转换为对应的 name 字段
|
||||
*
|
||||
* <pre>
|
||||
* 使用示例:
|
||||
* Page<SdHbrvDic> page = hbrvDicService.queryPageList(...);
|
||||
* dictCodeToNameConverter.convertCodeToName(page, CODE_TO_NAME_META_LIST);
|
||||
* </pre>
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param metadataList 元数据配置列表
|
||||
* @param <T> VO 类型
|
||||
* 分页结果转换
|
||||
*/
|
||||
public <T> void convertCodeToName(Page<T> page, List<CodeToNameMetadataBo> metadataList) {
|
||||
if (page == null || page.getRecords() == null || page.getRecords().isEmpty()) {
|
||||
@ -128,17 +172,7 @@ public class DictCodeToNameConverter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 单个对象转换:将单个对象的 code 字段转换为对应的 name 字段
|
||||
*
|
||||
* <pre>
|
||||
* 使用示例:
|
||||
* SdHbrvDic hbrvDic = hbrvDicService.getById(hbrvcd);
|
||||
* dictCodeToNameConverter.convertCodeToName(hbrvDic, CODE_TO_NAME_META_LIST);
|
||||
* </pre>
|
||||
*
|
||||
* @param obj 单个对象
|
||||
* @param metadataList 元数据配置列表
|
||||
* @param <T> VO 类型
|
||||
* 单个对象转换
|
||||
*/
|
||||
public <T> void convertCodeToName(T obj, List<CodeToNameMetadataBo> metadataList) {
|
||||
if (obj == null || metadataList == null || metadataList.isEmpty()) {
|
||||
@ -147,14 +181,13 @@ public class DictCodeToNameConverter {
|
||||
convertCodeToName(Collections.singletonList(obj), metadataList);
|
||||
}
|
||||
|
||||
// ==================== 缓存管理方法 ====================
|
||||
|
||||
/**
|
||||
* 加载静态字典映射
|
||||
*
|
||||
* @param dictCode 字典编码
|
||||
* @return Map<itemCode, dictName>
|
||||
* 加载静态字典映射(从 Caffeine Cache 获取,缓存未命中则查库)
|
||||
*/
|
||||
public Map<String, String> loadStaticDictMap(String dictCode) {
|
||||
return staticDictCache.computeIfAbsent(dictCode, key -> {
|
||||
return staticDictCache.get(dictCode, key -> {
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
try {
|
||||
SysDictionary dict = sysDictionaryMapper.selectOne(
|
||||
@ -175,6 +208,7 @@ public class DictCodeToNameConverter {
|
||||
map.put(item.getItemCode(), StrUtil.nullToDefault(item.getDictName(), item.getItemCode()));
|
||||
}
|
||||
}
|
||||
log.debug("静态字典已加载: dictCode={}, size={}", key, map.size());
|
||||
} catch (Exception e) {
|
||||
log.error("加载静态字典失败: dictCode={}", key, e);
|
||||
}
|
||||
@ -183,17 +217,11 @@ public class DictCodeToNameConverter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载动态字典映射(从业务表查询)
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @param codeColumn 编码列名
|
||||
* @param nameColumn 名称列名
|
||||
* @param filter 额外过滤条件
|
||||
* @return Map<code, name>
|
||||
* 加载动态字典映射(从 Caffeine Cache 获取,缓存未命中则查库)
|
||||
*/
|
||||
public Map<String, String> loadDynamicDictMap(String tableName, String codeColumn, String nameColumn, String filter) {
|
||||
String cacheKey = tableName + "|" + codeColumn + "|" + nameColumn + "|" + StrUtil.nullToDefault(filter, "");
|
||||
return dynamicDictCache.computeIfAbsent(cacheKey, key -> {
|
||||
return dynamicDictCache.get(cacheKey, key -> {
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
try {
|
||||
StringBuilder sql = new StringBuilder();
|
||||
@ -213,6 +241,7 @@ public class DictCodeToNameConverter {
|
||||
map.put(code, StrUtil.nullToDefault(name, code));
|
||||
}
|
||||
}
|
||||
log.debug("动态字典已加载: tableName={}, size={}", tableName, map.size());
|
||||
} catch (Exception e) {
|
||||
log.error("加载动态字典失败: tableName={}", tableName, e);
|
||||
}
|
||||
@ -220,6 +249,48 @@ public class DictCodeToNameConverter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有缓存
|
||||
*/
|
||||
public void clearCache() {
|
||||
staticDictCache.invalidateAll();
|
||||
dynamicDictCache.invalidateAll();
|
||||
log.info("所有字典缓存已清除");
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定静态字典缓存
|
||||
*/
|
||||
public void clearStaticDictCache(String dictCode) {
|
||||
if (StrUtil.isNotBlank(dictCode)) {
|
||||
staticDictCache.invalidate(dictCode);
|
||||
log.info("静态字典缓存已清除: dictCode={}", dictCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定动态字典缓存
|
||||
*/
|
||||
public void clearDynamicDictCache(String tableName, String codeColumn, String nameColumn, String filter) {
|
||||
String cacheKey = tableName + "|" + codeColumn + "|" + nameColumn + "|" + StrUtil.nullToDefault(filter, "");
|
||||
dynamicDictCache.invalidate(cacheKey);
|
||||
log.info("动态字典缓存已清除: key={}", cacheKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存统计信息
|
||||
*/
|
||||
public Map<String, Object> getCacheStats() {
|
||||
Map<String, Object> stats = new LinkedHashMap<>();
|
||||
stats.put("staticDictSize", staticDictCache.estimatedSize());
|
||||
stats.put("dynamicDictSize", dynamicDictCache.estimatedSize());
|
||||
stats.put("ttlHours", CACHE_TTL_HOURS);
|
||||
stats.put("maxSize", MAX_CACHE_SIZE);
|
||||
return stats;
|
||||
}
|
||||
|
||||
// ==================== 私有方法 ====================
|
||||
|
||||
/**
|
||||
* 根据元数据加载 code->name 映射
|
||||
*/
|
||||
@ -251,7 +322,6 @@ public class DictCodeToNameConverter {
|
||||
|
||||
String nameValue;
|
||||
if (metadata.isMulti()) {
|
||||
// 多选:逗号分隔的多个code
|
||||
nameValue = Arrays.stream(codeValue.split(","))
|
||||
.map(String::trim)
|
||||
.filter(StrUtil::isNotBlank)
|
||||
@ -267,40 +337,12 @@ public class DictCodeToNameConverter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有缓存
|
||||
*/
|
||||
public void clearCache() {
|
||||
staticDictCache.clear();
|
||||
dynamicDictCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定静态字典缓存
|
||||
*/
|
||||
public void clearStaticDictCache(String dictCode) {
|
||||
staticDictCache.remove(dictCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定动态字典缓存
|
||||
*/
|
||||
public void clearDynamicDictCache(String tableName, String codeColumn, String nameColumn, String filter) {
|
||||
String cacheKey = tableName + "|" + codeColumn + "|" + nameColumn + "|" + StrUtil.nullToDefault(filter, "");
|
||||
dynamicDictCache.remove(cacheKey);
|
||||
}
|
||||
|
||||
// ==================== 反射工具方法 ====================
|
||||
|
||||
/**
|
||||
* 通过反射获取对象的字段值(转为字符串),支持驼峰命名
|
||||
*/
|
||||
private <T> String getFieldValue(T obj, String fieldName) {
|
||||
try {
|
||||
Field field = findField(obj.getClass(), fieldName);
|
||||
if (field == null) {
|
||||
return null;
|
||||
}
|
||||
if (field == null) return null;
|
||||
field.setAccessible(true);
|
||||
Object value = field.get(obj);
|
||||
return value != null ? value.toString() : null;
|
||||
@ -309,82 +351,51 @@ public class DictCodeToNameConverter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过反射设置对象的字段值,支持驼峰命名
|
||||
*/
|
||||
private <T> void setFieldValue(T obj, String fieldName, String value) {
|
||||
try {
|
||||
Field field = findField(obj.getClass(), fieldName);
|
||||
if (field == null) {
|
||||
return;
|
||||
}
|
||||
if (field == null) return;
|
||||
field.setAccessible(true);
|
||||
field.set(obj, value);
|
||||
} catch (Exception ignored) {
|
||||
// 忽略设置失败
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找字段(忽略大小写,支持驼峰和下划线变体),递归查找父类
|
||||
*/
|
||||
private Field findField(Class<?> clazz, String fieldName) {
|
||||
if (clazz == null || clazz == Object.class) {
|
||||
return null;
|
||||
}
|
||||
// 先精确匹配
|
||||
if (clazz == null || clazz == Object.class) return null;
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.getName().equals(fieldName)) {
|
||||
return field;
|
||||
}
|
||||
if (field.getName().equals(fieldName)) return field;
|
||||
}
|
||||
// 忽略大小写匹配
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.getName().equalsIgnoreCase(fieldName)) {
|
||||
return field;
|
||||
}
|
||||
if (field.getName().equalsIgnoreCase(fieldName)) return field;
|
||||
}
|
||||
// 驼峰-下划线互转匹配
|
||||
String camelName = underlineToCamel(fieldName);
|
||||
String underlineName = camelToUnderline(fieldName);
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
String fName = field.getName();
|
||||
if (fName.equalsIgnoreCase(camelName) || fName.equalsIgnoreCase(underlineName)) {
|
||||
return field;
|
||||
}
|
||||
if (fName.equalsIgnoreCase(camelName) || fName.equalsIgnoreCase(underlineName)) return field;
|
||||
}
|
||||
return findField(clazz.getSuperclass(), fieldName);
|
||||
}
|
||||
|
||||
private String underlineToCamel(String str) {
|
||||
if (StrUtil.isBlank(str) || !str.contains("_")) {
|
||||
return str;
|
||||
}
|
||||
if (StrUtil.isBlank(str) || !str.contains("_")) return str;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean upper = false;
|
||||
for (char c : str.toCharArray()) {
|
||||
if (c == '_') {
|
||||
upper = true;
|
||||
} else {
|
||||
sb.append(upper ? Character.toUpperCase(c) : c);
|
||||
upper = false;
|
||||
}
|
||||
if (c == '_') { upper = true; }
|
||||
else { sb.append(upper ? Character.toUpperCase(c) : c); upper = false; }
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String camelToUnderline(String str) {
|
||||
if (StrUtil.isBlank(str)) {
|
||||
return str;
|
||||
}
|
||||
if (StrUtil.isBlank(str)) return str;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
char c = str.charAt(i);
|
||||
if (Character.isUpperCase(c)) {
|
||||
sb.append('_').append(Character.toLowerCase(c));
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
if (Character.isUpperCase(c)) sb.append('_').append(Character.toLowerCase(c));
|
||||
else sb.append(c);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user