Browse Source

jsr303支持i18n

zhou-hao 3 years ago
parent
commit
f3808589db
19 changed files with 251 additions and 97 deletions
  1. 34 15
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/ValidateEventListener.java
  2. 10 6
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/CommonErrorControllerAdvice.java
  3. 5 1
      hsweb-core/pom.xml
  4. 2 7
      hsweb-core/src/main/java/org/hswebframework/web/dict/EnumDict.java
  5. 5 1
      hsweb-core/src/main/java/org/hswebframework/web/exception/BusinessException.java
  6. 12 3
      hsweb-core/src/main/java/org/hswebframework/web/exception/I18nSupportException.java
  7. 31 16
      hsweb-core/src/main/java/org/hswebframework/web/exception/ValidationException.java
  8. 13 0
      hsweb-core/src/main/java/org/hswebframework/web/i18n/ContextLocaleResolver.java
  9. 38 15
      hsweb-core/src/main/java/org/hswebframework/web/i18n/LocaleUtils.java
  10. 8 7
      hsweb-core/src/main/java/org/hswebframework/web/i18n/WebFluxLocaleFilter.java
  11. 14 7
      hsweb-core/src/main/java/org/hswebframework/web/validator/ValidatorUtils.java
  12. 2 1
      hsweb-core/src/main/resources/i18n/core/messages_en_US.properties
  13. 2 1
      hsweb-core/src/main/resources/i18n/core/messages_zh_CN.properties
  14. 42 0
      hsweb-core/src/test/java/org/hswebframework/web/validator/ValidatorUtilsTest.java
  15. 27 13
      hsweb-starter/src/main/java/org/hswebframework/web/starter/jackson/CustomJackson2JsonDecoder.java
  16. 2 3
      hsweb-system/hsweb-system-authorization/hsweb-system-authorization-default/src/main/java/org/hswebframework/web/system/authorization/defaults/service/DefaultReactiveUserService.java
  17. 2 0
      hsweb-system/hsweb-system-authorization/hsweb-system-authorization-default/src/main/resources/i18n/authentication-default/messages_en_US.properties
  18. 2 0
      hsweb-system/hsweb-system-authorization/hsweb-system-authorization-default/src/main/resources/i18n/authentication-default/messages_zh_CN.properties
  19. 0 1
      hsweb-system/hsweb-system-authorization/hsweb-system-authorization-default/src/main/resources/i18n/messages_zh_CN.properties

+ 34 - 15
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/ValidateEventListener.java

@@ -5,11 +5,14 @@ import org.hswebframework.ezorm.rdb.events.EventListener;
 import org.hswebframework.ezorm.rdb.events.EventType;
 import org.hswebframework.ezorm.rdb.mapping.events.MappingContextKeys;
 import org.hswebframework.ezorm.rdb.mapping.events.MappingEventTypes;
+import org.hswebframework.ezorm.rdb.mapping.events.ReactiveResultHolder;
 import org.hswebframework.web.api.crud.entity.Entity;
+import org.hswebframework.web.i18n.LocaleUtils;
 import org.hswebframework.web.validator.CreateGroup;
 import org.hswebframework.web.validator.UpdateGroup;
 
 import java.util.List;
+import java.util.Optional;
 
 public class ValidateEventListener implements EventListener {
 
@@ -24,33 +27,49 @@ public class ValidateEventListener implements EventListener {
     }
 
     @Override
-    @SuppressWarnings("all")
+
     public void onEvent(EventType type, EventContext context) {
+        Optional<ReactiveResultHolder> resultHolder = context.get(MappingContextKeys.reactiveResultHolder);
+
+        if (resultHolder.isPresent()) {
+            resultHolder
+                    .ifPresent(holder -> holder
+                            .before(LocaleUtils
+                                            .currentReactive()
+                                            .doOnNext(locale -> LocaleUtils.doWith(locale, (l) -> tryValidate(type, context)))
+                                            .then()
+                            ));
+        } else {
+            tryValidate(type, context);
+        }
+    }
+
+    @SuppressWarnings("all")
+    public void tryValidate(EventType type, EventContext context) {
         if (type == MappingEventTypes.insert_before || type == MappingEventTypes.save_before) {
 
             boolean single = context.get(MappingContextKeys.type).map("single"::equals).orElse(false);
             if (single) {
                 context.get(MappingContextKeys.instance)
-                        .filter(Entity.class::isInstance)
-                        .map(Entity.class::cast)
-                        .ifPresent(entity -> entity.tryValidate(CreateGroup.class));
+                       .filter(Entity.class::isInstance)
+                       .map(Entity.class::cast)
+                       .ifPresent(entity -> entity.tryValidate(CreateGroup.class));
             } else {
                 context.get(MappingContextKeys.instance)
-                        .filter(List.class::isInstance)
-                        .map(List.class::cast)
-                        .ifPresent(lst -> lst.stream()
-                                .filter(Entity.class::isInstance)
-                                .map(Entity.class::cast)
-                                .forEach(e -> ((Entity) e).tryValidate(CreateGroup.class))
-                        );
+                       .filter(List.class::isInstance)
+                       .map(List.class::cast)
+                       .ifPresent(lst -> lst.stream()
+                                            .filter(Entity.class::isInstance)
+                                            .map(Entity.class::cast)
+                                            .forEach(e -> ((Entity) e).tryValidate(CreateGroup.class))
+                       );
             }
 
         } else if (type == MappingEventTypes.update_before) {
             context.get(MappingContextKeys.instance)
-                    .filter(Entity.class::isInstance)
-                    .map(Entity.class::cast)
-                    .ifPresent(entity -> entity.tryValidate(UpdateGroup.class));
+                   .filter(Entity.class::isInstance)
+                   .map(Entity.class::cast)
+                   .ifPresent(entity -> entity.tryValidate(UpdateGroup.class));
         }
-
     }
 }

+ 10 - 6
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/CommonErrorControllerAdvice.java

@@ -62,7 +62,8 @@ public class CommonErrorControllerAdvice {
     @ResponseStatus(HttpStatus.UNAUTHORIZED)
     public Mono<ResponseMessage<TokenState>> handleException(UnAuthorizedException e) {
         return LocaleUtils
-                .resolveThrowable(messageSource, e, (err, msg) -> (ResponseMessage.<TokenState>error(401, "unauthorized", msg).result(e.getState())));
+                .resolveThrowable(messageSource, e, (err, msg) -> (ResponseMessage.<TokenState>error(401, "unauthorized", msg)
+                        .result(e.getState())));
     }
 
     @ExceptionHandler
@@ -84,14 +85,17 @@ public class CommonErrorControllerAdvice {
     @ExceptionHandler
     @ResponseStatus(HttpStatus.BAD_REQUEST)
     public Mono<ResponseMessage<List<ValidationException.Detail>>> handleException(ValidationException e) {
-        return Mono.just(ResponseMessage.<List<ValidationException.Detail>>error(400, "illegal_argument", e.getMessage())
-                                 .result(e.getDetails()));
+        return LocaleUtils
+                .resolveThrowable(messageSource, e, (err, msg) -> ResponseMessage
+                        .<List<ValidationException.Detail>>error(400, "illegal_argument",msg)
+                        .result(e.getDetails()))
+                ;
     }
 
     @ExceptionHandler
     @ResponseStatus(HttpStatus.BAD_REQUEST)
     public Mono<ResponseMessage<List<ValidationException.Detail>>> handleException(ConstraintViolationException e) {
-        return handleException(new ValidationException(e.getMessage(), e.getConstraintViolations()));
+        return handleException(new ValidationException(e.getConstraintViolations()));
     }
 
     @ExceptionHandler
@@ -178,7 +182,7 @@ public class CommonErrorControllerAdvice {
     public Mono<ResponseMessage<Object>> handleException(AuthenticationException e) {
         return LocaleUtils
                 .resolveThrowable(messageSource, e, (err, msg) -> ResponseMessage.error(400, e.getCode(), msg))
-                .doOnEach(ReactiveLogger.onNext(r -> log.error(e.getMessage(), e)))
+                .doOnEach(ReactiveLogger.onNext(r -> log.error(e.getLocalizedMessage(), e)))
                 ;
     }
 
@@ -190,7 +194,7 @@ public class CommonErrorControllerAdvice {
                 .map(msg -> ResponseMessage
                         .error(415, "unsupported_media_type", msg)
                         .result(e.getSupportedMediaTypes()))
-                .doOnEach(ReactiveLogger.onNext(r -> log.error(e.getMessage(), e)));
+                .doOnEach(ReactiveLogger.onNext(r -> log.error(e.getLocalizedMessage(), e)));
     }
 
     @ExceptionHandler

+ 5 - 1
hsweb-core/pom.xml

@@ -35,7 +35,6 @@
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-web</artifactId>
-
         </dependency>
 
         <dependency>
@@ -97,5 +96,10 @@
             <version>3.0.0</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.hibernate.validator</groupId>
+            <artifactId>hibernate-validator</artifactId>
+        </dependency>
+
     </dependencies>
 </project>

+ 2 - 7
hsweb-core/src/main/java/org/hswebframework/web/dict/EnumDict.java

@@ -385,10 +385,7 @@ public interface EnumDict<V> extends JSONSerializable {
                             return e.name();
                         }).collect(Collectors.toList());
 
-                return new ValidationException("validation.parameter_does_not_exist_in_enums",
-                                               Arrays.asList(
-                                                       new ValidationException.Detail(currentName, "选项中不存在此值", values)
-                                               ), currentName);
+                return new ValidationException(currentName,"validation.parameter_does_not_exist_in_enums", currentName);
             };
             if (EnumDict.class.isAssignableFrom(findPropertyType) && findPropertyType.isEnum()) {
                 if (node.isObject()) {
@@ -406,9 +403,7 @@ public interface EnumDict<V> extends JSONSerializable {
                             .find(findPropertyType, node.textValue())
                             .orElseThrow(exceptionSupplier);
                 }
-                throw new ValidationException("validation.parameter_does_not_exist_in_enums", Arrays.asList(
-                        new ValidationException.Detail(currentName, "选项中不存在此值", null)
-                ), currentName);
+                return exceptionSupplier.get();
             }
             if (findPropertyType.isEnum()) {
                 return Stream

+ 5 - 1
hsweb-core/src/main/java/org/hswebframework/web/exception/BusinessException.java

@@ -31,7 +31,6 @@ public class BusinessException extends I18nSupportException {
 
     @Getter
     private int status = 500;
-
     @Getter
     private String code;
 
@@ -43,6 +42,11 @@ public class BusinessException extends I18nSupportException {
         this(message, null, status, args);
     }
 
+    public BusinessException(String message, String code) {
+        this(message, code, 500);
+    }
+
+
     public BusinessException(String message, String code, int status, Object... args) {
         super(message, args);
         this.code = code;

+ 12 - 3
hsweb-core/src/main/java/org/hswebframework/web/exception/I18nSupportException.java

@@ -1,27 +1,36 @@
 package org.hswebframework.web.exception;
 
 
+import lombok.AccessLevel;
 import lombok.Getter;
+import lombok.Setter;
+import org.hswebframework.web.i18n.LocaleUtils;
 
 @Getter
+@Setter(AccessLevel.PROTECTED)
 public class I18nSupportException extends RuntimeException {
+    private String code;
+    private Object[] args;
 
-    private final Object[] args;
+    protected I18nSupportException() {
+
+    }
 
     public I18nSupportException(String code, Object... args) {
         super(code);
+        this.code = code;
         this.args = args;
     }
 
     public I18nSupportException(String code, Throwable cause, Object... args) {
         super(code, cause);
         this.args = args;
+        this.code = code;
     }
 
 
     @Override
     public String getLocalizedMessage() {
-        // TODO: 2021/6/21
-        return super.getLocalizedMessage();
+        return LocaleUtils.resolveMessage(code, args);
     }
 }

+ 31 - 16
hsweb-core/src/main/java/org/hswebframework/web/exception/ValidationException.java

@@ -4,19 +4,17 @@ import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 import lombok.Setter;
+import org.hswebframework.web.i18n.LocaleUtils;
 import org.springframework.http.HttpStatus;
 import org.springframework.web.bind.annotation.ResponseStatus;
 
 import javax.validation.ConstraintViolation;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
 
 @Getter
 @Setter
 @ResponseStatus(HttpStatus.BAD_REQUEST)
-public class ValidationException extends BusinessException {
+public class ValidationException extends I18nSupportException {
 
     private List<Detail> details;
 
@@ -24,25 +22,36 @@ public class ValidationException extends BusinessException {
         super(message);
     }
 
-    public ValidationException(String property, String message) {
-        this(message, Collections.singletonList(new Detail(property, message, null)));
+    public ValidationException(String property, String message, Object... args) {
+        this(message, Collections.singletonList(new Detail(property, message, null)), args);
     }
 
-    public ValidationException(String message, List<Detail> details,Object... args) {
-        super(message,400,args);
+    public ValidationException(String message, List<Detail> details, Object... args) {
+        super(message, 400, args);
         this.details = details;
+        for (Detail detail : this.details) {
+            detail.translateI18n(args);
+        }
     }
 
-    public ValidationException(String message, Set<? extends ConstraintViolation<?>> violations) {
-        super(message);
-        if (null != violations && !violations.isEmpty()) {
-            details = new ArrayList<>();
-            for (ConstraintViolation<?> violation : violations) {
-                details.add(new Detail(violation.getPropertyPath().toString(), violation.getMessage(), null));
-            }
+    public ValidationException(Set<? extends ConstraintViolation<?>> violations) {
+        ConstraintViolation<?> first = violations.iterator().next();
+        if (Objects.equals(first.getMessageTemplate(), first.getMessage())) {
+            //模版和消息相同,说明是自定义的message,而不是已经通过i18n获取的.
+            setCode(first.getMessage());
+        } else {
+            setCode("validation.property_validate_failed");
+        }
+        //{0} 属性 ,{1} 验证消息
+        setArgs(new Object[]{first.getPropertyPath().toString(), first.getMessage()});
+
+        details = new ArrayList<>(violations.size());
+        for (ConstraintViolation<?> violation : violations) {
+            details.add(new Detail(violation.getPropertyPath().toString(), violation.getMessage(), null));
         }
     }
 
+
     @Getter
     @Setter
     @AllArgsConstructor
@@ -55,5 +64,11 @@ public class ValidationException extends BusinessException {
 
         @Schema(description = "详情")
         Object detail;
+
+        public void translateI18n(Object... args) {
+            if (message.contains(".")) {
+                message = LocaleUtils.resolveMessage(message, message, args);
+            }
+        }
     }
 }

+ 13 - 0
hsweb-core/src/main/java/org/hswebframework/web/i18n/ContextLocaleResolver.java

@@ -0,0 +1,13 @@
+package org.hswebframework.web.i18n;
+
+import org.hibernate.validator.spi.messageinterpolation.LocaleResolver;
+import org.hibernate.validator.spi.messageinterpolation.LocaleResolverContext;
+
+import java.util.Locale;
+
+public class ContextLocaleResolver implements LocaleResolver {
+    @Override
+    public Locale resolve(LocaleResolverContext context) {
+        return LocaleUtils.current();
+    }
+}

+ 38 - 15
hsweb-core/src/main/java/org/hswebframework/web/i18n/LocaleUtils.java

@@ -2,12 +2,15 @@ package org.hswebframework.web.i18n;
 
 import org.hswebframework.web.exception.I18nSupportException;
 import org.springframework.context.MessageSource;
-import org.springframework.context.i18n.LocaleContext;
-import org.springframework.context.i18n.SimpleLocaleContext;
 import reactor.core.publisher.Mono;
+import reactor.core.publisher.Signal;
+import reactor.core.publisher.SignalType;
+import reactor.util.context.Context;
 
 import java.util.Locale;
+import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
+import java.util.function.Consumer;
 import java.util.function.Function;
 
 /**
@@ -18,9 +21,9 @@ import java.util.function.Function;
  */
 public class LocaleUtils {
 
-    public static final LocaleContext DEFAULT_CONTEXT = new SimpleLocaleContext(Locale.getDefault());
+    public static final Locale DEFAULT_LOCALE = Locale.getDefault();
 
-    private static final ThreadLocal<LocaleContext> CONTEXT_THREAD_LOCAL = new ThreadLocal<>();
+    private static final ThreadLocal<Locale> CONTEXT_THREAD_LOCAL = new ThreadLocal<>();
 
     static MessageSource messageSource;
 
@@ -30,11 +33,11 @@ public class LocaleUtils {
      * @return Locale
      */
     public static Locale current() {
-        LocaleContext context = CONTEXT_THREAD_LOCAL.get();
-        if (context == null || context.getLocale() == null) {
-            context = DEFAULT_CONTEXT;
+        Locale locale = CONTEXT_THREAD_LOCAL.get();
+        if (locale == null) {
+            locale = DEFAULT_LOCALE;
         }
-        return context.getLocale();
+        return locale;
     }
 
     /**
@@ -51,27 +54,47 @@ public class LocaleUtils {
      */
     public static <T, R> R doWith(T data, Locale locale, BiFunction<T, Locale, R> mapper) {
         try {
-            CONTEXT_THREAD_LOCAL.set(new SimpleLocaleContext(locale));
+            CONTEXT_THREAD_LOCAL.set(locale);
             return mapper.apply(data, locale);
         } finally {
             CONTEXT_THREAD_LOCAL.remove();
         }
     }
 
+    public static Function<Context, Context> useLocale(Locale locale) {
+        return ctx -> ctx.put(Locale.class, locale);
+    }
+
+    public static void doWith(Locale locale, Consumer<Locale> consumer) {
+        try {
+            CONTEXT_THREAD_LOCAL.set(locale);
+            consumer.accept(locale);
+        } finally {
+            CONTEXT_THREAD_LOCAL.remove();
+        }
+    }
+
     /**
      * 响应式方式获取当前语言地区
+     *
      * @return 语言地区
      */
+    @SuppressWarnings("all")
     public static Mono<Locale> currentReactive() {
         return Mono
                 .subscriberContext()
-                .map(ctx -> ctx
-                        .<LocaleContext>getOrEmpty(LocaleContext.class)
-                        .map(LocaleContext::getLocale)
-                        .orElseGet(Locale::getDefault)
-                );
+                .map(ctx -> ctx.getOrDefault(Locale.class, DEFAULT_LOCALE));
     }
 
+    public static <T> void onNext(Signal<T> signal, BiConsumer<T, Locale> consumer) {
+        if (signal.getType() != SignalType.ON_NEXT) {
+            return;
+        }
+        Locale locale = signal.getContext().getOrDefault(Locale.class, DEFAULT_LOCALE);
+
+        doWith(locale, l -> consumer.accept(signal.get(), l));
+
+    }
 
     public static <S extends I18nSupportException, R> Mono<R> resolveThrowable(S source,
                                                                                BiFunction<S, String, R> mapper) {
@@ -81,7 +104,7 @@ public class LocaleUtils {
     public static <S extends I18nSupportException, R> Mono<R> resolveThrowable(MessageSource messageSource,
                                                                                S source,
                                                                                BiFunction<S, String, R> mapper) {
-        return doWithReactive(messageSource, source, Throwable::getMessage, mapper, source.getArgs());
+        return doWithReactive(messageSource, source, I18nSupportException::getCode, mapper, source.getArgs());
     }
 
     public static <S extends Throwable, R> Mono<R> resolveThrowable(S source,

+ 8 - 7
hsweb-core/src/main/java/org/hswebframework/web/i18n/WebFluxLocaleFilter.java

@@ -1,7 +1,5 @@
 package org.hswebframework.web.i18n;
 
-import org.springframework.context.i18n.LocaleContext;
-import org.springframework.context.i18n.SimpleLocaleContext;
 import org.springframework.lang.NonNull;
 import org.springframework.util.StringUtils;
 import org.springframework.web.server.ServerWebExchange;
@@ -10,7 +8,6 @@ import org.springframework.web.server.WebFilterChain;
 import reactor.core.publisher.Mono;
 
 import java.util.Locale;
-import java.util.Optional;
 
 public class WebFluxLocaleFilter implements WebFilter {
     @Override
@@ -18,16 +15,20 @@ public class WebFluxLocaleFilter implements WebFilter {
     public Mono<Void> filter(@NonNull ServerWebExchange exchange, WebFilterChain chain) {
         return chain
                 .filter(exchange)
-                .subscriberContext(ctx -> ctx.put(LocaleContext.class, getLocaleContext(exchange)));
+                .subscriberContext(LocaleUtils.useLocale(getLocaleContext(exchange)));
     }
 
-    public LocaleContext getLocaleContext(ServerWebExchange exchange) {
+    public Locale getLocaleContext(ServerWebExchange exchange) {
         String lang = exchange.getRequest()
                               .getQueryParams()
                               .getFirst(":lang");
         if (StringUtils.hasText(lang)) {
-            return new SimpleLocaleContext(Locale.forLanguageTag(lang));
+            return Locale.forLanguageTag(lang);
         }
-        return exchange.getLocaleContext();
+        Locale locale = exchange.getLocaleContext().getLocale();
+        if (locale == null) {
+            return Locale.getDefault();
+        }
+        return locale;
     }
 }

+ 14 - 7
hsweb-core/src/main/java/org/hswebframework/web/validator/ValidatorUtils.java

@@ -1,12 +1,10 @@
 package org.hswebframework.web.validator;
 
+import org.hibernate.validator.BaseHibernateValidatorConfiguration;
 import org.hswebframework.web.exception.ValidationException;
+import org.hswebframework.web.i18n.ContextLocaleResolver;
 
-import javax.el.ExpressionFactory;
-import javax.validation.ConstraintViolation;
-import javax.validation.Validation;
-import javax.validation.Validator;
-import javax.validation.ValidatorFactory;
+import javax.validation.*;
 import java.util.Set;
 
 public final class ValidatorUtils {
@@ -19,17 +17,26 @@ public final class ValidatorUtils {
     public static Validator getValidator() {
         if (validator == null) {
             synchronized (ValidatorUtils.class) {
-                ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+                Configuration<?> configuration = Validation
+                        .byDefaultProvider()
+                        .configure();
+                configuration.addProperty(BaseHibernateValidatorConfiguration.LOCALE_RESOLVER_CLASSNAME,
+                                          ContextLocaleResolver.class.getName());
+                configuration.messageInterpolator(configuration.getDefaultMessageInterpolator());
+
+                ValidatorFactory factory = configuration.buildValidatorFactory();
+
                 return validator = factory.getValidator();
             }
         }
         return validator;
     }
 
+    @SuppressWarnings("all")
     public static <T> T tryValidate(T bean, Class... group) {
         Set<ConstraintViolation<T>> violations = getValidator().validate(bean, group);
         if (!violations.isEmpty()) {
-            throw new ValidationException(violations.iterator().next().getMessage(), violations);
+            throw new ValidationException(violations);
         }
 
         return bean;

+ 2 - 1
hsweb-core/src/main/resources/i18n/core/messages_en_US.properties

@@ -1,3 +1,4 @@
 error.not_found=The data does not exist
 error.cant_create_instance=Unable to create instance:{0}
-validation.parameter_does_not_exist_in_enums=Parameter {0} does not exist in option
+validation.parameter_does_not_exist_in_enums=Parameter {0} does not exist in option
+validation.property_validate_failed=Parameter '{0}' {1}

+ 2 - 1
hsweb-core/src/main/resources/i18n/core/messages_zh_CN.properties

@@ -1,4 +1,5 @@
 error.not_found=数据不存在
 error.cant_create_instance=无法创建实例:{0}
 
-validation.parameter_does_not_exist_in_enums=参数[{0}]在选择中不存在
+validation.parameter_does_not_exist_in_enums=参数[{0}]在选择中不存在
+validation.property_validate_failed=参数'{0}'{1}

+ 42 - 0
hsweb-core/src/test/java/org/hswebframework/web/validator/ValidatorUtilsTest.java

@@ -0,0 +1,42 @@
+package org.hswebframework.web.validator;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.hswebframework.web.exception.ValidationException;
+import org.hswebframework.web.i18n.LocaleUtils;
+import org.junit.Test;
+
+import javax.validation.constraints.NotBlank;
+
+import java.util.Locale;
+
+import static org.junit.Assert.*;
+
+public class ValidatorUtilsTest {
+
+
+    @Test
+    public void test(){
+        test(Locale.CHINA,"不能为空");
+        test(Locale.ENGLISH,"must not be blank");
+    }
+
+    public void test(Locale locale,String msg){
+        try {
+            LocaleUtils.doWith(locale,en->{
+                ValidatorUtils.tryValidate(new TestEntity());
+            });
+            throw new IllegalStateException();
+        }catch (ValidationException e){
+            assertEquals(msg,e.getDetails().get(0).getMessage());
+        }
+    }
+
+    @Getter
+    @Setter
+    public static class TestEntity{
+
+        @NotBlank
+        private String notBlank;
+    }
+}

+ 27 - 13
hsweb-starter/src/main/java/org/hswebframework/web/starter/jackson/CustomJackson2JsonDecoder.java

@@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.ObjectReader;
 import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
 import com.fasterxml.jackson.databind.util.TokenBuffer;
 import org.hswebframework.web.api.crud.entity.EntityFactory;
+import org.hswebframework.web.i18n.LocaleUtils;
 import org.reactivestreams.Publisher;
 import org.springframework.core.MethodParameter;
 import org.springframework.core.ResolvableType;
@@ -67,17 +68,23 @@ public class CustomJackson2JsonDecoder extends Jackson2CodecSupport implements H
 
         ObjectReader reader = getObjectReader(elementType, hints);
 
-        return tokens.handle((tokenBuffer, sink) -> {
-            try {
-                Object value = reader.readValue(tokenBuffer.asParser(getObjectMapper()));
-                logValue(value, hints);
-                if (value != null) {
-                    sink.next(value);
-                }
-            } catch (IOException ex) {
-                sink.error(processException(ex));
-            }
-        });
+        return LocaleUtils
+                .currentReactive()
+                .flatMapMany(locale -> tokens
+                        .handle((tokenBuffer, sink) -> {
+                            LocaleUtils.doWith(locale, l -> {
+                                try {
+                                    Object value = reader.readValue(tokenBuffer.asParser(getObjectMapper()));
+                                    logValue(value, hints);
+                                    if (value != null) {
+                                        sink.next(value);
+                                    }
+                                } catch (IOException ex) {
+                                    sink.error(processException(ex));
+                                }
+                            });
+
+                        }));
     }
 
     @Override
@@ -85,8 +92,15 @@ public class CustomJackson2JsonDecoder extends Jackson2CodecSupport implements H
     public Mono<Object> decodeToMono(@NonNull Publisher<DataBuffer> input, @NonNull ResolvableType elementType,
                                      @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
 
-        return DataBufferUtils.join(input)
-                              .map(dataBuffer -> decode(dataBuffer, elementType, mimeType, hints));
+        return LocaleUtils
+                .currentReactive()
+                .flatMap(locale -> DataBufferUtils
+                        .join(input)
+                        .map(dataBuffer -> LocaleUtils
+                                .doWith(dataBuffer,
+                                        locale,
+                                        (buf, l) -> decode(buf, elementType, mimeType, hints)))
+                );
     }
 
     @Override

+ 2 - 3
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-default/src/main/java/org/hswebframework/web/system/authorization/defaults/service/DefaultReactiveUserService.java

@@ -82,13 +82,12 @@ public class DefaultReactiveUserService extends GenericReactiveCrudService<UserE
                             .where(userEntity::getUsername)
                             .fetch()
                             .doOnNext(u -> {
-                                throw new org.hswebframework.web.exception.ValidationException("用户已存在");
+                                throw new org.hswebframework.web.exception.ValidationException("error.user_already_exists");
                             })
                             .then(Mono.just(userEntity))
-                            .doOnNext(e -> e.tryValidate(CreateGroup.class))
                             .as(getRepository()::insert)
                             .onErrorMap(DuplicateKeyException.class, e -> {
-                                throw new org.hswebframework.web.exception.ValidationException("用户已存在");
+                                throw new org.hswebframework.web.exception.ValidationException("error.user_already_exists");
                             })
                             .thenReturn(userEntity)
                             .flatMap(user -> new UserCreatedEvent(user).publish(eventPublisher))

+ 2 - 0
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-default/src/main/resources/i18n/authentication-default/messages_en_US.properties

@@ -0,0 +1,2 @@
+error.duplicate_key=Duplicate Data
+error.user_already_exists=User already exists

+ 2 - 0
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-default/src/main/resources/i18n/authentication-default/messages_zh_CN.properties

@@ -0,0 +1,2 @@
+error.duplicate_key=重复的请求
+error.user_already_exists=用户已存在

+ 0 - 1
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-default/src/main/resources/i18n/messages_zh_CN.properties

@@ -1 +0,0 @@
-error.duplicate_key=重复的请求