在实际开发中常常要对传入参数做校验,往往会冗余大量的if/else来进行数据校验显得既不美观也不优雅,使用springboot中@Valid和@Valided结合jsr303数据校验可以很好的解决这个问题
这里处理的思路:
使用@Valided注解标识在需要校验的方法参数上并根据分组校验规则校验,配合BindResult对象封装校验的错误信息(BindResult对象会对校验结果自动进行封装)
使用spring的aop,配置需要进行校验的方法的切点对BindResult对象进行统一处理,如果有异常则抛出自定义的异常
使用@RestControllerAdvice配置处理自定义校验的通知并返回通用结果集BaseResult到前端显示
@Valid和@Validated的区别:
参考:
https://blog.csdn.net/qq_27680317/article/details/79970590
话不多说直接上代码:
BaseResult
/**
* Web接口统一返回结果
* @author
* @since
*/
@Data
@ToString
@Accessors(chain = true)
public class BaseResult implements Serializable {
private static final long serialVersionUID = 1L;
public static final String ERROR_MSG = "操作失败";
/**
* 状态码
*/
private int code;
/**
* 消息信息
*/
private String message;
private Object data;
private Map<String,Object> row;
public BaseResult() {
}
public BaseResult(int code, String message) {
this(code, message, null);
}
public BaseResult(int code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
}
/**
* 获取成功的对象
* @return
*/
public static BaseResult successResult(){
BaseResult baseResult = new BaseResult();
baseResult.setCode(ResultEnum.SUCCESS.getCode());
baseResult.setMessage(ResultEnum.SUCCESS.getValue());
return baseResult;
}
/**
* 设置为操作成功
* @return
*/
public BaseResult success(){
this.setCode(ResultEnum.SUCCESS.getCode());
this.setMessage(ResultEnum.SUCCESS.getValue());
return this;
}
/**
* 设置为失败
* @return
*/
public BaseResult error(){
this.setCode(ResultEnum.ERR_PARAM.getCode());
this.setMessage(ResultEnum.ERR_PARAM.getValue());
return this;
}
/**
* 设置为失败
* @param code
* @param msg
* @return
*/
public BaseResult error(int code, String msg){
this.setCode(ResultEnum.ERR_PARAM.getCode());
this.setMessage(ResultEnum.ERR_PARAM.getValue());
return this;
}
/**
* 是否成功
* @return
*/
public boolean isSuccess(){
return this.code == 200? true:false;
}
}
ValidType:
定义几个空的接口用于@Valided注解中group属性对校验进行分组以满足不同的校验业务场景
public class ValidType {
public interface Add{};
public interface Delete{};
public interface Select{};
public interface Update{};
}
User实体:
可以使用校验注解的group属性对校验进行分类,用来区分不同场景下是否需要进行校验,比如add时需要对id进行校验,而update操作时不需要对id进行校验
我这里定义了CRUD四个场景用来分组
@Data
@Accessors(chain = true)
public class User extends BaseModel{
@NotNull(message="id不能为空",groups = {ValidType.Update.class,ValidType.Delete.class})
private Long id;
@NotNull(message="username不能为空",groups = {ValidType.Add.class,ValidType.Update.class})
@NotBlank(message="username不能为空",groups = {ValidType.Add.class,ValidType.Update.class})
private String username;
@Email(message = "邮箱格式不正确",groups = {ValidType.Add.class,ValidType.Update.class})
@NotNull(message="邮箱不能为空",groups = {ValidType.Add.class,ValidType.Update.class})
@NotBlank(message="邮箱不能为空",groups = {ValidType.Add.class,ValidType.Update.class})
private String email;
}
@null 验证对象是否为空
@notnull 验证对象是否为非空
@asserttrue 验证 boolean 对象是否为 true
@assertfalse 验证 boolean 对象是否为 false
@min 验证 number 和 string 对象是否大等于指定的值
@max 验证 number 和 string 对象是否小等于指定的值
@decimalmin 验证 number 和 string 对象是否大等于指定的值,小数存在精度
@decimalmax 验证 number 和 string 对象是否小等于指定的值,小数存在精度
@size 验证对象(array,collection,map,string)长度是否在给定的范围之内
@digits 验证 number 和 string 的构成是否合法
@past 验证 date 和 calendar 对象是否在当前时间之前
@future 验证 date 和 calendar 对象是否在当前时间之后
@pattern 验证 string 对象是否符合正则表达式的规则
@Email 验证邮箱
CheckParamException:自定义异常
package lf.ssm.exception;
import com.sun.tools.javac.jvm.Code;
import lf.ssm.enums.ResultEnum;
import lombok.Data;
import lombok.Getter;
import org.springframework.validation.BindingResult;
/**
* @Classname CheckParamException
* @Date 2020/4/10 8:25
* @Created by liufeng
*/
public class CheckParamException extends Exception{
@Getter
private String msg;
@Getter
private int code;
@Getter
private BindingResult bindingResult;
public CheckParamException(ResultEnum resultEnum,BindingResult bindingResult) {
this.msg = resultEnum.value;
this.code = resultEnum.code;
this.bindingResult = bindingResult;
}
}
Contoller:
package lf.ssm.controller.test;
@Api(description = "用户控制器",tags = "BaseController")
@RestController
@RequestMapping("/user")
public class UserController extends BaseController {
@Autowired
private IUserService service;
@PostMapping("/add")
@ApiOperation("添加用户")
public BaseResult userAdd(@RequestBody @Validated({ValidType.Add.class}) User user, BindingResult bindingResult) {
BaseResult baseResult = BaseResult.successResult();
service.insert(user);
return baseResult;
}
@PostMapping("/update")
@ApiOperation("更新用户")
public BaseResult userUpdate(@RequestBody @Validated({ValidType.Update.class}) User user, BindingResult bindingResult){
BaseResult baseResult = BaseResult.successResult();
service.updateById(user);
return baseResult;
}
}
配置aop切面:
当请求controller(切点)时,会进入所配置的前置通知中,这样我们就可以对校验的结果进行判断并处理
@Slf4j
@Aspect
@Component
public class CheckParamAop {
//execution(* lf.ssm.controller.*.*(..)):controller包下所有控制器的方法
//execution(* lf.ssm.controller..*.*(..)):controller包及其子包下controller的所有方法
//execution(* lf.ssm.controller.UserController.*(..)): UserController的所有方法
private final String expCheckPoint="execution(* lf.ssm.controller..*.*(..))";
@Pointcut(expCheckPoint) //配置切点
public void expCheckPoint(){}
/**
*配置前置通知并织入
* @author liufeng
* @date 2020/4/13 8:05
* @return void
*/
@Before("expCheckPoint()")
public void doBefore(JoinPoint joinPoint) throws CheckParamException {
log.info("这是前置通知参数校验");
BindingResult bindingResult=null;
for (Object arg:joinPoint.getArgs()){
if(arg instanceof BindingResult){ //取出校验结果
bindingResult= (BindingResult) arg;
}
}
//当校验不通过时抛出自定义异常
if(bindingResult != null && bindingResult.hasErrors()){
throw new CheckParamException(ResultEnum.PARAM_ERROR,bindingResult);
}
}
}
ExceptionControllerAdvice
使用spring的@RestControllerAdvice注解配置通知处理自定义异常,封装异常信息返回给前端:
/**
* 校验异常处理通知,异常统一处理
* @Classname ExceptionControllerAdvice
* @Date 2020/4/13 10:10
* @Created by liufeng
*/
@Slf4j
@RestControllerAdvice
public class ExceptionControllerAdvice {
@ExceptionHandler(CheckParamException.class)
public BaseResult bindException(CheckParamException e){
BaseResult baseResult = new BaseResult();
BindingResult bindingResult = e.getBindingResult();
log.warn("CheckParamException:>>>");
if(bindingResult!=null && bindingResult.hasErrors()){
List<ObjectError> allErrors = bindingResult.getAllErrors();
baseResult.setCode(e.getCode());
String message="参数错误:%s";
for (ObjectError err:allErrors){
message = String.format(message, err.getDefaultMessage());
// log.warn(err.getDefaultMessage());
break;
}
baseResult.setMessage(message);
}else{
baseResult.setCode(e.getCode());
baseResult.setMessage(e.getMessage());
}
return baseResult;
}
}
总结:使用@Valided的方式对数据进行校验有效减少大量if/else的校验代码,对于复杂的校验@valided可以与if/else配合一起使用以满足不同的业务场景