其他
Java 是如何优雅地实现接口数据校验的?
The following article is from 无敌码农 Author 无敌码农
@RestController
public class OrderController {
@Autowired
private OrderService orderServiceImpl;
@PostMapping("/createOrder")
public CreateOrderBO validationTest(@Validated CreateOrderDTO createOrderDTO) {
return orderServiceImpl.createOrder(createOrderDTO);
}
}
使用@Validated 注解实现 Controller 接口层数据直接绑定校验;
扩展约束性注解实现数据取值范围的校验;
更加灵活的对象数据合法性校验工具类封装;
数据合法性校验结果异常统一返回处理;
@Data
public class CreateOrderDTO {
@NotNull(message = "订单号不能为空")
private String orderId;
@NotNull(message = "订单金额不能为空")
@Min(value = 1, message = "订单金额不能小于0")
private Integer amount;
@Pattern(regexp = "^1[3|4|5|7|8][0-9]{9}$", message = "用户手机号不合法")
private String mobileNo;
private String orderType;
private String status;
}
@PostMapping("/createOrder")
public CreateOrderBO validationTest(@Validated CreateOrderDTO createOrderDTO) {
return orderServiceImpl.createOrder(createOrderDTO);
}
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {EnumValueValidator.class})
public @interface EnumValue {
//默认错误消息
String message() default "必须为指定值";
//支持string数组验证
String[] strValues() default {};
//支持int数组验证
int[] intValues() default {};
//支持枚举列表验证
Class<?>[] enumValues() default {};
//分组
Class<?>[] groups() default {};
//负载
Class<? extends Payload>[] payload() default {};
//指定多个时使用
@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Documented
@interface List {
EnumValue[] value();
}
/**
* 校验类逻辑定义
*/
class EnumValueValidator implements ConstraintValidator<EnumValue, Object> {
//字符串类型数组
private String[] strValues;
//int类型数组
private int[] intValues;
//枚举类
private Class<?>[] enumValues;
/**
* 初始化方法
*
* @param constraintAnnotation
*/
@Override
public void initialize(EnumValue constraintAnnotation) {
strValues = constraintAnnotation.strValues();
intValues = constraintAnnotation.intValues();
enumValues = constraintAnnotation.enumValues();
}
/**
* 校验方法
*
* @param value
* @param context
* @return
*/
@SneakyThrows
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
//针对字符串数组的校验匹配
if (strValues != null && strValues.length > 0) {
if (value instanceof String) {
for (String s : strValues) {//判断值类型是否为Integer类型
if (s.equals(value)) {
return true;
}
}
}
}
//针对整型数组的校验匹配
if (intValues != null && intValues.length > 0) {
if (value instanceof Integer) {//判断值类型是否为Integer类型
for (Integer s : intValues) {
if (s == value) {
return true;
}
}
}
}
//针对枚举类型的校验匹配
if (enumValues != null && enumValues.length > 0) {
for (Class<?> cl : enumValues) {
if (cl.isEnum()) {
//枚举类验证
Object[] objs = cl.getEnumConstants();
//这里需要注意,定义枚举时,枚举值名称统一用value表示
Method method = cl.getMethod("getValue");
for (Object obj : objs) {
Object code = method.invoke(obj, null);
if (value.equals(code.toString())) {
return true;
}
}
}
}
}
return false;
}
}
}
/**
* 定制化注解,支持参数值与指定类型数组列表值进行匹配(缺点是需要将枚举值写死在字段定义的注解中)
*/
@EnumValue(strValues = {"pay", "refund"}, message = "订单类型错误")
private String orderType;
/**
* 定制化注解,实现参数值与枚举列表的自动匹配校验(能更好地与实际业务开发匹配)
*/
@EnumValue(enumValues = Status.class, message = "状态值不在指定范围")
private String status;
public enum Status {
PROCESSING(1, "处理中"),
SUCCESS(2, "订单已完成");
Integer value;
String desc;
Status(Integer value, String desc) {
this.value = value;
this.desc = desc;
}
public Integer getValue() {
return value;
}
public String getDesc() {
return desc;
}
}
public class ValidatorUtils {
private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
/**
* bean整体校验,有不合规范,抛出第1个违规异常
*/
public static void validate(Object obj, Class<?>... groups) {
Set<ConstraintViolation<Object>> resultSet = validator.validate(obj, groups);
if (resultSet.size() > 0) {
//如果存在错误结果,则将其解析并进行拼凑后异常抛出
List<String> errorMessageList = resultSet.stream().map(o -> o.getMessage()).collect(Collectors.toList());
StringBuilder errorMessage = new StringBuilder();
errorMessageList.stream().forEach(o -> errorMessage.append(o + ";"));
throw new IllegalArgumentException(errorMessage.toString());
}
}
}
public boolean orderCheck(OrderCheckBO orderCheckBO) {
//对参数对象进行数据校验
ValidatorUtils.validate(orderCheckBO);
return true;
}
@Data
@Builder
public class OrderCheckBO {
@NotNull(message = "订单号不能为空")
private String orderId;
@Min(value = 1, message = "订单金额不能小于0")
private Integer orderAmount;
@NotNull(message = "创建人不能为空")
private String operator;
@NotNull(message = "操作时间不能为空")
private String operatorTime;
}
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 统一处理参数校验错误异常(非Spring接口数据绑定验证)
*
* @param response
* @param e
* @return
*/
@ExceptionHandler(BindException.class)
@ResponseBody
public ResponseResult<?> processValidException(HttpServletResponse response, BindException e) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
//获取校验错误结果信息,并将信息组装
List<String> errorStringList = e.getBindingResult().getAllErrors()
.stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
String errorMessage = String.join("; ", errorStringList);
response.setContentType("application/json;charset=UTF-8");
log.error(e.toString() + "_" + e.getMessage(), e);
return ResponseResult.systemException(GlobalCodeEnum.GL_FAIL_9998.getCode(),
errorMessage);
}
/**
* 统一处理参数校验错误异常
*
* @param response
* @param e
* @return
*/
@ExceptionHandler(IllegalArgumentException.class)
@ResponseBody
public ResponseResult<?> processValidException(HttpServletResponse response, IllegalArgumentException e) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
String errorMessage = String.join("; ", e.getMessage());
response.setContentType("application/json;charset=UTF-8");
log.error(e.toString() + "_" + e.getMessage(), e);
return ResponseResult.systemException(GlobalCodeEnum.GL_FAIL_9998.getCode(),
errorMessage);
}
...
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonPropertyOrder({"code", "message", "data"})
public class ResponseResult<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 返回的对象
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
private T data;
/**
* 返回的编码
*/
private Integer code;
/**
* 返回的信息
*/
private String message;
/**
* @param data 返回的数据
* @param <T> 返回的数据类型
* @return 响应结果
*/
public static <T> ResponseResult<T> OK(T data) {
return packageObject(data, GlobalCodeEnum.GL_SUCC_0);
}
/**
* 自定义系统异常信息
*
* @param code
* @param message 自定义消息
* @param <T>
* @return
*/
public static <T> ResponseResult<T> systemException(Integer code, String message) {
return packageObject(null, code, message);
}
}
☞程序员有钱了都干什么?买豪宅,玩跑车,上太空!| 涛滔不绝
☞他被称为印度“ IT 大王”,富可敌国,却精打细算如守财奴
☞红帽"干掉" CentOS 8,CentOS Stream 上位
☞科技垄断正在朝着纵向发展