init: 导入RuoYi‑Vue‑Plus 6.X完整代码
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>ruoyi-common-json</artifactId>
|
||||
|
||||
<description>
|
||||
ruoyi-common-json 序列化模块
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson 序列化 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jackson</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package org.dromara.common.json.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.json.handler.BigNumberSerializer;
|
||||
import org.dromara.common.json.handler.CustomDateDeserializer;
|
||||
import org.dromara.common.json.handler.CustomLocalDateTimeDeserializer;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import tools.jackson.databind.ext.javatime.ser.LocalDateTimeSerializer;
|
||||
import tools.jackson.databind.module.SimpleModule;
|
||||
import tools.jackson.databind.ser.std.ToStringSerializer;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* jackson 配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
@AutoConfiguration(before = JacksonAutoConfiguration.class)
|
||||
public class JacksonConfig {
|
||||
|
||||
/**
|
||||
* 注册 Jackson 序列化与反序列化模块。
|
||||
*
|
||||
* @return Jackson 模块
|
||||
*/
|
||||
@Bean
|
||||
public SimpleModule registerJavaTimeModule() {
|
||||
// 全局配置序列化返回 JSON 处理
|
||||
SimpleModule module = new SimpleModule();
|
||||
module.addSerializer(Long.class, BigNumberSerializer.INSTANCE);
|
||||
module.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE);
|
||||
module.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE);
|
||||
module.addSerializer(BigDecimal.class, ToStringSerializer.instance);
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
|
||||
module.addDeserializer(LocalDateTime.class, new CustomLocalDateTimeDeserializer());
|
||||
module.addDeserializer(Date.class, new CustomDateDeserializer());
|
||||
return module;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 Jackson 构建器默认配置。
|
||||
*
|
||||
* @return Jackson 构建器自定义器
|
||||
*/
|
||||
@Bean
|
||||
public JsonMapperBuilderCustomizer jsonInitCustomizer() {
|
||||
return builder -> {
|
||||
builder.defaultTimeZone(TimeZone.getDefault());
|
||||
log.info("初始化 jackson 配置");
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package org.dromara.common.json.config;
|
||||
|
||||
import org.dromara.common.json.enhance.JsonFieldProcessor;
|
||||
import org.dromara.common.json.enhance.JsonValueEnhancer;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 响应增强核心配置。
|
||||
*/
|
||||
@AutoConfiguration
|
||||
public class JsonEnhancementConfig {
|
||||
|
||||
/**
|
||||
* 创建 JSON 字段增强处理器入口。
|
||||
*
|
||||
* @param jsonMapper JSON 映射器
|
||||
* @param processors 字段处理器集合
|
||||
* @return JSON 值增强器
|
||||
*/
|
||||
@Bean
|
||||
public JsonValueEnhancer jsonValueEnhancer(JsonMapper jsonMapper, List<JsonFieldProcessor> processors) {
|
||||
return new JsonValueEnhancer(jsonMapper, processors);
|
||||
}
|
||||
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package org.dromara.common.json.enhance;
|
||||
|
||||
import lombok.Getter;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 单次响应增强上下文。
|
||||
*/
|
||||
@Getter
|
||||
public class JsonEnhancementContext {
|
||||
|
||||
private final JsonMapper jsonMapper;
|
||||
|
||||
private final Map<String, Object> attributes = new LinkedHashMap<>();
|
||||
|
||||
private boolean processingRequired;
|
||||
|
||||
/**
|
||||
* 构造响应增强上下文。
|
||||
*
|
||||
* @param jsonMapper JSON 映射器
|
||||
*/
|
||||
public JsonEnhancementContext(JsonMapper jsonMapper) {
|
||||
this.jsonMapper = jsonMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上下文属性。
|
||||
*
|
||||
* @param key 属性键
|
||||
* @param <T> 属性值类型
|
||||
* @return 属性值
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getAttribute(String 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置上下文属性。
|
||||
*
|
||||
* @param key 属性键
|
||||
* @param value 属性值
|
||||
*/
|
||||
public void setAttribute(String key, Object 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;
|
||||
}
|
||||
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package org.dromara.common.json.enhance;
|
||||
|
||||
import tools.jackson.databind.introspect.AnnotatedMember;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
/**
|
||||
* 响应字段上下文。
|
||||
*/
|
||||
public record JsonFieldContext(Object owner, String propertyName, AnnotatedMember member, Object value) {
|
||||
|
||||
/**
|
||||
* 获取字段上的指定注解。
|
||||
*
|
||||
* @param annotationType 注解类型
|
||||
* @param <A> 注解类型
|
||||
* @return 注解对象
|
||||
*/
|
||||
public <A extends Annotation> A getAnnotation(Class<A> annotationType) {
|
||||
return member == null ? null : member.getAnnotation(annotationType);
|
||||
}
|
||||
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package org.dromara.common.json.enhance;
|
||||
|
||||
/**
|
||||
* 响应字段处理器。
|
||||
*
|
||||
* <p>生命周期按顺序分为三个阶段,由 {@link JsonValueEnhancer} 统一驱动:
|
||||
* <ol>
|
||||
* <li><b>collect</b>(收集阶段):递归扫描响应对象树时,对每个字段调用一次。
|
||||
* 用于采集需要处理的字段 key,存入 {@link JsonEnhancementContext} 供下一阶段批量处理。</li>
|
||||
* <li><b>prepare</b>(预处理阶段):collect 全部完成后调用一次。
|
||||
* 用于执行批量 IO(如批量查询数据库),将结果写入 {@link JsonEnhancementContext}。
|
||||
* 此阶段应消除 N+1 查询问题。</li>
|
||||
* <li><b>process</b>(处理阶段):渲染响应 JSON 树时,对每个字段调用一次。
|
||||
* 从 {@link JsonEnhancementContext} 取出 prepare 阶段写入的结果,返回替换后的字段值。
|
||||
* 返回原 {@code value} 表示不修改;返回 {@code null} 表示将字段值置为 null。</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>实现类通过 {@link JsonEnhancementContext#setAttribute} / {@link JsonEnhancementContext#getAttribute}
|
||||
* 在三个阶段之间共享数据,建议以实现类全限定名作为 attribute key 前缀以避免冲突。
|
||||
*/
|
||||
public interface JsonFieldProcessor {
|
||||
|
||||
/**
|
||||
* 判断当前处理器是否需要处理该字段。
|
||||
* 默认返回 true 以兼容无注解驱动的自定义处理器。
|
||||
*/
|
||||
default boolean supports(JsonFieldContext fieldContext) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集阶段:扫描字段,将需要处理的 key 存入 context。
|
||||
* 每个字段调用一次,整个对象树扫描完成后才会进入 prepare 阶段。
|
||||
*/
|
||||
default void collect(JsonFieldContext fieldContext, JsonEnhancementContext context) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 预处理阶段:基于 collect 阶段收集到的数据执行批量处理(如批量查询),结果写入 context。
|
||||
* 每次响应只调用一次。
|
||||
*/
|
||||
default void prepare(JsonEnhancementContext context) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理阶段:根据 prepare 阶段写入的结果,对字段值进行替换并返回。
|
||||
* 返回原 {@code value} 表示不修改该字段;返回 {@code null} 表示将字段值置为 null。
|
||||
*/
|
||||
default Object process(JsonFieldContext fieldContext, Object value, JsonEnhancementContext context) {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
+377
@@ -0,0 +1,377 @@
|
||||
package org.dromara.common.json.enhance;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
|
||||
import org.springframework.http.converter.ResourceHttpMessageConverter;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
import tools.jackson.databind.JavaType;
|
||||
import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.SerializationConfig;
|
||||
import tools.jackson.databind.introspect.AnnotatedClass;
|
||||
import tools.jackson.databind.introspect.AnnotatedMember;
|
||||
import tools.jackson.databind.introspect.BeanPropertyDefinition;
|
||||
import tools.jackson.databind.introspect.ClassIntrospector;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
import tools.jackson.databind.node.ArrayNode;
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.time.temporal.Temporal;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 统一响应增强器,支持在出站前执行翻译、脱敏等字段处理。
|
||||
*/
|
||||
public class JsonValueEnhancer {
|
||||
|
||||
/**
|
||||
* JSON 映射器。
|
||||
*/
|
||||
private final JsonMapper jsonMapper;
|
||||
|
||||
/**
|
||||
* 字段增强处理器列表。
|
||||
*/
|
||||
private final List<JsonFieldProcessor> processors;
|
||||
|
||||
/**
|
||||
* 类型属性元数据缓存。
|
||||
*/
|
||||
private final Map<Class<?>, List<PropertyMetadata>> propertyCache = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 构造统一响应增强器。
|
||||
*
|
||||
* @param jsonMapper JSON 映射器
|
||||
* @param processors 字段处理器列表
|
||||
*/
|
||||
public JsonValueEnhancer(JsonMapper jsonMapper, List<JsonFieldProcessor> processors) {
|
||||
this.jsonMapper = jsonMapper;
|
||||
List<JsonFieldProcessor> sortedProcessors = new ArrayList<>(processors);
|
||||
AnnotationAwareOrderComparator.sort(sortedProcessors);
|
||||
this.processors = Collections.unmodifiableList(sortedProcessors);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增强响应对象。
|
||||
*
|
||||
* @param body 响应对象
|
||||
* @return 增强后的响应对象
|
||||
*/
|
||||
public Object enhance(Object body) {
|
||||
if (body == null || body instanceof JsonNode || processors.isEmpty()) {
|
||||
return 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<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断消息转换器是否支持响应增强。
|
||||
*
|
||||
* @param converterType 消息转换器类型
|
||||
* @return true 支持 false 不支持
|
||||
*/
|
||||
public boolean supports(Class<?> converterType) {
|
||||
return !processors.isEmpty()
|
||||
&& !ByteArrayHttpMessageConverter.class.isAssignableFrom(converterType)
|
||||
&& !StringHttpMessageConverter.class.isAssignableFrom(converterType)
|
||||
&& !ResourceHttpMessageConverter.class.isAssignableFrom(converterType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对已处理后的对象再次执行树形增强。
|
||||
*
|
||||
* @param value 待增强对象
|
||||
* @return 增强后的 JSON 节点
|
||||
*/
|
||||
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<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归收集对象中需要增强的字段信息。
|
||||
*
|
||||
* @param value 当前对象
|
||||
* @param context 增强上下文
|
||||
* @param visited 已访问对象集合,用于避免循环引用
|
||||
*/
|
||||
private void collectValue(Object value, JsonEnhancementContext context, IdentityHashMap<Object, Boolean> visited) {
|
||||
switch (value) {
|
||||
case null -> {
|
||||
return;
|
||||
}
|
||||
case Map<?, ?> map -> {
|
||||
map.values().forEach(child -> collectValue(child, context, visited));
|
||||
return;
|
||||
}
|
||||
case Iterable<?> iterable -> {
|
||||
iterable.forEach(child -> collectValue(child, context, visited));
|
||||
return;
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
if (value.getClass().isArray()) {
|
||||
int length = Array.getLength(value);
|
||||
for (int i = 0; i < length; i++) {
|
||||
collectValue(Array.get(value, i), context, visited);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isSimpleValue(value.getClass()) || visited.put(value, Boolean.TRUE) != null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
for (PropertyMetadata metadata : getProperties(value.getClass())) {
|
||||
Object propertyValue = metadata.getValue(value);
|
||||
JsonFieldContext fieldContext = new JsonFieldContext(value, metadata.propertyName(), metadata.member(), propertyValue);
|
||||
collectField(fieldContext, context);
|
||||
collectValue(propertyValue, context, visited);
|
||||
}
|
||||
} finally {
|
||||
visited.remove(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集单个字段的增强信息。
|
||||
*
|
||||
* @param fieldContext 字段上下文
|
||||
* @param context 增强上下文
|
||||
*/
|
||||
private void collectField(JsonFieldContext fieldContext, JsonEnhancementContext context) {
|
||||
for (JsonFieldProcessor processor : processors) {
|
||||
if (processor.supports(fieldContext)) {
|
||||
context.markProcessingRequired();
|
||||
processor.collect(fieldContext, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象渲染为增强后的 JSON 节点。
|
||||
*
|
||||
* @param value 当前对象
|
||||
* @param context 增强上下文
|
||||
* @param visited 已访问对象集合,用于避免循环引用
|
||||
* @return JSON 节点
|
||||
*/
|
||||
private JsonNode renderValue(Object value, JsonEnhancementContext context, IdentityHashMap<Object, Boolean> visited) {
|
||||
switch (value) {
|
||||
case null -> {
|
||||
return jsonMapper.nullNode();
|
||||
}
|
||||
case JsonNode jsonNode -> {
|
||||
return jsonNode;
|
||||
}
|
||||
case Map<?, ?> map -> {
|
||||
return renderMap(map, context, visited);
|
||||
}
|
||||
case Iterable<?> iterable -> {
|
||||
return renderIterable(iterable, context, visited);
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
if (value.getClass().isArray()) {
|
||||
return renderArray(value, context, visited);
|
||||
}
|
||||
if (isSimpleValue(value.getClass())) {
|
||||
return jsonMapper.valueToTree(value);
|
||||
}
|
||||
if (visited.put(value, Boolean.TRUE) != null) {
|
||||
return jsonMapper.valueToTree(value);
|
||||
}
|
||||
try {
|
||||
return renderPojo(value, context, visited);
|
||||
} finally {
|
||||
visited.remove(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染 Map 对象。
|
||||
*
|
||||
* @param map Map 对象
|
||||
* @param context 增强上下文
|
||||
* @param visited 已访问对象集合
|
||||
* @return 对象节点
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染可迭代对象。
|
||||
*
|
||||
* @param iterable 可迭代对象
|
||||
* @param context 增强上下文
|
||||
* @param visited 已访问对象集合
|
||||
* @return 数组节点
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染数组对象。
|
||||
*
|
||||
* @param value 数组对象
|
||||
* @param context 增强上下文
|
||||
* @param visited 已访问对象集合
|
||||
* @return 数组节点
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染普通 Java 对象。
|
||||
*
|
||||
* @param value Java 对象
|
||||
* @param context 增强上下文
|
||||
* @param visited 已访问对象集合
|
||||
* @return 对象节点
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对字段处理后得到的复杂对象执行二次增强。
|
||||
*
|
||||
* @param value 字段处理后的值
|
||||
* @param context 增强上下文
|
||||
* @param visited 已访问对象集合
|
||||
* @return JSON 节点
|
||||
*/
|
||||
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);
|
||||
}
|
||||
return enhanceTree(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型可序列化属性元数据。
|
||||
*
|
||||
* @param type 类型
|
||||
* @return 属性元数据列表
|
||||
*/
|
||||
private List<PropertyMetadata> getProperties(Class<?> type) {
|
||||
return propertyCache.computeIfAbsent(type, this::resolveProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析指定类型可序列化属性元数据。
|
||||
*
|
||||
* @param type 类型
|
||||
* @return 属性元数据列表
|
||||
*/
|
||||
private List<PropertyMetadata> resolveProperties(Class<?> type) {
|
||||
if (isSimpleValue(type) || type.isArray() || Map.class.isAssignableFrom(type) || Iterable.class.isAssignableFrom(type)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
JavaType javaType = jsonMapper.constructType(type);
|
||||
SerializationConfig config = jsonMapper.serializationConfig();
|
||||
ClassIntrospector classIntrospector = config.classIntrospectorInstance().forOperation(config);
|
||||
AnnotatedClass annotatedClass = classIntrospector.introspectClassAnnotations(javaType);
|
||||
List<BeanPropertyDefinition> definitions = classIntrospector.introspectForSerialization(javaType, annotatedClass).findProperties();
|
||||
List<PropertyMetadata> properties = new ArrayList<>(definitions.size());
|
||||
for (BeanPropertyDefinition definition : definitions) {
|
||||
AnnotatedMember member = definition.getAccessor();
|
||||
if (member == null) {
|
||||
member = definition.getField();
|
||||
}
|
||||
if (member == null) {
|
||||
continue;
|
||||
}
|
||||
member.fixAccess(true);
|
||||
properties.add(new PropertyMetadata(definition.getName(), member));
|
||||
}
|
||||
return Collections.unmodifiableList(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断类型是否为简单值类型。
|
||||
*
|
||||
* @param type 类型
|
||||
* @return true 简单值 false 复杂对象
|
||||
*/
|
||||
private boolean isSimpleValue(Class<?> type) {
|
||||
return type.isPrimitive()
|
||||
|| CharSequence.class.isAssignableFrom(type)
|
||||
|| Number.class.isAssignableFrom(type)
|
||||
|| Boolean.class == type
|
||||
|| Character.class == type
|
||||
|| Date.class.isAssignableFrom(type)
|
||||
|| Temporal.class.isAssignableFrom(type)
|
||||
|| Enum.class.isAssignableFrom(type)
|
||||
|| UUID.class.isAssignableFrom(type)
|
||||
|| Class.class == type;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON 属性元数据。
|
||||
*
|
||||
* @param propertyName 属性名称
|
||||
* @param member Jackson 属性成员
|
||||
*/
|
||||
private record PropertyMetadata(String propertyName, AnnotatedMember member) {
|
||||
|
||||
/**
|
||||
* 从源对象读取属性值。
|
||||
*
|
||||
* @param source 源对象
|
||||
* @return 属性值
|
||||
*/
|
||||
Object getValue(Object source) {
|
||||
return member.getValue(source);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package org.dromara.common.json.handler;
|
||||
|
||||
import tools.jackson.core.JsonGenerator;
|
||||
import tools.jackson.databind.SerializationContext;
|
||||
import tools.jackson.databind.annotation.JacksonStdImpl;
|
||||
import tools.jackson.databind.ser.jdk.NumberSerializer;
|
||||
|
||||
/**
|
||||
* 超出 JS 最大最小值 处理
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@JacksonStdImpl
|
||||
public class BigNumberSerializer extends NumberSerializer {
|
||||
|
||||
/**
|
||||
* 提供实例
|
||||
*/
|
||||
public static final BigNumberSerializer INSTANCE = new BigNumberSerializer(Number.class);
|
||||
/**
|
||||
* 根据 JS Number.MAX_SAFE_INTEGER 与 Number.MIN_SAFE_INTEGER 得来
|
||||
*/
|
||||
private static final long MAX_SAFE_INTEGER = 9007199254740991L;
|
||||
/**
|
||||
* JavaScript 最小安全整数。
|
||||
*/
|
||||
private static final long MIN_SAFE_INTEGER = -9007199254740991L;
|
||||
|
||||
/**
|
||||
* 构造大数字序列化器。
|
||||
*
|
||||
* @param rawType 数字类型
|
||||
*/
|
||||
public BigNumberSerializer(Class<? extends Number> rawType) {
|
||||
super(rawType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化数字,超出 JS 安全整数范围时输出字符串。
|
||||
*
|
||||
* @param value 数字值
|
||||
* @param gen JSON 生成器
|
||||
* @param provider 序列化上下文
|
||||
*/
|
||||
@Override
|
||||
public void serialize(Number value, JsonGenerator gen, SerializationContext provider) {
|
||||
// 超出范围 序列化为字符串
|
||||
if (value.longValue() >= MIN_SAFE_INTEGER && value.longValue() <= MAX_SAFE_INTEGER) {
|
||||
super.serialize(value, gen, provider);
|
||||
} else {
|
||||
gen.writeString(value.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package org.dromara.common.json.handler;
|
||||
|
||||
import cn.hutool.core.date.DateTime;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import tools.jackson.core.JsonParser;
|
||||
import tools.jackson.databind.DeserializationContext;
|
||||
import tools.jackson.databind.ValueDeserializer;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 自定义 Date 类型反序列化处理器(支持多种格式)
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
public class CustomDateDeserializer extends ValueDeserializer<Date> {
|
||||
|
||||
/**
|
||||
* 反序列化逻辑:将字符串转换为 Date 对象
|
||||
*
|
||||
* @param p JSON 解析器,用于获取字符串值
|
||||
* @param ctxt 上下文环境(可用于获取更多配置)
|
||||
* @return 转换后的 Date 对象,若为空字符串返回 null
|
||||
*/
|
||||
@Override
|
||||
public Date deserialize(JsonParser p, DeserializationContext ctxt) {
|
||||
String text = p.getString();
|
||||
if (text == null || text.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
DateTime parse = DateUtil.parse(text.trim());
|
||||
return parse.toJdkDate();
|
||||
}
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package org.dromara.common.json.handler;
|
||||
|
||||
import cn.hutool.core.date.DateTime;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import tools.jackson.core.JsonParser;
|
||||
import tools.jackson.databind.DeserializationContext;
|
||||
import tools.jackson.databind.ValueDeserializer;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 自定义 LocalDateTime 类型反序列化处理器
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
public class CustomLocalDateTimeDeserializer extends ValueDeserializer<LocalDateTime> {
|
||||
|
||||
/**
|
||||
* 反序列化逻辑:将字符串转换为 LocalDateTime 对象
|
||||
*
|
||||
* @param p JSON 解析器,用于获取字符串值
|
||||
* @param ctxt 上下文环境(可用于获取更多配置)
|
||||
* @return 转换后的 LocalDateTime 对象,若为空字符串返回 null
|
||||
*/
|
||||
@Override
|
||||
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) {
|
||||
String text = p.getString();
|
||||
if (text == null || text.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
DateTime parse = DateUtil.parse(text.trim());
|
||||
return parse.toLocalDateTime();
|
||||
}
|
||||
|
||||
}
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
package org.dromara.common.json.utils;
|
||||
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import tools.jackson.core.type.TypeReference;
|
||||
import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* JSON 工具类
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class JsonUtils {
|
||||
|
||||
/**
|
||||
* 全局 JSON 映射器。
|
||||
*/
|
||||
private static final JsonMapper JSON_MAPPER = SpringUtils.getBean(JsonMapper.class);
|
||||
|
||||
/**
|
||||
* 获取全局 JsonMapper 实例。
|
||||
*
|
||||
* @return JsonMapper
|
||||
*/
|
||||
public static JsonMapper getJsonMapper() {
|
||||
return JSON_MAPPER;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象转换为JSON格式的字符串
|
||||
*
|
||||
* @param object 要转换的对象
|
||||
* @return JSON格式的字符串,如果对象为null,则返回null
|
||||
* @throws RuntimeException 如果转换过程中发生JSON处理异常,则抛出运行时异常
|
||||
*/
|
||||
public static String toJsonString(Object object) {
|
||||
if (ObjectUtil.isNull(object)) {
|
||||
return null;
|
||||
}
|
||||
return JSON_MAPPER.writeValueAsString(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将JSON格式的字符串转换为指定类型的对象
|
||||
*
|
||||
* @param text JSON格式的字符串
|
||||
* @param clazz 要转换的目标对象类型
|
||||
* @param <T> 目标对象的泛型类型
|
||||
* @return 转换后的对象,如果字符串为空则返回null
|
||||
* @throws RuntimeException 如果转换过程中发生IO异常,则抛出运行时异常
|
||||
*/
|
||||
public static <T> T parseObject(String text, Class<T> clazz) {
|
||||
if (StringUtils.isEmpty(text)) {
|
||||
return null;
|
||||
}
|
||||
return JSON_MAPPER.readValue(text, clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字节数组转换为指定类型的对象
|
||||
*
|
||||
* @param bytes 字节数组
|
||||
* @param clazz 要转换的目标对象类型
|
||||
* @param <T> 目标对象的泛型类型
|
||||
* @return 转换后的对象,如果字节数组为空则返回null
|
||||
* @throws RuntimeException 如果转换过程中发生IO异常,则抛出运行时异常
|
||||
*/
|
||||
public static <T> T parseObject(byte[] bytes, Class<T> clazz) {
|
||||
if (ArrayUtil.isEmpty(bytes)) {
|
||||
return null;
|
||||
}
|
||||
return JSON_MAPPER.readValue(bytes, clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将JSON格式的字符串转换为指定类型的对象,支持复杂类型
|
||||
*
|
||||
* @param text JSON格式的字符串
|
||||
* @param typeReference 指定类型的TypeReference对象
|
||||
* @param <T> 目标对象的泛型类型
|
||||
* @return 转换后的对象,如果字符串为空则返回null
|
||||
* @throws RuntimeException 如果转换过程中发生IO异常,则抛出运行时异常
|
||||
*/
|
||||
public static <T> T parseObject(String text, TypeReference<T> typeReference) {
|
||||
if (StringUtils.isBlank(text)) {
|
||||
return null;
|
||||
}
|
||||
return JSON_MAPPER.readValue(text, typeReference);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将JSON格式的字符串转换为Dict对象
|
||||
*
|
||||
* @param text JSON格式的字符串
|
||||
* @return 转换后的Dict对象,如果字符串为空或者不是JSON格式则返回null
|
||||
* @throws RuntimeException 如果转换过程中发生IO异常,则抛出运行时异常
|
||||
*/
|
||||
public static Dict parseMap(String text) {
|
||||
if (StringUtils.isBlank(text)) {
|
||||
return null;
|
||||
}
|
||||
return JSON_MAPPER.readValue(text, JSON_MAPPER.getTypeFactory().constructType(Dict.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将JSON格式的字符串转换为Dict对象的列表
|
||||
*
|
||||
* @param text JSON格式的字符串
|
||||
* @return 转换后的Dict对象的列表,如果字符串为空则返回null
|
||||
* @throws RuntimeException 如果转换过程中发生IO异常,则抛出运行时异常
|
||||
*/
|
||||
public static List<Dict> parseArrayMap(String text) {
|
||||
if (StringUtils.isBlank(text)) {
|
||||
return null;
|
||||
}
|
||||
return JSON_MAPPER.readValue(text, JSON_MAPPER.getTypeFactory().constructCollectionType(List.class, Dict.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将JSON格式的字符串转换为指定类型对象的列表
|
||||
*
|
||||
* @param text JSON格式的字符串
|
||||
* @param clazz 要转换的目标对象类型
|
||||
* @param <T> 目标对象的泛型类型
|
||||
* @return 转换后的对象的列表,如果字符串为空则返回空列表
|
||||
* @throws RuntimeException 如果转换过程中发生IO异常,则抛出运行时异常
|
||||
*/
|
||||
public static <T> List<T> parseArray(String text, Class<T> clazz) {
|
||||
if (StringUtils.isEmpty(text)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return JSON_MAPPER.readValue(text, JSON_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象转换为 JSON 字符串,并递归移除指定字段。
|
||||
*
|
||||
* @param object 要转换的对象
|
||||
* @param fieldNames 需要移除的字段名
|
||||
* @return 移除字段后的 JSON 字符串
|
||||
*/
|
||||
public static String toJsonStringExcludeFields(Object object, String... fieldNames) {
|
||||
if (ObjectUtil.isNull(object)) {
|
||||
return null;
|
||||
}
|
||||
JsonNode node = JSON_MAPPER.valueToTree(object);
|
||||
removeFields(node, fieldNames);
|
||||
return toJsonString(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 JSON 树中递归移除指定字段。
|
||||
*
|
||||
* @param node JSON 节点
|
||||
* @param fieldNames 需要移除的字段名
|
||||
* @return 原 JSON 节点
|
||||
*/
|
||||
public static JsonNode removeFields(JsonNode node, String... fieldNames) {
|
||||
if (node == null || ArrayUtil.isEmpty(fieldNames)) {
|
||||
return node;
|
||||
}
|
||||
if (node.isObject()) {
|
||||
ObjectNode objectNode = (ObjectNode) node;
|
||||
for (String fieldName : fieldNames) {
|
||||
if (StringUtils.isNotBlank(fieldName)) {
|
||||
objectNode.remove(fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (JsonNode child : node) {
|
||||
removeFields(child, fieldNames);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否为合法 JSON(对象或数组)
|
||||
*
|
||||
* @param str 待校验字符串
|
||||
* @return true = 合法 JSON,false = 非法或空
|
||||
*/
|
||||
public static boolean isJson(String str) {
|
||||
return readTreeQuietly(str) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否为 JSON 对象({})
|
||||
*
|
||||
* @param str 待校验字符串
|
||||
* @return true = JSON 对象
|
||||
*/
|
||||
public static boolean isJsonObject(String str) {
|
||||
JsonNode node = readTreeQuietly(str);
|
||||
return node != null && node.isObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否为 JSON 数组([])
|
||||
*
|
||||
* @param str 待校验字符串
|
||||
* @return true = JSON 数组
|
||||
*/
|
||||
public static boolean isJsonArray(String str) {
|
||||
if (StringUtils.isBlank(str)) {
|
||||
return false;
|
||||
}
|
||||
JsonNode node = readTreeQuietly(str);
|
||||
return node != null && node.isArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 安静读取 JSON 树,解析失败时返回 null。
|
||||
*
|
||||
* @param str JSON 字符串
|
||||
* @return JSON 节点,解析失败或空字符串时返回 null
|
||||
*/
|
||||
private static JsonNode readTreeQuietly(String str) {
|
||||
if (StringUtils.isBlank(str)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return JSON_MAPPER.readTree(str);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package org.dromara.common.json.validate;
|
||||
|
||||
import jakarta.validation.Constraint;
|
||||
import jakarta.validation.Payload;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* JSON 格式校验注解
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
@Documented
|
||||
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Constraint(validatedBy = JsonPatternValidator.class)
|
||||
public @interface JsonPattern {
|
||||
|
||||
/**
|
||||
* 限制 JSON 类型,默认为 {@link JsonType#ANY},即对象或数组都允许
|
||||
*/
|
||||
JsonType type() default JsonType.ANY;
|
||||
|
||||
/**
|
||||
* 校验失败时的提示消息
|
||||
*/
|
||||
String message() default "不是有效的 JSON 格式";
|
||||
|
||||
/**
|
||||
* Bean Validation 分组。
|
||||
*/
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
/**
|
||||
* Bean Validation 负载信息。
|
||||
*/
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package org.dromara.common.json.validate;
|
||||
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
|
||||
/**
|
||||
* JSON 格式校验器
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
public class JsonPatternValidator implements ConstraintValidator<JsonPattern, String> {
|
||||
|
||||
/**
|
||||
* 注解中指定的 JSON 类型枚举
|
||||
*/
|
||||
private JsonType jsonType;
|
||||
|
||||
/**
|
||||
* 初始化校验器,从注解中提取 JSON 类型
|
||||
*
|
||||
* @param annotation 注解实例
|
||||
*/
|
||||
@Override
|
||||
public void initialize(JsonPattern annotation) {
|
||||
this.jsonType = annotation.type();
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验字符串是否为合法 JSON
|
||||
*
|
||||
* @param value 待校验字符串
|
||||
* @param context 校验上下文,可用于自定义错误信息
|
||||
* @return true = 合法 JSON 或为空,false = 非法 JSON
|
||||
*/
|
||||
@Override
|
||||
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||||
if (StringUtils.isBlank(value)) {
|
||||
// 交给 @NotBlank 或 @NotNull 控制是否允许为空
|
||||
return true;
|
||||
}
|
||||
// 根据 JSON 类型进行不同的校验
|
||||
return switch (jsonType) {
|
||||
case ANY -> JsonUtils.isJson(value);
|
||||
case OBJECT -> JsonUtils.isJsonObject(value);
|
||||
case ARRAY -> JsonUtils.isJsonArray(value);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package org.dromara.common.json.validate;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* JSON 类型枚举
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum JsonType {
|
||||
|
||||
/**
|
||||
* JSON 对象,例如 {"a":1}
|
||||
*/
|
||||
OBJECT,
|
||||
|
||||
/**
|
||||
* JSON 数组,例如 [1,2,3]
|
||||
*/
|
||||
ARRAY,
|
||||
|
||||
/**
|
||||
* 任意 JSON 类型,对象或数组都可以
|
||||
*/
|
||||
ANY
|
||||
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
org.dromara.common.json.config.JacksonConfig
|
||||
org.dromara.common.json.config.JsonEnhancementConfig
|
||||
Reference in New Issue
Block a user