一个健壮的系统都要对外部提交的数据进行完整性、合法性的校验。
校验是我们程序开发中必不可少的过程。
即使开发一个不面对最终用户的工具包,也需要对传入的数据进行缜密的校验来防止引发底层难以追踪的问题。
后端参数校验最简单的做法是直接在业务方法里面进行判断,当判断成功之后再继续往下执行。但这样带给我们的是代码的耦合,冗余。当我们多个地方需要校验时,我们就需要在每一个地方调用校验程序,导致代码很冗余,且不美观。
不使用Bean Validation校验数据的代码基本都是靠大量的 if-else
.所以我这里学习使用了 注解方式实现数据校验.
SpringBoot 中的 bean validation
是集成了hibernate-validator
和tomcat-embed-el在介绍validation 之前首先
简述SR303/JSR-349,hibernate validation,spring validation之间的关系。JSR303是一项标准,JSR-349是其的升级版本,添加了一些新特性,他们规定一些校验规范即校验注解,如@Null,@NotNull,@Pattern,他们位于javax.validation.constraints包下,只提供规范不提供实现。而hibernate validation是对这个规范的实践(不要将hibernate和数据库orm框架联系在一起),他提供了相应的实现,并增加了一些其他校验注解,如@Email,@Length,@Range等等,他们位于org.hibernate.validator.constraints包下。而万能的spring为了给开发者提供便捷,对hibernate validation进行了二次封装,显示校验validated bean时,你可以使用spring validation或者hibernate validation,而spring validation另一个特性,便是其在springmvc模块中添加了自动校验,并将校验信息封装进了特定的类中。这无疑便捷了我们的web开发。
后端对数据进行验证
添加包
二:返回信息
我这里通过抛出异常来统一返回异常信息
import com.shitou.huishi.contract.datacontract.code.RspCode;
import com.shitou.huishi.contract.datacontract.exception.AuthException;
import com.shitou.huishi.contract.datacontract.response.DataResponse;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* Created by qhong on 2018/5/28 15:51
**/
@ControllerAdvice
public class GlobalExceptionHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
/**
* 登陆异常
* @param req
* @param e
* @return
* @throws AuthException
*/
@ExceptionHandler(value = AuthException.class)
@ResponseBody
public DataResponse handleAuthException(HttpServletRequest req, AuthException e) throws AuthException {
DataResponse r = new DataResponse();
r.setResCode(e.getCode()+"");
r.setMsg(e.getMsg());
logger.info("AuthException",e.getMsg());
return r;
}
/**
* 验证异常
* @param req
* @param e
* @return
* @throws MethodArgumentNotValidException
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
@ResponseBody
public DataResponse handleMethodArgumentNotValidException(HttpServletRequest req, MethodArgumentNotValidException e) throws MethodArgumentNotValidException {
DataResponse r = new DataResponse();
BindingResult bindingResult = e.getBindingResult();
String errorMesssage = "Invalid Request:\n";
for (FieldError fieldError : bindingResult.getFieldErrors()) {
errorMesssage += fieldError.getDefaultMessage() + "\n";
}
r.setResCode(RspCode.VERIFICATION_DOES_NOT_PASS.getCode());
r.setMsg(errorMesssage);
logger.info("MethodArgumentNotValidException",e);
return r;
}
/**
* 全局异常
* @param req
* @param e
* @return
* @throws Exception
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public DataResponse handleException(HttpServletRequest req, Exception e) throws Exception {
DataResponse r = new DataResponse();
r.setResCode(RspCode.CODE_ERROR.getCode());
r.setMsg(RspCode.CODE_ERROR.getMessage()+","+e.getMessage());
logger.error(e.getMessage(),e);
return r;
}
}
三:具体代码
总结框架提供了那些校验:
JSR提供的校验注解:
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
Hibernate Validator提供的校验注解:
@NotBlank(message =) 验证字符串非null,且trim后长度必须大于0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
Code:
@Range(min=1,max=2,message = "档案类型错误")
private Integer archiveType;
@NotBlank(message = "档案主体名称不能为空")
private String subjectName;
@NotBlank(message = "证件号不能为空")
private String subjectNo;
Controller:
public DataResponse createArchive(@RequestBody @Valid ArchiveInfoRequest request)
添加@Valid或者@Validated都可以。
@Valid:常见用在方法,类中字段上进行校验
@Validated:是spring提供的对@Valid的封装,常见用在方法上进行校验
BindingResult:是验证是否错误
Model中
@Range(max = 150, min = 1, message = "年龄范围应该在1-150内。")
private Integer age;
Controller中
@PostMapping("save")
public void v1(@RequestBody @Valid AppUser appUser,BindingResult result){
if(result.hasErrors()){
for (ObjectError error : result.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
}
绑定多个校验对象
@PostMapping("save")
public void v1(@RequestBody @Valid AppUser appUser,BindingResult result,@RequestBody @Valid AppUser appUser2,BindingResult result2){
if(result.hasErrors()){
for (ObjectError error : result.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
}
补充:
网上提供的其他异常:
@ExceptionHandler(value =BindException.class)
@ResponseBody
public DataResponse handleBindException(BindException e) throws BindException {
// ex.getFieldError():随机返回一个对象属性的异常信息。如果要一次性返回所有对象属性异常信息,则调用ex.getAllErrors()
FieldError fieldError = e.getFieldError();
StringBuilder sb = new StringBuilder();
sb.append(fieldError.getField()).append("=[").append(fieldError.getRejectedValue()).append("]")
.append(fieldError.getDefaultMessage());
// 生成返回结果
DataResponse r = new DataResponse();
r.setResCode(RspCode.VERIFICATION_DOES_NOT_PASS.getCode());
r.setMsg(sb.toString());
logger.info("BindException", e);
return r;
}
自定义注解
参考已有注解
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* The annotated element must not be {@code null}.
* Accepts any type.
*
* @author Emmanuel Bernard
*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = { })
public @interface NotNull {
String message() default "{javax.validation.constraints.NotNull.message}";
Class[] groups() default { };
Class[] payload() default { };
/**
* Defines several {@link NotNull} annotations on the same element.
*
* @see javax.validation.constraints.NotNull
*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@interface List {
NotNull[] value();
}
}
自己写注解
第一步
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {IsMobileValidator.class })
public @interface IsMobile {
boolean required() default true;
String message() default "手机号码格式错误";
Class[] groups() default { };
Class[] payload() default { };
}
第二步
public class IsMobileValidator implements ConstraintValidator
private boolean required = false;
public void initialize(IsMobile constraintAnnotation) {
required = constraintAnnotation.required();
}
public boolean isValid(String value, ConstraintValidatorContext context) {
if(required) {
return ValidatorUtil.isMobile(value);
}else {
if(StringUtils.isEmpty(value)) {
return true;
}else {
return ValidatorUtil.isMobile(value);
}
}
}
}
public class ValidatorUtil {
private static final Pattern mobile_pattern = Pattern.compile("1\\d{10}");
public static boolean isMobile(String src) {
if(StringUtils.isEmpty(src)) {
return false;
}
Matcher m = mobile_pattern.matcher(src);
return m.matches();
}