打造一款适合自己的快速开发框架-数据校验之Hibernate Validator

805 阅读7分钟

前言

在前面讲的三层架构中,数据校验是放在控制层上的。springboot 配合 Hibernate Validator,可以很优雅地处理数据校验的问题,让业务代码和校验逻辑分开,无需编写重复的校验逻辑。 Hibernate Validator 除了提供 JSR 303 规范中所有内置 constraint 的实现 。还可以根据自己的需要自定义constraint。

Hibernate Validator的作用

  • 验证逻辑与业务逻辑分离,降低程序的耦合度
  • 统一规范的验证方式,无需重复编写验证代码逻辑
  • 让开发人员更转注于业务
<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-validator</artifactId>
   <version>6.0.16.Final</version>
</dependency>

引入spring-boot-starter-web后,会自动引入hibernate-validator,所以无需再单独引入hibernate-validator。

校验使用说明

校验样例

  • 非分组方式

实体类:


import javax.validation.constraints.NotBlank;
public class SysUserParam {
	@NotBlank(message="用户名不能为空",groups={Groups.Save.class,Groups.Update.class})
	private String userName;
}

控制层:

import org.springframework.validation.annotation.Validated;

@RestController
@RequestMapping("/sys/user")
public class SysUserController {
	@Autowired
	private SysUserService sysUserService;
	@PostMapping("save")
	public CommonResult<?> save(@RequestBody @Validated SysUserParam param) {
		int count = sysUserService.save(param);
		if(count>0) {
			return CommonResult.success();
		} else {
			return CommonResult.fail();
		}
	}
}
  • 分组方式

实体类:

import javax.validation.constraints.NotBlank;
public class SysUserParam {
	@NotBlank(message="用户名不能为空",groups={Groups.Save.class,Groups.Update.class})
	private String userName;
}

控制层:

import org.springframework.validation.annotation.Validated;
import com.mldong.common.validator.Groups;

@RestController
@RequestMapping("/sys/user")
public class SysUserController {
	@Autowired
	private SysUserService sysUserService;
	@PostMapping("save")
	public CommonResult<?> save(@RequestBody @Validated({Groups.Save.class}) SysUserParam param) {
		int count = sysUserService.save(param);
		if(count>0) {
			return CommonResult.success();
		} else {
			return CommonResult.fail();
		}
	}
}

分组类:

package com.mldong.common.validator;
/**
 * 校验分组-常用的其实就Save and Update,共用一个实体
 * @author mldong
 *
 */
public interface Groups {
    interface Save{}
    interface Update{}
    interface Delete{}
    interface One{}
    interface Two{}
    interface Three{}
    interface Four{}
}
  • 自定义注解
import javax.validation.constraints.NotBlank;
import com.mldong.common.annotation.FlagValidator;
public class SysUserParam {
	@FlagValidator(values="1,2,3", required=true,message="类型不能为空,只能为(1,2,3)")
    private String type;
}

校验全局异常处理

  • DefaultExceptionHandler.java
/**
     * 控制层参数校验异常
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class) 
    @ResponseBody
    public CommonResult<?> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e){
    	log.error("参数校验异常:{}", e.getMessage());
    	;
    	String errorMsg = String.join(",", e.getBindingResult().getAllErrors().stream().map(item->{
    		return item.getDefaultMessage();
    	}).collect(Collectors.toList()));
    	return CommonResult.fail(errorMsg, e.getBindingResult());
    }

常用校验注解

注解 说明
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
@Size(min=, max=) 验证对象(Array,Collection,Map)长度是否在给定的范围之内
@Length(min=, max=) 验证对象(String)长度是否在给定的范围之内
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Range(min=, max=) 检查数字是否介于min和max之间.
@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumbe 信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。

开始编码

目录结构

只罗列需要新增或修改的文件

├── mldong-admin  管理端接口
    ├── src/main/java
      └──com.mldong.modules.sys
        ├── controller 控制层
        	└── SysUserController.java
    	└── dto
    		└── SysUserParam.java
├── mldong-common  工具类及通用代码
    ├── src/main/java
    	├── com.mldong.common.annotation
    		├── FlagValidator.java
    		└── PhoneValidator.java
    	├── com.mldong.common.base
    		└── CommonResult.java
    	├── com.mldong.common.exception
    		└── DefaultExceptionHandler.java
    	├── com.mldong.common.validator
    		├── FlagValidatorClass.java
    		├── Groups.java
    		└── PhoneValidatorClass
├── mldong-generator  代码生成器
└── mldong-mapper 持久层

文件说明

  • mldong-admin/src/main/java/com/mldong/modules/sys/controller/SysUserontroller.java

控制层,新增数据校验

package com.mldong.modules.sys.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.mldong.common.base.CommonPage;
import com.mldong.common.base.CommonResult;
import com.mldong.common.base.IdParam;
import com.mldong.common.base.IdsParam;
import com.mldong.common.validator.Groups;
import com.mldong.modules.sys.dto.SysUserParam;
import com.mldong.modules.sys.entity.SysUser;
import com.mldong.modules.sys.service.SysUserService;

@RestController
@RequestMapping("/sys/user")
@Api(tags="sys-用户管理")
public class SysUserController {
	@Autowired
	private SysUserService sysUserService;
	/**
	 * 添加用户
	 * @param param
	 * @return
	 */
	@PostMapping("save")
	@ApiOperation(value="添加用户", notes="添加用户")
	public CommonResult<?> save(@RequestBody @Validated({Groups.Save.class}) SysUserParam param) {
		int count = sysUserService.save(param);
		if(count>0) {
			return CommonResult.success();
		} else {
			return CommonResult.fail();
		}
	}
	/**
	 * 更新用户
	 * @param param
	 * @return
	 */
	@PostMapping("update")
	@ApiOperation(value="更新用户", notes="更新用户")
	public CommonResult<?> update(@RequestBody @Validated({Groups.Update.class}) SysUserParam param) {
		int count = sysUserService.update(param);
		if(count>0) {
			return CommonResult.success();
		} else {
			return CommonResult.fail();
		}
	}
	/**
	 * 删除用户
	 * @param param
	 * @return
	 */
	@PostMapping("remove")
	@ApiOperation(value="删除用户", notes="删除用户")
	public CommonResult<?> remove(@RequestBody @Validated IdsParam param) {
		int count = sysUserService.remove(param.getIds());
		if(count>0) {
			return CommonResult.success();
		} else {
			return CommonResult.fail();
		}
	}
	/**
	 * 通过id获取用户
	 * @param param
	 * @return
	 */
	@PostMapping("get")
	@ApiOperation(value="通过id获取用户", notes="通过id获取用户")
	public CommonResult<SysUser> get(@RequestBody @Validated IdParam param) {
		return CommonResult.success(sysUserService.get(param.getId()));
	}
	/**
	 * 分页查询用户列表
	 * @param param
	 * @return
	 */
	@PostMapping("list")
	@ApiOperation(value="分页查询用户列表", notes="分页查询用户列表")
	public CommonResult<CommonPage<SysUser>> list(SysUserParam param, @ApiParam(value="第n页,默认1")@RequestParam(defaultValue="1")Integer pageNum, @ApiParam(value="每页大小,默认10")@RequestParam(defaultValue="10")int pageSize) {
		return CommonResult.success(sysUserService.list(param, pageNum, pageSize));
	}
}
  • mldong-admin/src/main/java/com/mldong/modules/sys/dto/SysUserParam.java

新增数据校验规则属性注解

package com.mldong.modules.sys.dto;

import io.swagger.annotations.ApiModelProperty;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;

import com.mldong.common.annotation.PhoneValidator;
import com.mldong.common.validator.Groups;
import com.mldong.modules.sys.entity.SysUser;

public class SysUserParam {
	@ApiModelProperty(name="用户名",required=true)
	@NotBlank(message="用户名不能为空",groups={Groups.Save.class,Groups.Update.class})
	private String userName;
	@ApiModelProperty(name="姓名",required=true)
	@NotBlank(message="姓名不能为空",groups={Groups.Save.class,Groups.Update.class})
	private String realName;
	@ApiModelProperty(name="头像",required=false)
	private String avatar;
	@ApiModelProperty(name="邮箱",required=false)
	@Email(message="邮箱不合法",groups={Groups.Save.class,Groups.Update.class})
	private String email;
	@ApiModelProperty(name="手机号",required=true)
	@PhoneValidator(message="手机号不合法",required=false,groups={Groups.Save.class,Groups.Update.class})
	private String mobilePhone;
	@ApiModelProperty(name="联系电话",required=false)
	private String telephone;
	@ApiModelProperty(name="密码",required=true)
	@NotBlank(message="密码不能为空",groups={Groups.Save.class})
	private String password;
	@ApiModelProperty(name="确认密码",required=true)
	@NotBlank(message="确认密码不能为空",groups={Groups.Save.class})
	private String confirmPassword;
	@ApiModelProperty(name="性别",required=false)
	private SysUser.SexEnum sex;
	// get set 略
}
  • mldong-common/src/main/java/com/mldong/common/annotation/FlagValidator.java

自定义校验器注解

package com.mldong.common.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

import com.mldong.common.validator.FlagValidatorClass;
/**
 * flag 注解
 * @author mldong
 *
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER,ElementType.FIELD})
@Constraint(validatedBy = FlagValidatorClass.class)
public @interface FlagValidator  {
    // flag的有效值多个使用,隔开
    String values();
    // 是否必填
    boolean required() default false;
    // 提示内容
    String message() default "flag不存在";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}


  • mldong-common/src/main/java/com/mldong/common/validator/FlagValidatorClass.java

实现FlagValidator校验规则类:

package com.mldong.common.validator;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import com.mldong.common.annotation.FlagValidator;
/**
 * flag校验器
 * @author mldong
 *
 */
public class FlagValidatorClass implements ConstraintValidator<FlagValidator,Object> {

    // 保存flag值
    private String values;
    private boolean required;
    @Override
    public void initialize(FlagValidator flagValidator) {
        // 注解内配的值赋值给变量
        this.values = flagValidator.values();
        this.required = flagValidator.required();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
       if(this.required) {
    	   if(null == value || "".equals(value)) {
    		   return false;
    	   }
       }
    	// 切割获取值
        String[] valueArray = values.split(",");
        Boolean isFlag = false;
        for (int i = 0; i < valueArray.length; i++){
            // 存在一致就跳出循环
            if (valueArray[i] .equals(value.toString())){
                isFlag = true;
                break;
            }
        }
        return isFlag;
    }
}

  • mldong-common/src/main/java/com/mldong/common/annotation/PhoneValidator.java

自定义手机号码校验注解

package com.mldong.common.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

import com.mldong.common.validator.PhoneValidatorClass;

/**
 * 手机校验注解
 * @author mldong
 *
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER,ElementType.FIELD})
@Constraint(validatedBy = PhoneValidatorClass.class)
public @interface PhoneValidator {
	// 是否必填
    boolean required() default true;
    // 提示内容
    String message() default "手机号不合法";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
  • mldong-common/src/main/java/com/mldong/common/validator/PhoneValidatorClass.java

实现自定义手机号码注解类

package com.mldong.common.validator;

import java.util.regex.Pattern;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import com.mldong.common.annotation.PhoneValidator;
/**
 * 手机号校验器
 * @author mldong
 *
 */
public class PhoneValidatorClass implements ConstraintValidator<PhoneValidator,String> {
    private Pattern pattern = Pattern.compile("^1[3456789]\\d{9}$");
    private boolean required;
    @Override
    public void initialize(PhoneValidator validator) {
        // 注解内配的值赋值给变量
        this.required = validator.required();
    }
    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
    	if(this.required) {
     	   if(null == value || "".equals(value)) {
     		   return false;
     	   }
        }
        return pattern.matcher(value).matches();
    }
}
  • mldong-common/src/main/java/com/mldong/common/validator/Groups.java

分组接口,主要目的是区分分组

package com.mldong.common.validator;
/**
 * 校验分组
 * @author mldong
 *
 */
public interface Groups {
    interface Save{}
    interface Update{}
    interface Delete{}
    interface One{}
    interface Two{}
    interface Three{}
    interface Four{}
}
  • mldong-common/src/main/java/com/mldong/common/exception/DefaultExceptionHandler.java

新增处理数据校验异常处理及上一篇文章自定义枚举类转换异常处理,还有优化空间,最近想赶一下进度,快速出一个版本,后续会优化。

package com.mldong.common.exception;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.converter.HttpMessageConversionException;
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;

import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.mldong.base.CodedEnum;
import com.mldong.common.base.CommonError;
import com.mldong.common.base.CommonResult;
import com.mldong.common.base.constant.GlobalErrEnum;
 /**
  * 全局异常处理类
  * @author mldong
  *
  */
@ControllerAdvice
public class DefaultExceptionHandler {
 
    private static Logger log = LoggerFactory.getLogger(DefaultExceptionHandler.class);  
    /**
     * 服务层异常
     * @param e
     * @return
     */
    @ExceptionHandler(BizException.class)
    @ResponseBody
    public CommonResult<?> serviceExceptionHandler(BizException e) {
    	log.error("服务层异常:", e);
    	return CommonResult.error(e.getError());
    }
    /**
     * json转换异常,一般为自定义枚举类转换出问题
     * @param e
     * @return
     */
    @ExceptionHandler(HttpMessageConversionException.class)
    @ResponseBody
    public CommonResult<?> invalidDefinitionExceptionHandler(HttpMessageConversionException e) {
    	log.error("json转换异常:", e);
    	if(e.getCause().getClass().isAssignableFrom(InvalidDefinitionException.class)){
    		// 如果是InvalidDefinitionException
    		final InvalidDefinitionException invalidDefinitionException = (InvalidDefinitionException) e.getCause();
    		if(invalidDefinitionException.getType().isEnumType()){
    			// 如果是自定义枚举类异常
    			try {
    				String typeName = invalidDefinitionException.getType().getTypeName();
    				typeName = typeName.substring(20, typeName.length()-1);
					Class<?> clazz = Class.forName(typeName);
					List<Map<String,Object>> list = new ArrayList<>();
					Arrays.stream(clazz.getEnumConstants()).forEach(i->{
						if(i instanceof CodedEnum) {
							CodedEnum coded = (CodedEnum)i;
							int value = coded.getValue();
							String name = coded.getName();
							Map<String,Object> map = new HashMap<>();
							map.put("value", value);
							map.put("name", name);
							list.add(map);
						}
					});
					return CommonResult.error(new CommonError() {
						
						@Override
						public int getValue() {
							return GlobalErrEnum.GL99990100.getValue();
						}
						
						@Override
						public String getName() {
							return invalidDefinitionException.getMessage();
						}
					},list);
    			} catch (ClassNotFoundException e1) {
					return CommonResult.fail(e1);
				}
    		}
    	}
    	return CommonResult.fail();
    }
    
    /**
     * 控制层参数校验异常
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class) 
    @ResponseBody
    public CommonResult<?> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e){
    	log.error("参数校验异常:{}", e.getMessage());
    	final String errorMsg = String.join(",", e.getBindingResult().getAllErrors().stream().map(item->{
    		return item.getDefaultMessage();
    	}).collect(Collectors.toList()));
    	return CommonResult.error(new CommonError() {
			
			@Override
			public int getValue() {
				return GlobalErrEnum.GL99990100.getValue();
			}
			
			@Override
			public String getName() {
				return errorMsg;
			}
		});
    }
    /**
     * 其他异常
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class) 
    @ResponseBody
    public CommonResult<?> excetionHandler(Exception e){
    	log.error("未捕获异常:", e);
    	return CommonResult.fail(e.getMessage(), null);
    }
}
  • mldong-common/src/main/java/com/mldong/common/base/CommonResult.java

统一返回新增了错误码的处理

package com.mldong.common.base;

import java.io.Serializable;

/**
 * 统一返回
 * @author mldong
 *
 */
public class CommonResult<T> implements Serializable{

	/**
	 * 
	 */
	private static final long serialVersionUID = -7504120764828454657L;
	private int code;
    private String msg;
    private T data;
	/**
	 * 
	 */
    
	@SuppressWarnings("unused")
	private CommonResult () {
		
	}
	public CommonResult(Type type,String msg) {
		this.code = type.value;
		this.msg = msg;
	}
	
	public CommonResult(Type type,String msg,T data) {
		this.code = type.value;
		this.msg = msg;
		this.data = data;
		if(data instanceof CommonError) {
			CommonError error = (CommonError) data;
			this.code = error.getValue();
			this.msg = error.getName();
		}
	}
	public CommonResult(CommonError error) {
		this.code = error.getValue(); 
		this.msg = error.getName();
	}
	public CommonResult(CommonError error,T data) {
		this.code = error.getValue(); 
		this.msg = error.getName();
		this.data = data;
	}
	public enum Type {
		SUCCESS(0),
		FAIL(99999999)
		;
		private final int value;
		Type(int value) {
			this.value = value;
		}
	}
	/**
	 * 返回成功
	 * @param msg
	 * @param data
	 * @return
	 */
	public static <T> CommonResult<T> success(String msg,T data) {
		return new CommonResult<T>(Type.SUCCESS, msg, data);
	}
	/**
	 * 返回成功
	 * @param data
	 * @return
	 */
	public static <T> CommonResult<T> success(T data) {
		return success("操作成功",data);
	}
	/**
	 * 返回成功
	 * @return
	 */
	public static <T> CommonResult<T> success() {
		return success("操作成功", null);
	}
	/**
	 * 返回失败
	 * @param msg
	 * @param data
	 * @return
	 */
	public static <T> CommonResult<T> fail(String msg,T data) {
		return new CommonResult<T>(Type.FAIL, msg, data);
	}
	/**
	 * 返回失败
	 * @param data
	 * @return
	 */
	public static <T> CommonResult<T> fail(T data) {
		return fail("操作失败",data);
	}
	/**
	 * 返回失败
	 * @return
	 */
	public static <T> CommonResult<T> fail() {
		return fail("操作失败", null);
	}
	/**
	 * 自定义异常返回
	 * @return
	 */
	public static <T> CommonResult<T> error(CommonError error,T data) {
		return new CommonResult<T>(error, data);
	}
	/**
	 * 自定义异常返回
	 * @return
	 */
	public static <T> CommonResult<T> error(CommonError error) {
		return error(error,null);
	}
	public int getCode() {
		return code;
	}
	public void setCode(int code) {
		this.code = code;
	}
	public String getMsg() {
		return msg;
	}
	public void setMsg(String msg) {
		this.msg = msg;
	}
	public T getData() {
		return data;
	}
	public void setData(T data) {
		this.data = data;
	}
	
}

启动运行项目

MldongAdminApplication.java

右键->Run As -> Java Application

访问演示

使用postman或者swaggerui测试,略。

  • 参数校验不通过

入参:

{
	"avatar": "aaaa",
	"confirmPassword": "123456",
	"email": "aa@qq.com",
	"mobilePhone": "18565265262",
	"password": "123456",
	"realName": "",
	"telephone": "18565111",
	"userName": "",
	"sex": 1
}

返回:

{
  "code": 99990100,
  "msg": "姓名不能为空,用户名不能为空"
}
  • 枚举类入参不正确

入参:

{
	"avatar": "aaaa",
	"confirmPassword": "123456",
	"email": "aa@qq.com",
	"mobilePhone": "18565265262",
	"password": "123456",
	"realName": "aaa",
	"telephone": "18565111",
	"userName": "aaaafdf",
	"sex": 66
}

返回:

{
  "code": 99990100,
  "msg": "Cannot construct instance of `com.mldong.modules.sys.entity.SysUser$SexEnum`, problem: No value present\n at [Source: (PushbackInputStream); line: 10, column: 9] (through reference chain: com.mldong.modules.sys.dto.SysUserParam[\"sex\"])",
  "data": [
    {
      "name": "男",
      "value": 1
    },
    {
      "name": "女",
      "value": 2
    },
    {
      "name": "未知",
      "value": 3
    }
  ]
}

项目源码地址

  • 后端

gitee.com/mldong/mldo…

  • 前端

gitee.com/mldong/mldo…

相关文章

打造一款适合自己的快速开发框架-先导篇

打造一款适合自己的快速开发框架-后端脚手架搭建

打造一款适合自己的快速开发框架-集成mapper

打造一款适合自己的快速开发框架-集成swaggerui和knife4j

打造一款适合自己的快速开发框架-通用类封装之统一结果返回、统一异常处理

打造一款适合自己的快速开发框架-业务错误码规范及实践

打造一款适合自己的快速开发框架-框架分层及CURD样例

打造一款适合自己的快速开发框架-mapper逻辑删除及枚举类型规范