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
@@ -5,6 +5,7 @@ import tools.jackson.databind.json.JsonMapper;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; 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 final Map<String, Object> attributes = new LinkedHashMap<>();
private boolean processingRequired;
/** /**
* 构造响应增强上下文。 * 构造响应增强上下文。
* *
@@ -37,6 +40,20 @@ public class JsonEnhancementContext {
return (T) attributes.get(key); 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); 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;
}
} }
@@ -20,6 +20,14 @@ package org.dromara.common.json.enhance;
*/ */
public interface JsonFieldProcessor { public interface JsonFieldProcessor {
/**
* 判断当前处理器是否需要处理该字段。
* 默认返回 true 以兼容无注解驱动的自定义处理器。
*/
default boolean supports(JsonFieldContext fieldContext) {
return true;
}
/** /**
* 收集阶段:扫描字段,将需要处理的 key 存入 context。 * 收集阶段:扫描字段,将需要处理的 key 存入 context。
* 每个字段调用一次,整个对象树扫描完成后才会进入 prepare 阶段。 * 每个字段调用一次,整个对象树扫描完成后才会进入 prepare 阶段。
@@ -54,7 +54,13 @@ public class JsonValueEnhancer {
if (body == null || body instanceof JsonNode || processors.isEmpty()) { if (body == null || body instanceof JsonNode || processors.isEmpty()) {
return body; 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) { private JsonNode enhanceTree(Object value) {
JsonEnhancementContext context = new JsonEnhancementContext(jsonMapper); JsonEnhancementContext context = new JsonEnhancementContext(jsonMapper);
collectValue(value, context, new IdentityHashMap<>()); collectValue(value, context, new IdentityHashMap<>());
if (!context.isProcessingRequired()) {
return jsonMapper.valueToTree(value);
}
processors.forEach(processor -> processor.prepare(context)); processors.forEach(processor -> processor.prepare(context));
return renderValue(value, context, new IdentityHashMap<>()); return renderValue(value, context, new IdentityHashMap<>());
} }
@@ -103,7 +112,7 @@ public class JsonValueEnhancer {
for (PropertyMetadata metadata : getProperties(value.getClass())) { for (PropertyMetadata metadata : getProperties(value.getClass())) {
Object propertyValue = metadata.getValue(value); Object propertyValue = metadata.getValue(value);
JsonFieldContext fieldContext = new JsonFieldContext(value, metadata.propertyName(), metadata.member(), propertyValue); JsonFieldContext fieldContext = new JsonFieldContext(value, metadata.propertyName(), metadata.member(), propertyValue);
processors.forEach(processor -> processor.collect(fieldContext, context)); collectField(fieldContext, context);
collectValue(propertyValue, context, visited); collectValue(propertyValue, context, visited);
} }
} finally { } 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) { private JsonNode renderValue(Object value, JsonEnhancementContext context, IdentityHashMap<Object, Boolean> visited) {
switch (value) { switch (value) {
case null -> { case null -> {
@@ -120,27 +138,16 @@ public class JsonValueEnhancer {
return jsonNode; return jsonNode;
} }
case Map<?, ?> map -> { case Map<?, ?> map -> {
ObjectNode objectNode = jsonMapper.createObjectNode(); return renderMap(map, context, visited);
map.forEach((key, childValue) -> objectNode.set(String.valueOf(key), renderValue(childValue, context, visited)));
return objectNode;
} }
case Iterable<?> iterable -> { case Iterable<?> iterable -> {
ArrayNode arrayNode = jsonMapper.createArrayNode(); return renderIterable(iterable, context, visited);
for (Object child : iterable) {
arrayNode.add(renderValue(child, context, visited));
}
return arrayNode;
} }
default -> { default -> {
} }
} }
if (value.getClass().isArray()) { if (value.getClass().isArray()) {
ArrayNode arrayNode = jsonMapper.createArrayNode(); return renderArray(value, context, visited);
int length = Array.getLength(value);
for (int i = 0; i < length; i++) {
arrayNode.add(renderValue(Array.get(value, i), context, visited));
}
return arrayNode;
} }
if (isSimpleValue(value.getClass())) { if (isSimpleValue(value.getClass())) {
return jsonMapper.valueToTree(value); return jsonMapper.valueToTree(value);
@@ -149,28 +156,58 @@ public class JsonValueEnhancer {
return jsonMapper.valueToTree(value); return jsonMapper.valueToTree(value);
} }
try { try {
ObjectNode objectNode = jsonMapper.createObjectNode(); return renderPojo(value, context, visited);
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;
} finally { } finally {
visited.remove(value); 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) { private JsonNode enhanceTranslatedValue(Object value, JsonEnhancementContext context, IdentityHashMap<Object, Boolean> visited) {
if (value == null || value instanceof JsonNode || isSimpleValue(value.getClass())) { if (value == null || value instanceof JsonNode || isSimpleValue(value.getClass())) {
return renderValue(value, context, visited); return renderValue(value, context, visited);
@@ -2,7 +2,6 @@ package org.dromara.common.json.handler;
import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import org.dromara.common.core.utils.ObjectUtils;
import tools.jackson.core.JsonParser; import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext; import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.ValueDeserializer; import tools.jackson.databind.ValueDeserializer;
@@ -25,10 +24,11 @@ public class CustomDateDeserializer extends ValueDeserializer<Date> {
*/ */
@Override @Override
public Date deserialize(JsonParser p, DeserializationContext ctxt) { public Date deserialize(JsonParser p, DeserializationContext ctxt) {
DateTime parse = DateUtil.parse(p.getString()); String text = p.getString();
if (ObjectUtils.isNull(parse)) { if (text == null || text.isBlank()) {
return null; return null;
} }
DateTime parse = DateUtil.parse(text.trim());
return parse.toJdkDate(); return parse.toJdkDate();
} }
@@ -19,6 +19,10 @@ import java.util.List;
*/ */
public class CustomLocalDateTimeDeserializer extends ValueDeserializer<LocalDateTime> { public class CustomLocalDateTimeDeserializer extends ValueDeserializer<LocalDateTime> {
private static final int SECOND_TIMESTAMP_LENGTH = 10;
private static final int MILLIS_TIMESTAMP_LENGTH = 13;
/** 支持时间的格式列表(直接解析为 LocalDateTime */ /** 支持时间的格式列表(直接解析为 LocalDateTime */
private static final List<DateTimeFormatter> DATETIME_FORMATTERS = List.of( private static final List<DateTimeFormatter> DATETIME_FORMATTERS = List.of(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),
@@ -52,11 +56,10 @@ public class CustomLocalDateTimeDeserializer extends ValueDeserializer<LocalDate
} }
text = text.trim(); text = text.trim();
// 纯数字:按时间戳处理(毫秒) // 纯数字:支持秒级与毫秒级时间戳
if (text.chars().allMatch(Character::isDigit)) { LocalDateTime timestamp = parseTimestamp(text);
return Instant.ofEpochMilli(Long.parseLong(text)) if (timestamp != null) {
.atZone(ZoneId.systemDefault()) return timestamp;
.toLocalDateTime();
} }
// 尝试带时间的格式 // 尝试带时间的格式
@@ -78,4 +81,27 @@ public class CustomLocalDateTimeDeserializer extends ValueDeserializer<LocalDate
return null; 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();
}
} }
@@ -146,15 +146,7 @@ public class JsonUtils {
* @return true = 合法 JSONfalse = 非法或空 * @return true = 合法 JSONfalse = 非法或空
*/ */
public static boolean isJson(String str) { public static boolean isJson(String str) {
if (StringUtils.isBlank(str)) { return readTreeQuietly(str) != null;
return false;
}
try {
JSON_MAPPER.readTree(str);
return true;
} catch (Exception e) {
return false;
}
} }
/** /**
@@ -164,15 +156,8 @@ public class JsonUtils {
* @return true = JSON 对象 * @return true = JSON 对象
*/ */
public static boolean isJsonObject(String str) { public static boolean isJsonObject(String str) {
if (StringUtils.isBlank(str)) { JsonNode node = readTreeQuietly(str);
return false; return node != null && node.isObject();
}
try {
JsonNode node = JSON_MAPPER.readTree(str);
return node.isObject();
} catch (Exception e) {
return false;
}
} }
/** /**
@@ -185,11 +170,18 @@ public class JsonUtils {
if (StringUtils.isBlank(str)) { if (StringUtils.isBlank(str)) {
return false; return false;
} }
JsonNode node = readTreeQuietly(str);
return node != null && node.isArray();
}
private static JsonNode readTreeQuietly(String str) {
if (StringUtils.isBlank(str)) {
return null;
}
try { try {
JsonNode node = JSON_MAPPER.readTree(str); return JSON_MAPPER.readTree(str);
return node.isArray();
} catch (Exception e) { } catch (Exception e) {
return false; return null;
} }
} }
@@ -20,6 +20,11 @@ public class SensitiveJsonFieldProcessor implements JsonFieldProcessor {
@Autowired(required = false) @Autowired(required = false)
private SensitiveService sensitiveService; private SensitiveService sensitiveService;
@Override
public boolean supports(JsonFieldContext fieldContext) {
return fieldContext.getAnnotation(Sensitive.class) != null;
}
@Override @Override
public Object process(JsonFieldContext fieldContext, Object value, JsonEnhancementContext context) { public Object process(JsonFieldContext fieldContext, Object value, JsonEnhancementContext context) {
Sensitive sensitive = fieldContext.getAnnotation(Sensitive.class); Sensitive sensitive = fieldContext.getAnnotation(Sensitive.class);
@@ -57,6 +57,17 @@ public class TranslationJsonFieldProcessor implements JsonFieldProcessor {
this.translationMap = Collections.unmodifiableMap(map); 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 待批量翻译数据映射 * @return 待批量翻译数据映射
*/ */
private Map<TranslationBatchKey, Set<Object>> getOrCreateBatches(JsonEnhancementContext context) { private Map<TranslationBatchKey, Set<Object>> getOrCreateBatches(JsonEnhancementContext context) {
Map<TranslationBatchKey, Set<Object>> batches = context.getAttribute(ATTR_BATCHES); return context.getOrCreateAttribute(ATTR_BATCHES, LinkedHashMap::new);
if (batches == null) {
batches = new LinkedHashMap<>();
context.setAttribute(ATTR_BATCHES, batches);
}
return batches;
} }
/** /**