diff --git a/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JsonEnhancementConfig.java b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JsonEnhancementConfig.java new file mode 100644 index 000000000..e572f2b9d --- /dev/null +++ b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JsonEnhancementConfig.java @@ -0,0 +1,22 @@ +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 { + + @Bean + public JsonValueEnhancer jsonValueEnhancer(JsonMapper jsonMapper, List processors) { + return new JsonValueEnhancer(jsonMapper, processors); + } + +} diff --git a/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/enhance/JsonEnhancementContext.java b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/enhance/JsonEnhancementContext.java new file mode 100644 index 000000000..30d0924d8 --- /dev/null +++ b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/enhance/JsonEnhancementContext.java @@ -0,0 +1,32 @@ +package org.dromara.common.json.enhance; + +import lombok.Getter; +import tools.jackson.databind.json.JsonMapper; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 单次响应增强上下文。 + */ +@Getter +public class JsonEnhancementContext { + + private final JsonMapper jsonMapper; + + private final Map attributes = new ConcurrentHashMap<>(); + + public JsonEnhancementContext(JsonMapper jsonMapper) { + this.jsonMapper = jsonMapper; + } + + @SuppressWarnings("unchecked") + public T getAttribute(String key) { + return (T) attributes.get(key); + } + + public void setAttribute(String key, Object value) { + attributes.put(key, value); + } + +} diff --git a/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/enhance/JsonFieldContext.java b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/enhance/JsonFieldContext.java new file mode 100644 index 000000000..c5fa81148 --- /dev/null +++ b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/enhance/JsonFieldContext.java @@ -0,0 +1,16 @@ +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) { + + public A getAnnotation(Class annotationType) { + return member == null ? null : member.getAnnotation(annotationType); + } + +} diff --git a/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/enhance/JsonFieldProcessor.java b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/enhance/JsonFieldProcessor.java new file mode 100644 index 000000000..fd3b2d3ec --- /dev/null +++ b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/enhance/JsonFieldProcessor.java @@ -0,0 +1,18 @@ +package org.dromara.common.json.enhance; + +/** + * 响应字段处理器。 + */ +public interface JsonFieldProcessor { + + default void collect(JsonFieldContext fieldContext, JsonEnhancementContext context) { + } + + default void prepare(JsonEnhancementContext context) { + } + + default Object process(JsonFieldContext fieldContext, Object value, JsonEnhancementContext context) { + return value; + } + +} diff --git a/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/enhance/JsonValueEnhancer.java b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/enhance/JsonValueEnhancer.java new file mode 100644 index 000000000..393696782 --- /dev/null +++ b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/enhance/JsonValueEnhancer.java @@ -0,0 +1,217 @@ +package org.dromara.common.json.enhance; + +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +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 { + + private final JsonMapper jsonMapper; + + private final List processors; + + private final Map, List> propertyCache = new ConcurrentHashMap<>(); + + public JsonValueEnhancer(JsonMapper jsonMapper, List processors) { + this.jsonMapper = jsonMapper; + List sortedProcessors = new ArrayList<>(processors); + AnnotationAwareOrderComparator.sort(sortedProcessors); + this.processors = Collections.unmodifiableList(sortedProcessors); + } + + public Object enhance(Object body) { + if (body == null || body instanceof JsonNode || processors.isEmpty()) { + return body; + } + return enhanceTree(body); + } + + public boolean supports(Class converterType) { + return !processors.isEmpty() + && !StringHttpMessageConverter.class.isAssignableFrom(converterType) + && !ResourceHttpMessageConverter.class.isAssignableFrom(converterType); + } + + private JsonNode enhanceTree(Object value) { + JsonEnhancementContext context = new JsonEnhancementContext(jsonMapper); + collectValue(value, context, new IdentityHashMap<>()); + processors.forEach(processor -> processor.prepare(context)); + return renderValue(value, context, new IdentityHashMap<>()); + } + + private void collectValue(Object value, JsonEnhancementContext context, IdentityHashMap visited) { + if (value == null) { + return; + } + if (value instanceof Map map) { + map.values().forEach(child -> collectValue(child, context, visited)); + return; + } + if (value instanceof Iterable iterable) { + iterable.forEach(child -> collectValue(child, context, visited)); + return; + } + 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); + processors.forEach(processor -> processor.collect(fieldContext, context)); + collectValue(propertyValue, context, visited); + } + } finally { + visited.remove(value); + } + } + + private JsonNode renderValue(Object value, JsonEnhancementContext context, IdentityHashMap visited) { + switch (value) { + case null -> { + return jsonMapper.nullNode(); + } + case JsonNode jsonNode -> { + return jsonNode; + } + case Map map -> { + ObjectNode objectNode = jsonMapper.createObjectNode(); + map.forEach((key, childValue) -> objectNode.set(String.valueOf(key), renderValue(childValue, context, visited))); + return objectNode; + } + case Iterable iterable -> { + ArrayNode arrayNode = jsonMapper.createArrayNode(); + for (Object child : iterable) { + arrayNode.add(renderValue(child, context, visited)); + } + return arrayNode; + } + 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; + } + if (isSimpleValue(value.getClass())) { + return jsonMapper.valueToTree(value); + } + if (visited.put(value, Boolean.TRUE) != null) { + return jsonMapper.valueToTree(value); + } + try { + ObjectNode objectNode = asObjectNode(jsonMapper.valueToTree(value)); + 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 { + visited.remove(value); + } + } + + private JsonNode enhanceTranslatedValue(Object value, JsonEnhancementContext context, IdentityHashMap visited) { + if (value == null || value instanceof JsonNode || isSimpleValue(value.getClass())) { + return renderValue(value, context, visited); + } + return enhanceTree(value); + } + + private ObjectNode asObjectNode(JsonNode node) { + if (node instanceof ObjectNode objectNode) { + return objectNode; + } + return jsonMapper.createObjectNode(); + } + + private List getProperties(Class type) { + return propertyCache.computeIfAbsent(type, this::resolveProperties); + } + + private List 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 definitions = classIntrospector.introspectForSerialization(javaType, annotatedClass).findProperties(); + List 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); + } + + 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; + } + + private record PropertyMetadata(String propertyName, AnnotatedMember member) { + + Object getValue(Object source) { + return member.getValue(source); + } + + } + +} diff --git a/ruoyi-common/ruoyi-common-json/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-json/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 162539720..1b92bdade 100644 --- a/ruoyi-common/ruoyi-common-json/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-json/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1,2 @@ org.dromara.common.json.config.JacksonConfig +org.dromara.common.json.config.JsonEnhancementConfig diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/annotation/Sensitive.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/annotation/Sensitive.java index 2506c89b0..79f7b338d 100644 --- a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/annotation/Sensitive.java +++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/annotation/Sensitive.java @@ -1,10 +1,8 @@ package org.dromara.common.sensitive.annotation; -import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import org.dromara.common.sensitive.core.SensitiveStrategy; -import org.dromara.common.sensitive.handler.SensitiveHandler; -import tools.jackson.databind.annotation.JsonSerialize; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -17,8 +15,7 @@ import java.lang.annotation.Target; */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) -@JacksonAnnotationsInside -@JsonSerialize(using = SensitiveHandler.class) +@Documented public @interface Sensitive { SensitiveStrategy strategy(); diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/config/SensitiveConfig.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/config/SensitiveConfig.java new file mode 100644 index 000000000..ac32211ee --- /dev/null +++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/config/SensitiveConfig.java @@ -0,0 +1,18 @@ +package org.dromara.common.sensitive.config; + +import org.dromara.common.sensitive.handler.SensitiveJsonFieldProcessor; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; + +/** + * 脱敏模块配置。 + */ +@AutoConfiguration +public class SensitiveConfig { + + @Bean + public SensitiveJsonFieldProcessor sensitiveJsonFieldProcessor() { + return new SensitiveJsonFieldProcessor(); + } + +} diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveHandler.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveHandler.java deleted file mode 100644 index 4c11af845..000000000 --- a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveHandler.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.dromara.common.sensitive.handler; - -import cn.hutool.core.util.ObjectUtil; -import lombok.extern.slf4j.Slf4j; -import org.dromara.common.core.utils.SpringUtils; -import org.dromara.common.sensitive.annotation.Sensitive; -import org.dromara.common.sensitive.core.SensitiveService; -import org.dromara.common.sensitive.core.SensitiveStrategy; -import org.springframework.beans.BeansException; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonGenerator; -import tools.jackson.databind.BeanProperty; -import tools.jackson.databind.SerializationContext; -import tools.jackson.databind.ValueSerializer; - -import java.util.Objects; - -/** - * 数据脱敏json序列化工具 - * - * @author Yjoioooo - */ -@Slf4j -public class SensitiveHandler extends ValueSerializer { - - private final SensitiveStrategy strategy; - private final String[] roleKey; - private final String[] perms; - - /** - * 提供给 jackson 创建上下文序列化器时使用 不然会报错 - */ - public SensitiveHandler() { - this.strategy = null; - this.roleKey = null; - this.perms = null; - } - - public SensitiveHandler(SensitiveStrategy strategy, String[] strings, String[] perms) { - this.strategy = strategy; - this.roleKey = strings; - this.perms = perms; - } - - @Override - public void serialize(String value, JsonGenerator gen, SerializationContext ctxt) throws JacksonException { - try { - SensitiveService sensitiveService = SpringUtils.getBean(SensitiveService.class); - if (ObjectUtil.isNotNull(sensitiveService) && sensitiveService.isSensitive(roleKey, perms)) { - gen.writeString(strategy.desensitizer().apply(value)); - } else { - gen.writeString(value); - } - } catch (BeansException e) { - log.error("脱敏实现不存在, 采用默认处理 => {}", e.getMessage()); - gen.writeString(value); - } - } - - @Override - public ValueSerializer createContextual(SerializationContext ctxt, BeanProperty property) { - Sensitive annotation = property.getAnnotation(Sensitive.class); - if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) { - return new SensitiveHandler(annotation.strategy(), annotation.roleKey(), annotation.perms()); - } - return super.createContextual(ctxt, property); - } -} diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveJsonFieldProcessor.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveJsonFieldProcessor.java new file mode 100644 index 000000000..735f7143f --- /dev/null +++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveJsonFieldProcessor.java @@ -0,0 +1,39 @@ +package org.dromara.common.sensitive.handler; + +import cn.hutool.core.util.ObjectUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.json.enhance.JsonEnhancementContext; +import org.dromara.common.json.enhance.JsonFieldContext; +import org.dromara.common.json.enhance.JsonFieldProcessor; +import org.dromara.common.sensitive.annotation.Sensitive; +import org.dromara.common.sensitive.core.SensitiveService; +import org.springframework.beans.BeansException; +import org.springframework.core.annotation.Order; + +/** + * 响应脱敏处理器。 + */ +@Slf4j +@Order(100) +public class SensitiveJsonFieldProcessor implements JsonFieldProcessor { + + @Override + public Object process(JsonFieldContext fieldContext, Object value, JsonEnhancementContext context) { + Sensitive sensitive = fieldContext.getAnnotation(Sensitive.class); + if (sensitive == null || !(value instanceof String text)) { + return value; + } + try { + SensitiveService sensitiveService = SpringUtils.getBean(SensitiveService.class); + if (ObjectUtil.isNotNull(sensitiveService) && sensitiveService.isSensitive(sensitive.roleKey(), sensitive.perms())) { + return sensitive.strategy().desensitizer().apply(text); + } + return text; + } catch (BeansException e) { + log.error("脱敏实现不存在, 采用默认处理 => {}", e.getMessage()); + return text; + } + } + +} diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-sensitive/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 000000000..d509c11d2 --- /dev/null +++ b/ruoyi-common/ruoyi-common-sensitive/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.dromara.common.sensitive.config.SensitiveConfig diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/Translation.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/Translation.java index 7a515e21f..64d4baa05 100644 --- a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/Translation.java +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/Translation.java @@ -1,10 +1,10 @@ package org.dromara.common.translation.annotation; -import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; -import org.dromara.common.translation.core.handler.TranslationHandler; -import tools.jackson.databind.annotation.JsonSerialize; - -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** * 通用翻译注解 @@ -13,8 +13,7 @@ import java.lang.annotation.*; */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD}) -@JacksonAnnotationsInside -@JsonSerialize(using = TranslationHandler.class) +@Documented public @interface Translation { /** diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/config/TranslationConfig.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/config/TranslationConfig.java index d1b52fdf6..b56776093 100644 --- a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/config/TranslationConfig.java +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/config/TranslationConfig.java @@ -1,54 +1,23 @@ package org.dromara.common.translation.config; -import jakarta.annotation.PostConstruct; -import lombok.extern.slf4j.Slf4j; -import org.dromara.common.translation.annotation.TranslationType; import org.dromara.common.translation.core.TranslationInterface; -import org.dromara.common.translation.core.handler.TranslationBeanSerializerModifier; -import org.dromara.common.translation.core.handler.TranslationHandler; -import org.springframework.beans.factory.annotation.Autowired; +import org.dromara.common.translation.core.handler.TranslationJsonFieldProcessor; import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer; import org.springframework.context.annotation.Bean; -import tools.jackson.databind.ser.SerializerFactory; -import java.util.HashMap; import java.util.List; -import java.util.Map; /** * 翻译模块配置类 * * @author Lion Li */ -@Slf4j @AutoConfiguration public class TranslationConfig { - @Autowired - private List> list; - - @PostConstruct - public void init() { - Map> map = new HashMap<>(list.size()); - for (TranslationInterface trans : list) { - if (trans.getClass().isAnnotationPresent(TranslationType.class)) { - TranslationType annotation = trans.getClass().getAnnotation(TranslationType.class); - map.put(annotation.type(), trans); - } else { - log.warn(trans.getClass().getName() + " 翻译实现类未标注 TranslationType 注解!"); - } - } - TranslationHandler.TRANSLATION_MAPPER.putAll(map); - } - @Bean - public JsonMapperBuilderCustomizer translationInitCustomizer() { - return builder -> { - SerializerFactory serializerFactory = builder.serializerFactory(); - serializerFactory = serializerFactory.withSerializerModifier(new TranslationBeanSerializerModifier()); - builder.serializerFactory(serializerFactory); - }; + public TranslationJsonFieldProcessor translationJsonFieldProcessor(List> list) { + return new TranslationJsonFieldProcessor(list); } } diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/TranslationInterface.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/TranslationInterface.java index 6ed223bb1..dc269938b 100644 --- a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/TranslationInterface.java +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/TranslationInterface.java @@ -1,7 +1,19 @@ package org.dromara.common.translation.core; +import cn.hutool.core.convert.Convert; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.StringUtils; import org.dromara.common.translation.annotation.TranslationType; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + /** * 翻译接口 (实现类需标注 {@link TranslationType} 注解标明翻译类型) * @@ -17,4 +29,65 @@ public interface TranslationInterface { * @return 返回键对应的翻译值 */ T translation(Object key, String other); + + /** + * 批量翻译。 + * + * @param keys 需要被翻译的键集合 + * @param other 其他参数 + * @return 翻译结果映射 + */ + default Map translationBatch(Set keys, String other) { + Map result = new LinkedHashMap<>(keys.size()); + for (Object key : keys) { + result.put(key, translation(key, other)); + } + return result; + } + + /** + * 收集 Long 类型键集合。 + * + * @param keys 原始键集合 + * @return Long 键集合 + */ + default Set collectLongIds(Collection keys) { + Set result = new LinkedHashSet<>(); + for (Object key : keys) { + if (key instanceof String ids) { + result.addAll(parseLongIds(ids)); + } else if (key != null) { + result.add(Convert.toLong(key)); + } + } + return result; + } + + /** + * 解析逗号分隔的 Long ID 列表。 + * + * @param ids 逗号分隔字符串 + * @return Long 列表 + */ + default List parseLongIds(String ids) { + return StreamUtils.toList( + StreamUtils.filter(Arrays.asList(ids.split(StringUtils.SEPARATOR)), StringUtils::isNotBlank), + value -> Convert.toLong(value.trim()) + ); + } + + /** + * 按原始 ID 顺序拼接映射值。 + * + * @param ids 原始 ID 字符串 + * @param mapper ID 到值的映射函数 + * @return 拼接后的结果字符串 + * @param 值类型 + */ + default String joinMappedValues(String ids, Function mapper) { + return StreamUtils.join(parseLongIds(ids), id -> { + E value = mapper.apply(id); + return value == null ? null : String.valueOf(value); + }); + } } diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationBeanSerializerModifier.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationBeanSerializerModifier.java deleted file mode 100644 index c18fd1ee5..000000000 --- a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationBeanSerializerModifier.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.dromara.common.translation.core.handler; - -import tools.jackson.databind.BeanDescription; -import tools.jackson.databind.SerializationConfig; -import tools.jackson.databind.ser.BeanPropertyWriter; -import tools.jackson.databind.ser.ValueSerializerModifier; - -import java.util.List; - -/** - * Bean 序列化修改器 解决 Null 被单独处理问题 - * - * @author Lion Li - */ -public class TranslationBeanSerializerModifier extends ValueSerializerModifier { - - /** - * 为翻译字段补充空值序列化器,确保字段值为 {@code null} 时仍能走翻译处理链。 - * - * @param config 当前序列化配置 - * @param beanDesc Bean 描述提供者 - * @param beanProperties 当前 Bean 的属性写入器列表 - * @return 调整后的属性写入器列表 - */ - @Override - public List changeProperties(SerializationConfig config, BeanDescription.Supplier beanDesc, - List beanProperties) { - for (BeanPropertyWriter writer : beanProperties) { - // 如果序列化器为 TranslationHandler 的话 将 Null 值也交给他处理 - if (writer.getSerializer() instanceof TranslationHandler serializer) { - writer.assignNullSerializer(serializer); - } - } - return super.changeProperties(config, beanDesc, beanProperties); - } - -} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationHandler.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationHandler.java deleted file mode 100644 index f840524f6..000000000 --- a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationHandler.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.dromara.common.translation.core.handler; - -import cn.hutool.core.util.ObjectUtil; -import lombok.extern.slf4j.Slf4j; -import org.dromara.common.core.utils.StringUtils; -import org.dromara.common.core.utils.reflect.ReflectUtils; -import org.dromara.common.translation.annotation.Translation; -import org.dromara.common.translation.core.TranslationInterface; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonGenerator; -import tools.jackson.databind.BeanProperty; -import tools.jackson.databind.SerializationContext; -import tools.jackson.databind.ValueSerializer; - -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 翻译处理器 - * - * @author Lion Li - */ -@Slf4j -public class TranslationHandler extends ValueSerializer { - - /** - * 全局翻译实现类映射器 - */ - public static final Map> TRANSLATION_MAPPER = new ConcurrentHashMap<>(); - - private final Translation translation; - - /** - * 提供给 jackson 创建上下文序列化器时使用 不然会报错 - */ - public TranslationHandler() { - this.translation = null; - } - - /** - * 创建绑定指定翻译注解的序列化处理器。 - * - * @param translation 当前字段上声明的翻译注解 - */ - public TranslationHandler(Translation translation) { - this.translation = translation; - } - - /** - * 将原始字段值翻译为展示值并写回序列化结果。 - * - * @param value 原始字段值 - * @param gen Json 输出器 - * @param ctxt 序列化上下文 - * @throws JacksonException Json 序列化异常 - */ - @Override - public void serialize(Object value, JsonGenerator gen, SerializationContext ctxt) throws JacksonException { - TranslationInterface trans = TRANSLATION_MAPPER.get(translation.type()); - if (ObjectUtil.isNotNull(trans)) { - // 如果映射字段不为空 则取映射字段的值 - if (StringUtils.isNotBlank(translation.mapper())) { - value = ReflectUtils.invokeGetter(gen.currentValue(), translation.mapper()); - } - // 如果为 null 直接写出 - if (ObjectUtil.isNull(value)) { - gen.writeNull(); - return; - } - try { - Object result = trans.translation(value, translation.other()); - gen.writePOJO(result); - } catch (Exception e) { - log.error("翻译处理异常,type: {}, value: {}", translation.type(), value, e); - // 出现异常时输出原始值而不是中断序列化 - gen.writePOJO(value); - } - } else { - gen.writePOJO(value); - } - } - - /** - * 按字段上的 {@link Translation} 注解创建上下文相关的翻译序列化器。 - * - * @param ctxt 序列化上下文 - * @param property 当前序列化属性 - * @return 存在翻译注解时返回新的翻译处理器,否则沿用默认序列化器 - */ - @Override - public ValueSerializer createContextual(SerializationContext ctxt, BeanProperty property) { - Translation translation = property.getAnnotation(Translation.class); - if (Objects.nonNull(translation)) { - return new TranslationHandler(translation); - } - return super.createContextual(ctxt, property); - } -} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationJsonFieldProcessor.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationJsonFieldProcessor.java new file mode 100644 index 000000000..9c695070e --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationJsonFieldProcessor.java @@ -0,0 +1,133 @@ +package org.dromara.common.translation.core.handler; + +import cn.hutool.core.util.ObjectUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.core.utils.reflect.ReflectUtils; +import org.dromara.common.json.enhance.JsonEnhancementContext; +import org.dromara.common.json.enhance.JsonFieldContext; +import org.dromara.common.json.enhance.JsonFieldProcessor; +import org.dromara.common.translation.annotation.Translation; +import org.dromara.common.translation.annotation.TranslationType; +import org.dromara.common.translation.core.TranslationInterface; +import org.springframework.core.annotation.Order; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * 翻译响应处理器。 + */ +@Slf4j +@Order(0) +@RequiredArgsConstructor +public class TranslationJsonFieldProcessor implements JsonFieldProcessor { + + private static final String ATTR_BATCHES = TranslationJsonFieldProcessor.class.getName() + ".batches"; + + private static final String ATTR_RESULTS = TranslationJsonFieldProcessor.class.getName() + ".results"; + + private final List> translations; + + @Override + public void collect(JsonFieldContext fieldContext, JsonEnhancementContext context) { + Translation translation = fieldContext.getAnnotation(Translation.class); + if (translation == null) { + return; + } + Object sourceValue = resolveSourceValue(fieldContext, translation); + if (sourceValue == null) { + return; + } + Map> batches = getOrCreateBatches(context); + batches.computeIfAbsent(new TranslationBatchKey(translation.type(), translation.other()), key -> new LinkedHashSet<>()) + .add(sourceValue); + } + + @Override + public void prepare(JsonEnhancementContext context) { + Map> batches = context.getAttribute(ATTR_BATCHES); + if (batches == null || batches.isEmpty()) { + return; + } + Map> results = new LinkedHashMap<>(batches.size()); + for (Map.Entry> entry : batches.entrySet()) { + TranslationInterface translation = getTranslation(entry.getKey().type()); + if (translation == null) { + continue; + } + try { + Map translated = translation.translationBatch(entry.getValue(), entry.getKey().other()); + results.put(entry.getKey(), new LinkedHashMap<>(translated)); + } catch (Exception e) { + log.error("批量翻译处理异常,type: {}, other: {}", entry.getKey().type(), entry.getKey().other(), e); + } + } + context.setAttribute(ATTR_RESULTS, results); + } + + @Override + public Object process(JsonFieldContext fieldContext, Object value, JsonEnhancementContext context) { + Translation translation = fieldContext.getAnnotation(Translation.class); + if (translation == null) { + return value; + } + Object sourceValue = resolveSourceValue(fieldContext, translation); + if (sourceValue == null) { + return null; + } + TranslationBatchKey batchKey = new TranslationBatchKey(translation.type(), translation.other()); + Map> results = context.getAttribute(ATTR_RESULTS); + if (results != null) { + Map translatedMap = results.get(batchKey); + if (translatedMap != null && translatedMap.containsKey(sourceValue)) { + return translatedMap.get(sourceValue); + } + } + TranslationInterface trans = getTranslation(translation.type()); + if (ObjectUtil.isNull(trans)) { + return value; + } + try { + return trans.translation(sourceValue, translation.other()); + } catch (Exception e) { + log.error("翻译处理异常,type: {}, value: {}", translation.type(), sourceValue, e); + return value; + } + } + + private Map> getOrCreateBatches(JsonEnhancementContext context) { + Map> batches = context.getAttribute(ATTR_BATCHES); + if (batches == null) { + batches = new LinkedHashMap<>(); + context.setAttribute(ATTR_BATCHES, batches); + } + return batches; + } + + private Object resolveSourceValue(JsonFieldContext fieldContext, Translation translation) { + if (StringUtils.isNotBlank(translation.mapper())) { + return ReflectUtils.invokeGetter(fieldContext.owner(), translation.mapper()); + } + return fieldContext.value(); + } + + private TranslationInterface getTranslation(String type) { + for (TranslationInterface translation : translations) { + TranslationType translationType = translation.getClass().getAnnotation(TranslationType.class); + if (translationType != null && Objects.equals(type, translationType.type())) { + return translation; + } + } + return null; + } + + private record TranslationBatchKey(String type, String other) { + } + +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DeptNameTranslationImpl.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DeptNameTranslationImpl.java index cc2ec7b05..77e1b1560 100644 --- a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DeptNameTranslationImpl.java +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DeptNameTranslationImpl.java @@ -1,10 +1,15 @@ package org.dromara.common.translation.core.impl; +import cn.hutool.core.convert.Convert; +import lombok.AllArgsConstructor; import org.dromara.common.core.service.DeptService; import org.dromara.common.translation.annotation.TranslationType; import org.dromara.common.translation.constant.TransConstant; import org.dromara.common.translation.core.TranslationInterface; -import lombok.AllArgsConstructor; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; /** * 部门翻译实现 @@ -33,4 +38,25 @@ public class DeptNameTranslationImpl implements TranslationInterface { } return null; } + + @Override + public Map translationBatch(Set keys, String other) { + Set deptIds = collectLongIds(keys); + if (deptIds.isEmpty()) { + return Map.of(); + } + Map deptNames = deptService.selectDeptNamesByIds(deptIds); + Map result = new LinkedHashMap<>(keys.size()); + for (Object key : keys) { + result.put(key, buildValue(key, deptNames)); + } + return result; + } + + private String buildValue(Object source, Map deptNames) { + if (source instanceof String ids) { + return joinMappedValues(ids, deptNames::get); + } + return source == null ? null : deptNames.get(Convert.toLong(source)); + } } diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DictTypeTranslationImpl.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DictTypeTranslationImpl.java index 95ff5ffa3..799d5f9f1 100644 --- a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DictTypeTranslationImpl.java +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DictTypeTranslationImpl.java @@ -1,11 +1,17 @@ package org.dromara.common.translation.core.impl; +import lombok.AllArgsConstructor; import org.dromara.common.core.service.DictService; +import org.dromara.common.core.utils.StreamUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.translation.annotation.TranslationType; import org.dromara.common.translation.constant.TransConstant; import org.dromara.common.translation.core.TranslationInterface; -import lombok.AllArgsConstructor; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; /** * 字典翻译实现 @@ -32,4 +38,22 @@ public class DictTypeTranslationImpl implements TranslationInterface { } return null; } + + @Override + public Map translationBatch(Set keys, String other) { + if (keys.isEmpty() || StringUtils.isBlank(other)) { + return Map.of(); + } + Map dictMap = dictService.getAllDictByDictType(other); + Map result = new LinkedHashMap<>(keys.size()); + for (Object key : keys) { + if (key instanceof String dictValue) { + result.put(key, StreamUtils.join( + StreamUtils.filter(Arrays.asList(dictValue.split(",")), StringUtils::isNotBlank), + value -> dictMap.get(value.trim()) + )); + } + } + return result; + } } diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/NicknameTranslationImpl.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/NicknameTranslationImpl.java index 1aef7e319..18f7977d8 100644 --- a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/NicknameTranslationImpl.java +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/NicknameTranslationImpl.java @@ -1,11 +1,16 @@ package org.dromara.common.translation.core.impl; +import cn.hutool.core.convert.Convert; import lombok.AllArgsConstructor; import org.dromara.common.core.service.UserService; import org.dromara.common.translation.annotation.TranslationType; import org.dromara.common.translation.constant.TransConstant; import org.dromara.common.translation.core.TranslationInterface; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + /** * 用户昵称翻译实现 * @@ -33,4 +38,25 @@ public class NicknameTranslationImpl implements TranslationInterface { } return null; } + + @Override + public Map translationBatch(Set keys, String other) { + Set userIds = collectLongIds(keys); + if (userIds.isEmpty()) { + return Map.of(); + } + Map userNames = userService.selectUserNicksByIds(userIds); + Map result = new LinkedHashMap<>(keys.size()); + for (Object key : keys) { + result.put(key, buildValue(key, userNames)); + } + return result; + } + + private String buildValue(Object source, Map userNames) { + if (source instanceof String ids) { + return joinMappedValues(ids, userNames::get); + } + return source == null ? null : userNames.get(Convert.toLong(source)); + } } diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/OssUrlTranslationImpl.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/OssUrlTranslationImpl.java index a3b59aafb..bfc59f02b 100644 --- a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/OssUrlTranslationImpl.java +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/OssUrlTranslationImpl.java @@ -1,10 +1,18 @@ package org.dromara.common.translation.core.impl; +import cn.hutool.core.convert.Convert; +import lombok.AllArgsConstructor; +import org.dromara.common.core.domain.dto.OssDTO; import org.dromara.common.core.service.OssService; +import org.dromara.common.core.utils.StreamUtils; import org.dromara.common.translation.annotation.TranslationType; import org.dromara.common.translation.constant.TransConstant; import org.dromara.common.translation.core.TranslationInterface; -import lombok.AllArgsConstructor; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; /** * OSS翻译实现 @@ -33,4 +41,26 @@ public class OssUrlTranslationImpl implements TranslationInterface { } return null; } + + @Override + public Map translationBatch(Set keys, String other) { + Set ossIds = collectLongIds(keys); + if (ossIds.isEmpty()) { + return Map.of(); + } + String idText = ossIds.stream().map(String::valueOf).collect(Collectors.joining(",")); + Map ossUrls = new LinkedHashMap<>(StreamUtils.toMap(ossService.selectByIds(idText), OssDTO::getOssId, OssDTO::getUrl)); + Map result = new LinkedHashMap<>(keys.size()); + for (Object key : keys) { + result.put(key, buildValue(key, ossUrls)); + } + return result; + } + + private String buildValue(Object source, Map ossUrls) { + if (source instanceof String ids) { + return joinMappedValues(ids, ossUrls::get); + } + return source == null ? null : ossUrls.get(Convert.toLong(source)); + } } diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/UserNameTranslationImpl.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/UserNameTranslationImpl.java index 5a59511ad..a39549e5c 100644 --- a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/UserNameTranslationImpl.java +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/UserNameTranslationImpl.java @@ -1,11 +1,17 @@ package org.dromara.common.translation.core.impl; import cn.hutool.core.convert.Convert; +import lombok.AllArgsConstructor; +import org.dromara.common.core.domain.dto.UserDTO; import org.dromara.common.core.service.UserService; +import org.dromara.common.core.utils.StreamUtils; import org.dromara.common.translation.annotation.TranslationType; import org.dromara.common.translation.constant.TransConstant; import org.dromara.common.translation.core.TranslationInterface; -import lombok.AllArgsConstructor; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; /** * 用户名翻译实现 @@ -29,4 +35,25 @@ public class UserNameTranslationImpl implements TranslationInterface { public String translation(Object key, String other) { return userService.selectUserNameById(Convert.toLong(key)); } + + @Override + public Map translationBatch(Set keys, String other) { + Set userIds = collectLongIds(keys); + if (userIds.isEmpty()) { + return Map.of(); + } + Map userNames = new LinkedHashMap<>(StreamUtils.toMap(userService.selectListByIds(userIds), UserDTO::getUserId, UserDTO::getUserName)); + Map result = new LinkedHashMap<>(keys.size()); + for (Object key : keys) { + result.put(key, buildValue(key, userNames)); + } + return result; + } + + private String buildValue(Object source, Map userNames) { + if (source instanceof String ids) { + return joinMappedValues(ids, userNames::get); + } + return userNames.get(Convert.toLong(source)); + } } diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/advice/ResponseEnhancementAdvice.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/advice/ResponseEnhancementAdvice.java new file mode 100644 index 000000000..f694aea31 --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/advice/ResponseEnhancementAdvice.java @@ -0,0 +1,42 @@ +package org.dromara.common.web.advice; + +import lombok.RequiredArgsConstructor; +import org.dromara.common.json.enhance.JsonValueEnhancer; +import org.jspecify.annotations.NonNull; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +/** + * 响应体统一增强拦截器。 + */ +@RestControllerAdvice +@RequiredArgsConstructor +public class ResponseEnhancementAdvice implements ResponseBodyAdvice { + + private final JsonValueEnhancer jsonValueEnhancer; + + @Override + public boolean supports(@NonNull MethodParameter returnType, + @NonNull Class> converterType) { + return jsonValueEnhancer.supports(converterType); + } + + @Override + public Object beforeBodyWrite(Object body, + @NonNull MethodParameter returnType, + @NonNull MediaType selectedContentType, + @NonNull Class> selectedConverterType, + @NonNull ServerHttpRequest request, + @NonNull ServerHttpResponse response) { + if (!selectedContentType.isCompatibleWith(MediaType.APPLICATION_JSON)) { + return body; + } + return jsonValueEnhancer.enhance(body); + } + +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/ResourcesConfig.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/ResourcesConfig.java index f5527d69b..c73309ba3 100644 --- a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/ResourcesConfig.java +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/ResourcesConfig.java @@ -3,6 +3,8 @@ package org.dromara.common.web.config; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import org.dromara.common.core.utils.ObjectUtils; +import org.dromara.common.json.enhance.JsonValueEnhancer; +import org.dromara.common.web.advice.ResponseEnhancementAdvice; import org.dromara.common.web.handler.GlobalExceptionHandler; import org.dromara.common.web.interceptor.PlusWebInvokeTimeInterceptor; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -95,4 +97,10 @@ public class ResourcesConfig implements WebMvcConfigurer { public GlobalExceptionHandler globalExceptionHandler() { return new GlobalExceptionHandler(); } + + @Bean + public ResponseEnhancementAdvice responseEnhancementAdvice(JsonValueEnhancer jsonValueEnhancer) { + return new ResponseEnhancementAdvice(jsonValueEnhancer); + } + }