mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-04-01 00:03:25 +08:00
[重大更新] 重写翻译和脱敏实现 使用jackson tree解析加ResponseBodyAdvice处理数据的方案 实现可批量翻译大幅度提高效率 用法与灵活性不变
This commit is contained in:
@@ -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<JsonFieldProcessor> processors) {
|
||||
return new JsonValueEnhancer(jsonMapper, processors);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String, Object> attributes = new ConcurrentHashMap<>();
|
||||
|
||||
public JsonEnhancementContext(JsonMapper jsonMapper) {
|
||||
this.jsonMapper = jsonMapper;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getAttribute(String key) {
|
||||
return (T) attributes.get(key);
|
||||
}
|
||||
|
||||
public void setAttribute(String key, Object value) {
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 extends Annotation> A getAnnotation(Class<A> annotationType) {
|
||||
return member == null ? null : member.getAnnotation(annotationType);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<JsonFieldProcessor> processors;
|
||||
|
||||
private final Map<Class<?>, List<PropertyMetadata>> propertyCache = new ConcurrentHashMap<>();
|
||||
|
||||
public JsonValueEnhancer(JsonMapper jsonMapper, List<JsonFieldProcessor> processors) {
|
||||
this.jsonMapper = jsonMapper;
|
||||
List<JsonFieldProcessor> 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<Object, Boolean> 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<Object, Boolean> 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<Object, Boolean> 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<PropertyMetadata> getProperties(Class<?> type) {
|
||||
return propertyCache.computeIfAbsent(type, this::resolveProperties);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
org.dromara.common.json.config.JacksonConfig
|
||||
org.dromara.common.json.config.JsonEnhancementConfig
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String> {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.dromara.common.sensitive.config.SensitiveConfig
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<TranslationInterface<?>> list;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
Map<String, TranslationInterface<?>> 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<TranslationInterface<?>> list) {
|
||||
return new TranslationJsonFieldProcessor(list);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<T> {
|
||||
* @return 返回键对应的翻译值
|
||||
*/
|
||||
T translation(Object key, String other);
|
||||
|
||||
/**
|
||||
* 批量翻译。
|
||||
*
|
||||
* @param keys 需要被翻译的键集合
|
||||
* @param other 其他参数
|
||||
* @return 翻译结果映射
|
||||
*/
|
||||
default Map<Object, T> translationBatch(Set<Object> keys, String other) {
|
||||
Map<Object, T> result = new LinkedHashMap<>(keys.size());
|
||||
for (Object key : keys) {
|
||||
result.put(key, translation(key, other));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集 Long 类型键集合。
|
||||
*
|
||||
* @param keys 原始键集合
|
||||
* @return Long 键集合
|
||||
*/
|
||||
default Set<Long> collectLongIds(Collection<Object> keys) {
|
||||
Set<Long> 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<Long> 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 <E> 值类型
|
||||
*/
|
||||
default <E> String joinMappedValues(String ids, Function<Long, E> mapper) {
|
||||
return StreamUtils.join(parseLongIds(ids), id -> {
|
||||
E value = mapper.apply(id);
|
||||
return value == null ? null : String.valueOf(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription.Supplier beanDesc,
|
||||
List<BeanPropertyWriter> beanProperties) {
|
||||
for (BeanPropertyWriter writer : beanProperties) {
|
||||
// 如果序列化器为 TranslationHandler 的话 将 Null 值也交给他处理
|
||||
if (writer.getSerializer() instanceof TranslationHandler serializer) {
|
||||
writer.assignNullSerializer(serializer);
|
||||
}
|
||||
}
|
||||
return super.changeProperties(config, beanDesc, beanProperties);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Object> {
|
||||
|
||||
/**
|
||||
* 全局翻译实现类映射器
|
||||
*/
|
||||
public static final Map<String, TranslationInterface<?>> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<TranslationInterface<?>> 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<TranslationBatchKey, Set<Object>> batches = getOrCreateBatches(context);
|
||||
batches.computeIfAbsent(new TranslationBatchKey(translation.type(), translation.other()), key -> new LinkedHashSet<>())
|
||||
.add(sourceValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(JsonEnhancementContext context) {
|
||||
Map<TranslationBatchKey, Set<Object>> batches = context.getAttribute(ATTR_BATCHES);
|
||||
if (batches == null || batches.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<TranslationBatchKey, Map<Object, Object>> results = new LinkedHashMap<>(batches.size());
|
||||
for (Map.Entry<TranslationBatchKey, Set<Object>> entry : batches.entrySet()) {
|
||||
TranslationInterface<?> translation = getTranslation(entry.getKey().type());
|
||||
if (translation == null) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
Map<Object, ?> 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<TranslationBatchKey, Map<Object, Object>> results = context.getAttribute(ATTR_RESULTS);
|
||||
if (results != null) {
|
||||
Map<Object, Object> 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<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;
|
||||
}
|
||||
|
||||
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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String> {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, String> translationBatch(Set<Object> keys, String other) {
|
||||
Set<Long> deptIds = collectLongIds(keys);
|
||||
if (deptIds.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
Map<Long, String> deptNames = deptService.selectDeptNamesByIds(deptIds);
|
||||
Map<Object, String> result = new LinkedHashMap<>(keys.size());
|
||||
for (Object key : keys) {
|
||||
result.put(key, buildValue(key, deptNames));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String buildValue(Object source, Map<Long, String> deptNames) {
|
||||
if (source instanceof String ids) {
|
||||
return joinMappedValues(ids, deptNames::get);
|
||||
}
|
||||
return source == null ? null : deptNames.get(Convert.toLong(source));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, String> translationBatch(Set<Object> keys, String other) {
|
||||
if (keys.isEmpty() || StringUtils.isBlank(other)) {
|
||||
return Map.of();
|
||||
}
|
||||
Map<String, String> dictMap = dictService.getAllDictByDictType(other);
|
||||
Map<Object, String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, String> translationBatch(Set<Object> keys, String other) {
|
||||
Set<Long> userIds = collectLongIds(keys);
|
||||
if (userIds.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
Map<Long, String> userNames = userService.selectUserNicksByIds(userIds);
|
||||
Map<Object, String> result = new LinkedHashMap<>(keys.size());
|
||||
for (Object key : keys) {
|
||||
result.put(key, buildValue(key, userNames));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String buildValue(Object source, Map<Long, String> userNames) {
|
||||
if (source instanceof String ids) {
|
||||
return joinMappedValues(ids, userNames::get);
|
||||
}
|
||||
return source == null ? null : userNames.get(Convert.toLong(source));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, String> translationBatch(Set<Object> keys, String other) {
|
||||
Set<Long> ossIds = collectLongIds(keys);
|
||||
if (ossIds.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
String idText = ossIds.stream().map(String::valueOf).collect(Collectors.joining(","));
|
||||
Map<Long, String> ossUrls = new LinkedHashMap<>(StreamUtils.toMap(ossService.selectByIds(idText), OssDTO::getOssId, OssDTO::getUrl));
|
||||
Map<Object, String> result = new LinkedHashMap<>(keys.size());
|
||||
for (Object key : keys) {
|
||||
result.put(key, buildValue(key, ossUrls));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String buildValue(Object source, Map<Long, String> ossUrls) {
|
||||
if (source instanceof String ids) {
|
||||
return joinMappedValues(ids, ossUrls::get);
|
||||
}
|
||||
return source == null ? null : ossUrls.get(Convert.toLong(source));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> {
|
||||
public String translation(Object key, String other) {
|
||||
return userService.selectUserNameById(Convert.toLong(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, String> translationBatch(Set<Object> keys, String other) {
|
||||
Set<Long> userIds = collectLongIds(keys);
|
||||
if (userIds.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
Map<Long, String> userNames = new LinkedHashMap<>(StreamUtils.toMap(userService.selectListByIds(userIds), UserDTO::getUserId, UserDTO::getUserName));
|
||||
Map<Object, String> result = new LinkedHashMap<>(keys.size());
|
||||
for (Object key : keys) {
|
||||
result.put(key, buildValue(key, userNames));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String buildValue(Object source, Map<Long, String> userNames) {
|
||||
if (source instanceof String ids) {
|
||||
return joinMappedValues(ids, userNames::get);
|
||||
}
|
||||
return userNames.get(Convert.toLong(source));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Object> {
|
||||
|
||||
private final JsonValueEnhancer jsonValueEnhancer;
|
||||
|
||||
@Override
|
||||
public boolean supports(@NonNull MethodParameter returnType,
|
||||
@NonNull Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
return jsonValueEnhancer.supports(converterType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object beforeBodyWrite(Object body,
|
||||
@NonNull MethodParameter returnType,
|
||||
@NonNull MediaType selectedContentType,
|
||||
@NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
|
||||
@NonNull ServerHttpRequest request,
|
||||
@NonNull ServerHttpResponse response) {
|
||||
if (!selectedContentType.isCompatibleWith(MediaType.APPLICATION_JSON)) {
|
||||
return body;
|
||||
}
|
||||
return jsonValueEnhancer.enhance(body);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user