前言
在前面讲的三层架构中,数据校验是放在控制层上的。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 | 信用卡验证 |
验证是否是邮件地址,如果为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
}
]
}
项目源码地址
- 后端
- 前端
相关文章
打造一款适合自己的快速开发框架-集成swaggerui和knife4j