Controller 层数据校验实现思路

背景

我们编写后端代码的时候往往伴随着很多的参数校验,比如 mobile 字段必须接收一个有效的手机号码,sort 只能接收指定的参数进行排序等。

如果在 Controller 层一个个参数校验的话会显得很混乱,也伴随着大量的重复的代码。

我们可以使用 Hibernate Validator 在做这个事情。

代码

Validator 自带了很多参数校验,比如

public class DemoModel {
    @NotBlank(message="用户名不能为空")
    private String userName;

    @NotBlank(message="年龄不能为空")
    @Pattern(regexp="^[0-9]{1,2}$",message="年龄不正确")
    private String age;

    @AssertFalse(message = "必须为false")
    private Boolean isFalse;
    /**
     * 如果是空,则不校验,如果不为空,则校验
     */
    @Pattern(regexp="^[0-9]{4}-[0-9]{2}-[0-9]{2}$",message="出生日期格式不正确")
    private String birthday;
}

再编写一个全局的异常拦截器:


@ControllerAdvice
@Order(value = Ordered.LOWEST_PRECEDENCE)
public class GlobalExceptionHandler {

        // 直接在方法参数列表中使用注解进行校验,不通过时抛出的异常
        // e.g. public String testValidator(@Sort @RequestParam String sort)
	@ExceptionHandler(ValidationException.class)
	@ResponseBody
	public Object badArgumentHandler(ValidationException e) {
		e.printStackTrace();
		if (e instanceof ConstraintViolationException) {
			ConstraintViolationException exs = (ConstraintViolationException) e;
			Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
			for (ConstraintViolation<?> item : violations) {
				String message = ((PathImpl) item.getPropertyPath()).getLeafNode().getName() + item.getMessage();
				return message;
			}
		}
		return "error";
	}

        // 校验实体类中字段不通过时抛出的异常
        // e.g. @Sort private String sort;
	@ExceptionHandler(MethodArgumentNotValidException.class)
	@ResponseBody
	public Object badArgumentHandler(MethodArgumentNotValidException e) {

		e.printStackTrace();
		if (e instanceof MethodArgumentNotValidException) {
			BindingResult bindingResult = e.getBindingResult();
			List<FieldError> fieldErrors = bindingResult.getFieldErrors();

			StringJoiner stringJoiner = new StringJoiner(" ");
			fieldErrors.forEach(errors ->{
				stringJoiner.add(errors.getField().toString() + ":" + errors.getDefaultMessage());
			});

			return stringJoiner.toString();
		}
		return "error";
	}

	@ExceptionHandler(Exception.class)
	@ResponseBody
	public Object seriousHandler(Exception e) {
		e.printStackTrace();
		return "error";
	}
}

我们也可以自定义注解来实现某些特殊业务逻辑的判断:

1.自定义注解,需要有 groups() 和 payload() 两个方法,不然会报错。
2.编写 Validator 实现 ConstraintValidator<A extends Annotation, T> 作为异常判断逻辑

@Target({METHOD, FIELD, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = SortValidator.class)
public @interface Sort {
    String message() default "排序字段不支持";

    String[] accepts() default {"add_time", "id"};

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

public class SortValidator implements ConstraintValidator<Sort, String> {

    private List<String> valueList;

    @Override
    public void initialize(Sort sort) {
        valueList = new ArrayList<>();
        for (String val : sort.accepts()) {
            valueList.add(val.toUpperCase());
        }
    }

    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        if (!valueList.contains(s.toUpperCase())) {
            return false;
        }
        return true;
    }
}

@RestController
@Validated
public class ExampleController {

    @GetMapping("/testValidator")
    public String testValidator(@Sort @RequestParam String sort) {
        return "success";
    }

    @GetMapping("/testValidator2")
    public String testValidator2(@Validated @RequestBody User user) {
        return "success";
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

	private Integer id;
	private String name;
	private String address;
	private Integer age;
	@Sort
	private String sort;
}

如果是校验参数,必须在 Controller 上添加 @Validated 注解。
如果是校验实体内字段,必须在需要校验的实体参数前添加@Validated 注解。

posted @ 2021-02-23 14:47  LiuChengloong  阅读(727)  评论(0编辑  收藏  举报