Spring Boot 实现接口统一入参校验

  本文示例代码基于Spring Boot 2.2.6JDK1.8Spring Boot已经内置了所需参数校验的框架。代码中使用了lombok注解。

  本文涉及的代码地址:https://gitee.com/qiwan/params-validated-demo.git

1.基本类型参数入参校验及校验异常统一处理接口返回数据:

package com.qiwan.validated.controller;

import org.hibernate.validator.constraints.Length;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.qiwan.validated.entity.User;
import com.qiwan.validated.result.Result;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Validated
@RestController
@RequestMapping("/user")
public class UserController {
	
	/**
	 * @Description: 根据姓名查询用户接口
	 * @param name 前端入参查询参数
	 * @return Result
	 */
	@GetMapping
	public Result queryUser(
			@NotBlank(message = "姓名不能为空!")
			@Length(min = 2, max = 10, message = "姓名的长度只能在{min}到{max}之间!")
			String name) {

		User user = new User();
		user.setName(name);
		user.setAge(18);
		user.setPhone("13111111111");

		return new Result().success("查询用户数据成功!", user);
	}

  打开Postman使用Get请求http://localhost:8080/user接口,在参数name为空的情况下,结果如下图:
在这里插入图片描述
  在Controller类上面使用了@Validated注解,虽然参数校验成功了,但是这种返回格式不利于接口调用者处理返回值,下面使用@RestControllerAdvice注解对返回数据进行统一处理。

  新建ValidatedExceptionHandler类(类名随便定义),在类上添加@RestControllerAdvice注解,用于拦截Controller中的参数校验异常并处理。代码如下:

package com.qiwan.validated.exception;

import java.util.List;
import java.util.stream.Collectors;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import com.qiwan.validated.result.Result;

import lombok.extern.slf4j.Slf4j;

/**
 * @Description: 入参校验全局异常统一处理
 * @author: Qiwan
 * @date: 2020年5月4日 下午4:37:24
 */
@Slf4j
@RestControllerAdvice
public class ValidatedExceptionHandler {

	/**
	 * @Description: 基本类型参数入参校验异常返回数据处理
	 * @param e
	 * @return Result
	 */
	@ExceptionHandler(ConstraintViolationException.class)
	public Result basicTypeParamsCheckException(ConstraintViolationException e) {
		log.info("============基本类型参数入参校验异常============");
		List<String> defaultMsg = e.getConstraintViolations()
                .stream()
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.toList());
		return new Result().failure(defaultMsg.get(0));
	}
}

  注意:@ExceptionHandler(ConstraintViolationException.class)注解,基本类型参数校验异常处理时使用的是ConstraintViolationException.class

  打开Postman使用Get请求http://localhost:8080/user接口,在参数name为空的情况下,结果如下图:
在这里插入图片描述
name符合验证规则的情况下,结果如下图:
在这里插入图片描述
接口返回这种数据的话那接口的调用者就非常好处理了。

2.实体类型参数入参校验及校验异常统一处理接口返回数据:

在上面的UserController中添加如下方法,

/**
 * @Description: 添加用户接口
 * @param user 前端入参待添加数据
 * @return Result
 */
@PostMapping
public Result addUser(@Validated User user) {
	log.info("==========进入添加用户接口==========");
	return new Result().success("添加用户成功!");
}

  注意:除了Controller类上使用了@Validated注解,实体参数前面也要使用@Validated注解。同样的,我们需要对校验异常进行统一处理,否则不利于接口的调用者使用接口的返回数据。

在上面的ValidatedExceptionHandler类中添加如下代码:

/**
 * @Description: 实体类型参数入参校验异常返回数据处理
 * @param e
 * @return Result
 */
@ExceptionHandler(BindException.class)
public Result entityTypeParamsCheckException(BindException e) {
	log.info("============实体类型参数入参校验异常============");
	return new Result().failure(e.getBindingResult().getFieldError().getDefaultMessage());
}

  注意:@ExceptionHandler(BindException.class)注解,实体类型参数校验异常处理时使用的是BindException.class

  打开Postman使用Post请求http://localhost:8080/user接口,在参数user为空的情况下,结果如下图:
在这里插入图片描述
  在入参为空的情况下,成功进行了校验并返回标准的JSON数据。在设置了完整参数的情况下,返回结果如下图:
在这里插入图片描述
  至此,接口统一入参校验功能基本完成。为什么说基本呢?因为上面对实体类型参数进行校验时,如果入参为空的话,我们使用Postman多测几次就会发现,接口返回JSON里的字段校验提示信息的顺序是不固定的。下面我们就来固定一下实体中字段验证的先后顺序。

3.固定字段校验的先后顺序:

  自定义一些表示顺序的接口,如:

One.java

public interface One {}

Two.java

public interface Two {}

等等,够用就行了。

然后自定义UserCheckSequence.java接口,名字随便起,代码如下:

package com.qiwan.validated.order;

import javax.validation.GroupSequence;

@GroupSequence({ One.class, Two.class, Three.class, Four.class, Five.class, Six.class })
public interface UserCheckSequence {

}

  在@GroupSequence注解中添加校验的顺序,然后在User.java中给需要校验的字段指定校验顺序,代码如下:

package com.qiwan.validated.entity;

import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.Length;

import com.qiwan.validated.order.Eight;
import com.qiwan.validated.order.Five;
import com.qiwan.validated.order.Four;
import com.qiwan.validated.order.One;
import com.qiwan.validated.order.Seven;
import com.qiwan.validated.order.Six;
import com.qiwan.validated.order.Three;
import com.qiwan.validated.order.Two;

import lombok.Data;

@Data
public class User {
	
	@NotBlank(message = "姓名不能为空!", groups = One.class)
	@Length(min = 2, max = 20, message = "姓名的长度只能在{min}到{max}之间!", groups = Two.class)
	private String name;
	
	@NotNull(message = "年龄不能为空!", groups = Three.class)
	@DecimalMin(message = "年龄不能小于{value}岁!", value = "16", groups = Four.class)
	@DecimalMax(message = "年龄不能大于{value}岁!", value = "100", groups = Five.class)
	private Integer age;
	
	@NotBlank(message = "手机号码不能为空!", groups = Six.class)
	@Length(min = 11, max = 11, message = "手机号码长度只能为{max}位!", groups = Seven.class)
	@Pattern(regexp ="^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "手机号码格式有误!", groups = Eight.class)
	private String phone;
}

  然后再把Controller的方法中,参数User前面的@Validated注解修改为:@Validated(UserCheckSequence.class),代码如下:

@PostMapping
public Result addUser(@Validated(UserCheckSequence.class) User user) {
	log.info("==========进入添加用户接口==========");
	return new Result().success("添加用户成功!");
}

  最后再使用Postman对实体入参的接口进行测试后发现,字段的校验顺序已经按我们指定的顺序固定了。

补充:
Result.java

package com.qiwan.validated.result;

import java.io.Serializable;

import lombok.Data;

/**
 * @Description: 用于向页面传递信息的类
 * @author: Qiwan
 * @date: 2020年5月3日 上午2:29:18
 */
@Data
public class Result implements Serializable {

	private static final long serialVersionUID = 3166337785106881468L;

	private int status;

    private String msg;

    private Object data;

    public static String MSG_OK = "成功";
    public static String MSG_FAIL = "失败";

    public static int STATUS_OK = 1;
    public static int STATUS_FAIL = 0;

    public Result() {}

    public Result(int status, String msg, Object data) {
        this.status = status;
        this.msg = msg;
        this.data = data;
    }

    public Result success(String msg) {
        this.status= STATUS_OK;
        this.msg= msg;
        return this;
    }

    public Result success(String msg, Object data) {
        this.status= STATUS_OK;
        this.msg= msg;
        this.data = data;
        return this;
    }

    public Result failure(String msg) {
        this.status= STATUS_FAIL;
        this.msg= msg;
        return this;
    }

    public Result failure(String msg,Object data) {
        this.status= STATUS_FAIL;
        this.msg= msg;
        this.data = data;
        return this;
    }

    public Result failure(int status,String msg) {
        this.status= status;
        this.msg= msg;
        return this;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值