fix: 优化静态字典缓存问题

This commit is contained in:
tangwei 2026-07-30 17:10:03 +08:00
parent 0f5a172f08
commit 2dcde94d4b
11 changed files with 353 additions and 115 deletions

View File

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

View File

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

View File

@ -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)不能为空");
}

View File

@ -104,6 +104,9 @@ public class SdAiboxBH implements Serializable {
@FieldChinese("是否启用")
private Integer usfl;
@TableField(exist = false)
private String usflName;
/**
* 数据是否接入
*/

View File

@ -38,6 +38,11 @@ public class SdFpssrlRAiRequest {
*/
private String fsz;
/**
* 服务对象
*/
private String fwdx;
/**
* 鱼长度单位cm
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 {
/** 默认缓存 TTL1 小时 */
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&lt;itemCode, dictName&gt;
* 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&lt;code, name&gt;
* 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&lt;SdHbrvDic&gt; 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();
}