mirror of
https://github.com/dromara/RuoYi-Vue-Plus.git
synced 2026-06-11 00:06:15 +00:00
refactor(common-json): 优化 JSON 增强处理链路
- 抽取 JSON 字符串校验的重复解析逻辑 - 为 JsonFieldProcessor 增加 supports 判断,减少无关字段处理 - 优化 JsonValueEnhancer,未命中增强字段时直接返回原对象 - 拆分响应增强渲染逻辑,提升代码可读性 - 增强上下文属性管理能力 - 优化翻译、脱敏处理器的字段匹配逻辑 - 修正 Date 反序列化空字符串处理 - 支持 LocalDateTime 秒级、毫秒级及负时间戳反序列化
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,14 @@ package org.dromara.common.json.enhance;
|
||||
*/
|
||||
public interface JsonFieldProcessor {
|
||||
|
||||
/**
|
||||
* 判断当前处理器是否需要处理该字段。
|
||||
* 默认返回 true 以兼容无注解驱动的自定义处理器。
|
||||
*/
|
||||
default boolean supports(JsonFieldContext fieldContext) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集阶段:扫描字段,将需要处理的 key 存入 context。
|
||||
* 每个字段调用一次,整个对象树扫描完成后才会进入 prepare 阶段。
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -146,15 +146,7 @@ public class JsonUtils {
|
||||
* @return true = 合法 JSON,false = 非法或空
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user