refactor(common-json): 优化 JSON 增强处理链路

- 抽取 JSON 字符串校验的重复解析逻辑
  - 为 JsonFieldProcessor 增加 supports 判断,减少无关字段处理
  - 优化 JsonValueEnhancer,未命中增强字段时直接返回原对象
  - 拆分响应增强渲染逻辑,提升代码可读性
  - 增强上下文属性管理能力
  - 优化翻译、脱敏处理器的字段匹配逻辑
  - 修正 Date 反序列化空字符串处理
  - 支持 LocalDateTime 秒级、毫秒级及负时间戳反序列化
This commit is contained in:
疯狂的狮子Li
2026-05-16 14:08:52 +08:00
parent 7b66f0de26
commit 2af4b2c7a3
8 changed files with 180 additions and 68 deletions

View File

@@ -5,6 +5,7 @@ import tools.jackson.databind.json.JsonMapper;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Supplier;
/**
* 单次响应增强上下文。
@@ -16,6 +17,8 @@ public class JsonEnhancementContext {
private final Map<String, Object> attributes = new LinkedHashMap<>();
private boolean processingRequired;
/**
* 构造响应增强上下文。
*
@@ -37,6 +40,20 @@ public class JsonEnhancementContext {
return (T) attributes.get(key);
}
/**
* 获取上下文属性,不存在时创建并写入。
*
* @param key 属性键
* @param supplier 属性值创建器
* @param <T> 属性值类型
* @return 属性值
*/
@SuppressWarnings("unchecked")
public <T> T getOrCreateAttribute(String key, Supplier<T> supplier) {
Object value = attributes.computeIfAbsent(key, ignored -> supplier.get());
return (T) value;
}
/**
* 设置上下文属性。
*
@@ -47,4 +64,25 @@ public class JsonEnhancementContext {
attributes.put(key, value);
}
/**
* 判断上下文是否包含指定属性。
*/
public boolean containsAttribute(String key) {
return attributes.containsKey(key);
}
/**
* 移除上下文属性。
*/
public void removeAttribute(String key) {
attributes.remove(key);
}
/**
* 标记本次响应存在需要处理的字段。
*/
public void markProcessingRequired() {
this.processingRequired = true;
}
}

View File

@@ -20,6 +20,14 @@ package org.dromara.common.json.enhance;
*/
public interface JsonFieldProcessor {
/**
* 判断当前处理器是否需要处理该字段。
* 默认返回 true 以兼容无注解驱动的自定义处理器。
*/
default boolean supports(JsonFieldContext fieldContext) {
return true;
}
/**
* 收集阶段:扫描字段,将需要处理的 key 存入 context。
* 每个字段调用一次,整个对象树扫描完成后才会进入 prepare 阶段。

View File

@@ -54,7 +54,13 @@ public class JsonValueEnhancer {
if (body == null || body instanceof JsonNode || processors.isEmpty()) {
return body;
}
return enhanceTree(body);
JsonEnhancementContext context = new JsonEnhancementContext(jsonMapper);
collectValue(body, context, new IdentityHashMap<>());
if (!context.isProcessingRequired()) {
return body;
}
processors.forEach(processor -> processor.prepare(context));
return renderValue(body, context, new IdentityHashMap<>());
}
/**
@@ -73,6 +79,9 @@ public class JsonValueEnhancer {
private JsonNode enhanceTree(Object value) {
JsonEnhancementContext context = new JsonEnhancementContext(jsonMapper);
collectValue(value, context, new IdentityHashMap<>());
if (!context.isProcessingRequired()) {
return jsonMapper.valueToTree(value);
}
processors.forEach(processor -> processor.prepare(context));
return renderValue(value, context, new IdentityHashMap<>());
}
@@ -103,7 +112,7 @@ public class JsonValueEnhancer {
for (PropertyMetadata metadata : getProperties(value.getClass())) {
Object propertyValue = metadata.getValue(value);
JsonFieldContext fieldContext = new JsonFieldContext(value, metadata.propertyName(), metadata.member(), propertyValue);
processors.forEach(processor -> processor.collect(fieldContext, context));
collectField(fieldContext, context);
collectValue(propertyValue, context, visited);
}
} finally {
@@ -111,6 +120,15 @@ public class JsonValueEnhancer {
}
}
private void collectField(JsonFieldContext fieldContext, JsonEnhancementContext context) {
for (JsonFieldProcessor processor : processors) {
if (processor.supports(fieldContext)) {
context.markProcessingRequired();
processor.collect(fieldContext, context);
}
}
}
private JsonNode renderValue(Object value, JsonEnhancementContext context, IdentityHashMap<Object, Boolean> visited) {
switch (value) {
case null -> {
@@ -120,27 +138,16 @@ public class JsonValueEnhancer {
return jsonNode;
}
case Map<?, ?> map -> {
ObjectNode objectNode = jsonMapper.createObjectNode();
map.forEach((key, childValue) -> objectNode.set(String.valueOf(key), renderValue(childValue, context, visited)));
return objectNode;
return renderMap(map, context, visited);
}
case Iterable<?> iterable -> {
ArrayNode arrayNode = jsonMapper.createArrayNode();
for (Object child : iterable) {
arrayNode.add(renderValue(child, context, visited));
}
return arrayNode;
return renderIterable(iterable, context, visited);
}
default -> {
}
}
if (value.getClass().isArray()) {
ArrayNode arrayNode = jsonMapper.createArrayNode();
int length = Array.getLength(value);
for (int i = 0; i < length; i++) {
arrayNode.add(renderValue(Array.get(value, i), context, visited));
}
return arrayNode;
return renderArray(value, context, visited);
}
if (isSimpleValue(value.getClass())) {
return jsonMapper.valueToTree(value);
@@ -149,28 +156,58 @@ public class JsonValueEnhancer {
return jsonMapper.valueToTree(value);
}
try {
ObjectNode objectNode = jsonMapper.createObjectNode();
for (PropertyMetadata metadata : getProperties(value.getClass())) {
Object originalValue = metadata.getValue(value);
JsonFieldContext fieldContext = new JsonFieldContext(value, metadata.propertyName(), metadata.member(), originalValue);
Object processedValue = originalValue;
boolean changed = false;
for (JsonFieldProcessor processor : processors) {
Object nextValue = processor.process(fieldContext, processedValue, context);
changed = changed || !Objects.equals(processedValue, nextValue);
processedValue = nextValue;
}
JsonNode childNode = changed
? enhanceTranslatedValue(processedValue, context, visited)
: renderValue(processedValue, context, visited);
objectNode.set(metadata.propertyName(), childNode);
}
return objectNode;
return renderPojo(value, context, visited);
} finally {
visited.remove(value);
}
}
private ObjectNode renderMap(Map<?, ?> map, JsonEnhancementContext context, IdentityHashMap<Object, Boolean> visited) {
ObjectNode objectNode = jsonMapper.createObjectNode();
map.forEach((key, childValue) -> objectNode.set(String.valueOf(key), renderValue(childValue, context, visited)));
return objectNode;
}
private ArrayNode renderIterable(Iterable<?> iterable, JsonEnhancementContext context, IdentityHashMap<Object, Boolean> visited) {
ArrayNode arrayNode = jsonMapper.createArrayNode();
for (Object child : iterable) {
arrayNode.add(renderValue(child, context, visited));
}
return arrayNode;
}
private ArrayNode renderArray(Object value, JsonEnhancementContext context, IdentityHashMap<Object, Boolean> visited) {
ArrayNode arrayNode = jsonMapper.createArrayNode();
int length = Array.getLength(value);
for (int i = 0; i < length; i++) {
arrayNode.add(renderValue(Array.get(value, i), context, visited));
}
return arrayNode;
}
private ObjectNode renderPojo(Object value, JsonEnhancementContext context, IdentityHashMap<Object, Boolean> visited) {
ObjectNode objectNode = jsonMapper.createObjectNode();
for (PropertyMetadata metadata : getProperties(value.getClass())) {
Object originalValue = metadata.getValue(value);
JsonFieldContext fieldContext = new JsonFieldContext(value, metadata.propertyName(), metadata.member(), originalValue);
Object processedValue = originalValue;
boolean changed = false;
for (JsonFieldProcessor processor : processors) {
if (!processor.supports(fieldContext)) {
continue;
}
Object nextValue = processor.process(fieldContext, processedValue, context);
changed = changed || !Objects.equals(processedValue, nextValue);
processedValue = nextValue;
}
JsonNode childNode = changed
? enhanceTranslatedValue(processedValue, context, visited)
: renderValue(processedValue, context, visited);
objectNode.set(metadata.propertyName(), childNode);
}
return objectNode;
}
private JsonNode enhanceTranslatedValue(Object value, JsonEnhancementContext context, IdentityHashMap<Object, Boolean> visited) {
if (value == null || value instanceof JsonNode || isSimpleValue(value.getClass())) {
return renderValue(value, context, visited);

View File

@@ -2,7 +2,6 @@ package org.dromara.common.json.handler;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import org.dromara.common.core.utils.ObjectUtils;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.ValueDeserializer;
@@ -25,10 +24,11 @@ public class CustomDateDeserializer extends ValueDeserializer<Date> {
*/
@Override
public Date deserialize(JsonParser p, DeserializationContext ctxt) {
DateTime parse = DateUtil.parse(p.getString());
if (ObjectUtils.isNull(parse)) {
String text = p.getString();
if (text == null || text.isBlank()) {
return null;
}
DateTime parse = DateUtil.parse(text.trim());
return parse.toJdkDate();
}

View File

@@ -19,6 +19,10 @@ import java.util.List;
*/
public class CustomLocalDateTimeDeserializer extends ValueDeserializer<LocalDateTime> {
private static final int SECOND_TIMESTAMP_LENGTH = 10;
private static final int MILLIS_TIMESTAMP_LENGTH = 13;
/** 支持时间的格式列表(直接解析为 LocalDateTime */
private static final List<DateTimeFormatter> DATETIME_FORMATTERS = List.of(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),
@@ -52,11 +56,10 @@ public class CustomLocalDateTimeDeserializer extends ValueDeserializer<LocalDate
}
text = text.trim();
// 纯数字:按时间戳处理(毫秒)
if (text.chars().allMatch(Character::isDigit)) {
return Instant.ofEpochMilli(Long.parseLong(text))
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
// 纯数字:支持秒级与毫秒级时间戳
LocalDateTime timestamp = parseTimestamp(text);
if (timestamp != null) {
return timestamp;
}
// 尝试带时间的格式
@@ -78,4 +81,27 @@ public class CustomLocalDateTimeDeserializer extends ValueDeserializer<LocalDate
return null;
}
private LocalDateTime parseTimestamp(String text) {
int startIndex = text.startsWith("-") ? 1 : 0;
if (startIndex == text.length()) {
return null;
}
for (int i = startIndex; i < text.length(); i++) {
if (!Character.isDigit(text.charAt(i))) {
return null;
}
}
int digitLength = text.length() - startIndex;
long timestamp = Long.parseLong(text);
Instant instant;
if (digitLength == SECOND_TIMESTAMP_LENGTH) {
instant = Instant.ofEpochSecond(timestamp);
} else if (digitLength == MILLIS_TIMESTAMP_LENGTH) {
instant = Instant.ofEpochMilli(timestamp);
} else {
return null;
}
return instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
}
}

View File

@@ -146,15 +146,7 @@ public class JsonUtils {
* @return true = 合法 JSONfalse = 非法或空
*/
public static boolean isJson(String str) {
if (StringUtils.isBlank(str)) {
return false;
}
try {
JSON_MAPPER.readTree(str);
return true;
} catch (Exception e) {
return false;
}
return readTreeQuietly(str) != null;
}
/**
@@ -164,15 +156,8 @@ public class JsonUtils {
* @return true = JSON 对象
*/
public static boolean isJsonObject(String str) {
if (StringUtils.isBlank(str)) {
return false;
}
try {
JsonNode node = JSON_MAPPER.readTree(str);
return node.isObject();
} catch (Exception e) {
return false;
}
JsonNode node = readTreeQuietly(str);
return node != null && node.isObject();
}
/**
@@ -185,11 +170,18 @@ public class JsonUtils {
if (StringUtils.isBlank(str)) {
return false;
}
JsonNode node = readTreeQuietly(str);
return node != null && node.isArray();
}
private static JsonNode readTreeQuietly(String str) {
if (StringUtils.isBlank(str)) {
return null;
}
try {
JsonNode node = JSON_MAPPER.readTree(str);
return node.isArray();
return JSON_MAPPER.readTree(str);
} catch (Exception e) {
return false;
return null;
}
}

View File

@@ -20,6 +20,11 @@ public class SensitiveJsonFieldProcessor implements JsonFieldProcessor {
@Autowired(required = false)
private SensitiveService sensitiveService;
@Override
public boolean supports(JsonFieldContext fieldContext) {
return fieldContext.getAnnotation(Sensitive.class) != null;
}
@Override
public Object process(JsonFieldContext fieldContext, Object value, JsonEnhancementContext context) {
Sensitive sensitive = fieldContext.getAnnotation(Sensitive.class);

View File

@@ -57,6 +57,17 @@ public class TranslationJsonFieldProcessor implements JsonFieldProcessor {
this.translationMap = Collections.unmodifiableMap(map);
}
/**
* 判断字段是否包含翻译注解。
*
* @param fieldContext 字段上下文
* @return true 需要处理 false 无需处理
*/
@Override
public boolean supports(JsonFieldContext fieldContext) {
return fieldContext.getAnnotation(Translation.class) != null;
}
/**
* 收集字段上的翻译注解和原始值,供后续批量翻译使用。
*
@@ -150,12 +161,7 @@ public class TranslationJsonFieldProcessor implements JsonFieldProcessor {
* @return 待批量翻译数据映射
*/
private Map<TranslationBatchKey, Set<Object>> getOrCreateBatches(JsonEnhancementContext context) {
Map<TranslationBatchKey, Set<Object>> batches = context.getAttribute(ATTR_BATCHES);
if (batches == null) {
batches = new LinkedHashMap<>();
context.setAttribute(ATTR_BATCHES, batches);
}
return batches;
return context.getOrCreateAttribute(ATTR_BATCHES, LinkedHashMap::new);
}
/**