From 96c8b83dff2769ecb43c7bcb6f5211f59e34ead4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johnny=20Miller=20=28=E9=94=BA=E4=BF=8A=29?= Date: Sun, 6 Jun 2021 18:54:13 +0800 Subject: [PATCH] feat($Validation): support date time range value validation --- .../maf/common/bean/EnumerationBase.java | 18 ++++ .../aspect/ExceptionControllerAdvice.java | 46 +++++++---- .../MinioClientConfiguration.java | 38 +++++++++ .../annotation/DateTimeRangeConstraints.java | 24 ++++++ .../annotation/DateTimeRangeGroup.java | 17 ++++ .../annotation/DateTimeRangeType.java | 17 ++++ .../annotation/ValidEnumValue.java | 13 +-- .../validator/DateTimeRangeValidator.java | 82 +++++++++++++++++++ .../{ => validator}/EnumValueValidator.java | 14 ++-- 9 files changed, 244 insertions(+), 25 deletions(-) create mode 100644 common/src/main/java/com/jmsoftware/maf/common/bean/EnumerationBase.java create mode 100644 spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/MinioClientConfiguration.java create mode 100644 spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/annotation/DateTimeRangeConstraints.java create mode 100644 spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/annotation/DateTimeRangeGroup.java create mode 100644 spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/annotation/DateTimeRangeType.java rename spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/{ => validation}/annotation/ValidEnumValue.java (74%) create mode 100644 spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/validator/DateTimeRangeValidator.java rename spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/{ => validator}/EnumValueValidator.java (83%) diff --git a/common/src/main/java/com/jmsoftware/maf/common/bean/EnumerationBase.java b/common/src/main/java/com/jmsoftware/maf/common/bean/EnumerationBase.java new file mode 100644 index 00000000..4d56b063 --- /dev/null +++ b/common/src/main/java/com/jmsoftware/maf/common/bean/EnumerationBase.java @@ -0,0 +1,18 @@ +package com.jmsoftware.maf.common.bean; + +/** + *

EnumerationBase

+ *

+ * Change description here. + * + * @author Johnny Miller (鍾俊), email: johnnysviva@outlook.com + * @date 6/6/21 5:38 PM + **/ +public interface EnumerationBase { + /** + * Gets value. + * + * @return the value + */ + T getValue(); +} diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/aspect/ExceptionControllerAdvice.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/aspect/ExceptionControllerAdvice.java index a1e21e55..3722d06e 100644 --- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/aspect/ExceptionControllerAdvice.java +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/aspect/ExceptionControllerAdvice.java @@ -22,6 +22,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.validation.ConstraintViolationException; import java.util.Objects; /** @@ -44,7 +45,7 @@ public class ExceptionControllerAdvice { * @return custom exception info */ @ResponseStatus(HttpStatus.NOT_FOUND) - @ExceptionHandler(value = NoHandlerFoundException.class) + @ExceptionHandler(NoHandlerFoundException.class) public ResponseBodyBean handleException(HttpServletRequest request, NoHandlerFoundException exception) { requestLog(request); // ATTENTION: Use only ResponseBodyBean.ofStatus() in handleException() method and @@ -55,7 +56,7 @@ public ResponseBodyBean handleException(HttpServletRequest request, NoHandler } @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) - @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class) + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) public ResponseBodyBean handleException(HttpServletRequest request, HttpRequestMethodNotSupportedException exception) { requestLog(request); @@ -66,7 +67,7 @@ public ResponseBodyBean handleException(HttpServletRequest request, } @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(value = MethodArgumentNotValidException.class) + @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseBodyBean handleException(HttpServletRequest request, MethodArgumentNotValidException exception) { requestLog(request); log.error("Exception occurred when validation on an argument annotated with fails. Exception message: {}", @@ -76,7 +77,7 @@ public ResponseBodyBean handleException(HttpServletRequest request, MethodArg } @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(value = MethodArgumentTypeMismatchException.class) + @ExceptionHandler(MethodArgumentTypeMismatchException.class) public ResponseBodyBean handleException(HttpServletRequest request, MethodArgumentTypeMismatchException exception) { requestLog(request); @@ -86,14 +87,14 @@ public ResponseBodyBean handleException(HttpServletRequest request, } @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(value = HttpMessageNotReadableException.class) + @ExceptionHandler(HttpMessageNotReadableException.class) public ResponseBodyBean handleException(HttpServletRequest request, HttpMessageNotReadableException exception) { requestLog(request); log.error("HttpMessageNotReadableException: {}", exception.getMessage()); return ResponseBodyBean.ofStatus(HttpStatus.BAD_REQUEST, removeLineSeparator(exception.getMessage())); } - @ExceptionHandler(value = BaseException.class) + @ExceptionHandler(BaseException.class) public ResponseBodyBean handleException(HttpServletRequest request, HttpServletResponse response, BaseException exception) { requestLog(request); @@ -105,34 +106,51 @@ public ResponseBodyBean handleException(HttpServletRequest request, HttpServl } @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(value = BindException.class) + @ExceptionHandler(BindException.class) public ResponseBodyBean handleException(HttpServletRequest request, BindException exception) { requestLog(request); - log.error("Exception message: {} ", exception.getMessage()); + log.error("BindException message: {} ", exception.getMessage()); + String message; + if (exception.hasFieldErrors()) { + val fieldError = exception.getFieldError(); + message = String.format("Field [%s] has error: %s", Objects.requireNonNull(fieldError).getField(), + fieldError.getDefaultMessage()); + } else { + val globalError = exception.getGlobalError(); + message = String.format("Bind error: %s", Objects.requireNonNull(globalError).getDefaultMessage()); + } + return ResponseBodyBean.ofStatus(HttpStatus.BAD_REQUEST, message); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(ConstraintViolationException.class) + public ResponseBodyBean handleException(HttpServletRequest request, ConstraintViolationException exception) { + requestLog(request); + log.error("ConstraintViolationException message: {} ", exception.getMessage()); return ResponseBodyBean.ofStatus(HttpStatus.BAD_REQUEST, removeLineSeparator(exception.getMessage())); } @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(value = IllegalArgumentException.class) + @ExceptionHandler(IllegalArgumentException.class) public ResponseBodyBean handleException(HttpServletRequest request, IllegalArgumentException exception) { requestLog(request); - log.error("Exception message: {} ", exception.getMessage()); + log.error("IllegalArgumentException message: {} ", exception.getMessage()); return ResponseBodyBean.ofStatus(HttpStatus.BAD_REQUEST.value(), removeLineSeparator(exception.getMessage()), null); } @ResponseStatus(HttpStatus.FORBIDDEN) - @ExceptionHandler(value = BadCredentialsException.class) + @ExceptionHandler(BadCredentialsException.class) public ResponseBodyBean handleException(HttpServletRequest request, BadCredentialsException exception) { requestLog(request); // IMPORTANT: org.springframework.security.authentication.BadCredentialsException only exists in the project // that depends on org.springframework.boot.spring-boot-starter-security - log.error("Exception message: {} ", exception.getMessage()); + log.error("BadCredentialsException message: {} ", exception.getMessage()); return ResponseBodyBean.ofStatus(HttpStatus.FORBIDDEN.value(), removeLineSeparator(exception.getMessage()), null); } - @ExceptionHandler(value = InternalAuthenticationServiceException.class) + @ExceptionHandler(InternalAuthenticationServiceException.class) public ResponseBodyBean handleException(HttpServletRequest request, HttpServletResponse response, InternalAuthenticationServiceException exception) { requestLog(request); @@ -149,7 +167,7 @@ public ResponseBodyBean handleException(HttpServletRequest request, HttpServl null); } - @ExceptionHandler(value = {Throwable.class}) + @ExceptionHandler(Throwable.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ResponseBodyBean handleError(Throwable ex) { log.error("Internal server exception occurred! Exception message: {} ", ex.getMessage(), ex); diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/MinioClientConfiguration.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/MinioClientConfiguration.java new file mode 100644 index 00000000..7333cbfc --- /dev/null +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/MinioClientConfiguration.java @@ -0,0 +1,38 @@ +package com.jmsoftware.maf.springcloudstarter.configuration; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + *

MinioConfiguration

+ *

+ * Change description here. + * + * @author Johnny Miller (鍾俊), email: johnnysviva@outlook.com + * @date 4/29/21 2:23 PM + **/ +@Data +@Slf4j +@Validated +@Component +@ConfigurationProperties(prefix = MinioClientConfiguration.PREFIX) +public class MinioClientConfiguration { + public static final String PREFIX = "minio.client.configuration"; + @NotBlank + private String endpoint; + @NotNull + private Integer port; + @NotBlank + private String accessKey; + @NotBlank + private String secretKey; + private Boolean secure = Boolean.TRUE; + private String bucketName; + private String configDir; +} diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/annotation/DateTimeRangeConstraints.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/annotation/DateTimeRangeConstraints.java new file mode 100644 index 00000000..06e1b48d --- /dev/null +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/annotation/DateTimeRangeConstraints.java @@ -0,0 +1,24 @@ +package com.jmsoftware.maf.springcloudstarter.validation.annotation; + +import com.jmsoftware.maf.springcloudstarter.validation.validator.DateTimeRangeValidator; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +/** + * Description: DateTimeRangeConstraints, change description here. + * + * @author 钟俊(zhongjun), email: zhongjun@toguide.cn, date: 6/3/2021 2:08 PM + **/ +@Documented +@Constraint(validatedBy = DateTimeRangeValidator.class) +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DateTimeRangeConstraints { + String message() default "Invalid date time range"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/annotation/DateTimeRangeGroup.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/annotation/DateTimeRangeGroup.java new file mode 100644 index 00000000..cdfe25e7 --- /dev/null +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/annotation/DateTimeRangeGroup.java @@ -0,0 +1,17 @@ +package com.jmsoftware.maf.springcloudstarter.validation.annotation; + +import java.lang.annotation.*; + +/** + * Description: DateTimeRangeGroup, change description here. + * + * @author 钟俊(zhongjun), email: zhongjun@toguide.cn, date: 6/3/2021 2:58 PM + **/ +@Documented +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DateTimeRangeGroup { + String groupName() default "defaultDateTimeRangeGroup"; + + DateTimeRangeType type() default DateTimeRangeType.START_TIME; +} diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/annotation/DateTimeRangeType.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/annotation/DateTimeRangeType.java new file mode 100644 index 00000000..2cc7d4ab --- /dev/null +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/annotation/DateTimeRangeType.java @@ -0,0 +1,17 @@ +package com.jmsoftware.maf.springcloudstarter.validation.annotation; + +/** + * Description: DateTimeRangeType, change description here. + * + * @author 钟俊 (zhongjun), email: zhongjun@toguide.cn, date: 6/3/2021 3:07 PM + */ +public enum DateTimeRangeType { + /** + * Start time + */ + START_TIME, + /** + * End time + */ + END_TIME, +} diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/annotation/ValidEnumValue.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/annotation/ValidEnumValue.java similarity index 74% rename from spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/annotation/ValidEnumValue.java rename to spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/annotation/ValidEnumValue.java index c6f60b9e..8a7a92fe 100644 --- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/annotation/ValidEnumValue.java +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/annotation/ValidEnumValue.java @@ -1,8 +1,9 @@ -package com.jmsoftware.maf.springcloudstarter.annotation; +package com.jmsoftware.maf.springcloudstarter.validation.annotation; -import com.jmsoftware.maf.springcloudstarter.validation.EnumValueValidator; +import com.jmsoftware.maf.springcloudstarter.validation.validator.EnumValueValidator; import javax.validation.Constraint; +import javax.validation.Payload; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -26,11 +27,11 @@ public @interface ValidEnumValue { String message() default "Invalid enumeration value"; - Class targetEnum() default Class.class; + Class[] groups() default {}; - boolean ignoreNull() default false; + Class[] payload() default {}; - Class[] groups() default {}; + Class targetEnum() default Class.class; - Class[] payload() default {}; + boolean ignoreNull() default false; } diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/validator/DateTimeRangeValidator.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/validator/DateTimeRangeValidator.java new file mode 100644 index 00000000..4bd2eae2 --- /dev/null +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/validator/DateTimeRangeValidator.java @@ -0,0 +1,82 @@ +package com.jmsoftware.maf.springcloudstarter.validation.validator; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; +import com.jmsoftware.maf.springcloudstarter.validation.annotation.DateTimeRangeConstraints; +import com.jmsoftware.maf.springcloudstarter.validation.annotation.DateTimeRangeGroup; +import lombok.extern.slf4j.Slf4j; +import lombok.val; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.lang.reflect.Field; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; + +/** + * Description: DateTimeRangeValidator, change description here. + * + * @author 钟俊(zhongjun), email: zhongjun@toguide.cn, date: 6/3/2021 2:10 PM + **/ +@Slf4j +public class DateTimeRangeValidator implements ConstraintValidator { + public static final int MAX_GROUP_SIZE = 2; + + @Override + public boolean isValid(Object value, ConstraintValidatorContext context) { + val fields = value.getClass().getDeclaredFields(); + final HashSet annotatedFieldSet = CollUtil.newHashSet(); + for (val field : fields) { + val annotation = field.getAnnotation(DateTimeRangeGroup.class); + if (ObjectUtil.isNotNull(annotation)) { + annotatedFieldSet.add(field); + } + } + if (CollUtil.isEmpty(annotatedFieldSet)) { + log.warn("There is not fields annotated by {} in the class({})", value.getClass().getName(), + DateTimeRangeGroup.class.getSimpleName()); + return true; + } + final HashMap> dateTimeRangeGroupMap = CollUtil.newHashMap(fields.length); + for (val field : annotatedFieldSet) { + val annotation = field.getAnnotation(DateTimeRangeGroup.class); + if (!dateTimeRangeGroupMap.containsKey(annotation.groupName())) { + dateTimeRangeGroupMap.put(annotation.groupName(), CollUtil.newLinkedList(field)); + } else { + dateTimeRangeGroupMap.get(annotation.groupName()).add(field); + if (dateTimeRangeGroupMap.get(annotation.groupName()).size() > MAX_GROUP_SIZE) { + log.error("The length of DateTimeRangeGroup({}) cannot exceed {}!", annotation.groupName(), MAX_GROUP_SIZE); + return false; + } + } + } + for (val entry : dateTimeRangeGroupMap.entrySet()) { + val groupName = entry.getKey(); + val fieldList = entry.getValue(); + if (fieldList.size() != MAX_GROUP_SIZE) { + log.error("The length of DateTimeRangeGroup({}) is not correct!", groupName); + return false; + } + val dateTimeRangeGroup = fieldList.get(0).getAnnotation(DateTimeRangeGroup.class); + Date startTime = null, endTime = null; + switch (dateTimeRangeGroup.type()) { + case START_TIME: + startTime = (Date) ReflectUtil.getFieldValue(value, fieldList.get(0)); + endTime = (Date) ReflectUtil.getFieldValue(value, fieldList.get(1)); + break; + case END_TIME: + startTime = (Date) ReflectUtil.getFieldValue(value, fieldList.get(1)); + endTime = (Date) ReflectUtil.getFieldValue(value, fieldList.get(0)); + break; + default: + } + if (ObjectUtil.isAllNotEmpty(startTime, endTime) && startTime.after(endTime)) { + return false; + } + } + return true; + } +} diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/EnumValueValidator.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/validator/EnumValueValidator.java similarity index 83% rename from spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/EnumValueValidator.java rename to spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/validator/EnumValueValidator.java index 69cfd743..b4619f2d 100644 --- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/EnumValueValidator.java +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/validation/validator/EnumValueValidator.java @@ -1,7 +1,9 @@ -package com.jmsoftware.maf.springcloudstarter.validation; +package com.jmsoftware.maf.springcloudstarter.validation.validator; import cn.hutool.core.util.ObjectUtil; -import com.jmsoftware.maf.springcloudstarter.annotation.ValidEnumValue; +import cn.hutool.core.util.ReflectUtil; +import com.jmsoftware.maf.common.bean.EnumerationBase; +import com.jmsoftware.maf.springcloudstarter.validation.annotation.ValidEnumValue; import lombok.extern.slf4j.Slf4j; import lombok.val; @@ -21,7 +23,7 @@ **/ @Slf4j public class EnumValueValidator implements ConstraintValidator { - private final static String METHOD_NAME = "getValue"; + private String methodName; private ValidEnumValue validEnumValue; /** @@ -31,6 +33,8 @@ public class EnumValueValidator implements ConstraintValidator