1. 简介

我们都知道前台的验证只是为了满足界面的友好性、客户体验性等等。但是如果仅靠前端进行数据合法性校验,是远远不够的。因为非法用户可能会直接从客户端获取到请求地址进行非法请求,所以后台的校验是必须的;特别是应用如果不允许输入空值,对数据的合法行有要求的情况下。

2. 开撸

2.1 项目结构

结构说明:

├── java
│   └── com
│   └── ldx
│   └── valid
│   ├── ValidApplication.java # 启动类
│   ├── annotation
│   │   └── Phone.java # 自定义验证注解
│   ├── config
│   │   └── ValidatorConfig.java # 表单验证配置类
│   ├── controller
│   │   └── SysUserController.java # 用户管理控制器
│   ├── exception
│   │   ├── BusinessException.java # 业务异常类
│   │   └── GlobalExceptionHandler.java # 统一异常处理类
│   ├── model
│   │   ├── SysUser.java # 用户信息实体
│   │   └── ValidationInterface.java # 表单验证的通用分组接口
│   ├── util
│   │   └── CommonResult.java # 接口返回封装类
│   └── validation
│   └── PhoneValidator.java #自定义验证实现类
└── resources
├── application.yaml # 配置文件
└── messages
└── validation
└── messages.properties # 自定义验证信息源

2.1 quick start

2.1.1 导入依赖

创建springboot项目导入以下依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.ldx</groupId>
<artifactId>valid</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>valid</name>
<description>表单验证demo</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- web支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 表单验证 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

2.1.2 添加配置类

创建表单验证配置类,配置快速校验,不用等全部的参数校验完,只要有错,马上抛出。

import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory; /**
* 配置 Hibernate 参数校验
* @author ludangxin
* @date 2021/8/5
*/
@Configuration
public class ValidatorConfig {
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
//快速校验,只要有错马上返回
postProcessor.setValidator(validator());
return postProcessor;
} @Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.addProperty("hibernate.validator.fail_fast", "true")
.buildValidatorFactory();
return validatorFactory.getValidator();
}
}

2.1.3 添加实体类

import lombok.*;
import javax.validation.constraints.*;
import java.io.Serializable; /**
* 用户信息管理
* @author ludangxin
* @date 2021/8/5
*/
@Data
public class SysUser implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
private Long id; /**
* 用户名
*/
@NotEmpty(message = "用户名称不能为空")
private String username; /**
* 密码
*/
@Size(min = 6, max = 16, message = "密码长度必须在{min}-{max}之间")
private String password = "123456"; /**
* 邮箱地址
*/
@Email(message = "邮箱地址不合法")
@NotEmpty(message = "邮箱不能为空")
private String email; /**
* 电话
*/
@Size(min = 11, max = 11, message = "手机号不合法")
@NotEmpty(message = "手机号不能为空")
private String phone;
}

2.1.4 接口返回封装类

import lombok.Data;
import lombok.NoArgsConstructor; /**
* 操作消息提醒
* @author ludangxin
* @date 2021/8/5
*/
@Data
@NoArgsConstructor
public class CommonResult {
/** 状态码 */
private int code; /** 返回内容 */
private String msg; /** 数据对象 */
private Object data; /**
* 初始化一个新创建的 CommonResult 对象
* @param type 状态类型
* @param msg 返回内容
*/
public CommonResult(Type type, String msg) {
this.code = type.value;
this.msg = msg;
} /**
* 初始化一个新创建的 CommonResult 对象
* @param type 状态类型
* @param msg 返回内容
* @param data 数据对象
*/
public CommonResult(Type type, String msg, Object data) {
this.code = type.value;
this.msg = msg;
if (data != null) {
this.data = data;
}
} /**
* 返回成功消息
* @return 成功消息
*/
public static CommonResult success() {
return CommonResult.success("操作成功");
} /**
* 返回成功数据
* @return 成功消息
*/
public static CommonResult success(Object data) {
return CommonResult.success("操作成功", data);
} /**
* 返回成功消息
* @param msg 返回内容
* @return 成功消息
*/
public static CommonResult success(String msg) {
return CommonResult.success(msg, null);
} /**
* 返回成功消息
* @param msg 返回内容
* @param data 数据对象
* @return 成功消息
*/
public static CommonResult success(String msg, Object data) {
return new CommonResult(Type.SUCCESS, msg, data);
} /**
* 返回警告消息
* @param msg 返回内容
* @return 警告消息
*/
public static CommonResult warn(String msg) {
return CommonResult.warn(msg, null);
} /**
* 返回警告消息
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static CommonResult warn(String msg, Object data) {
return new CommonResult(Type.WARN, msg, data);
} /**
* 返回错误消息
* @return 错误信息
*/
public static CommonResult error() {
return CommonResult.error("操作失败");
} /**
* 返回错误消息
* @param msg 返回内容
* @return 错误消息
*/
public static CommonResult error(String msg) {
return CommonResult.error(msg, null);
} /**
* 返回错误消息
* @param msg 返回内容
* @param data 数据对象
* @return 错误消息
*/
public static CommonResult error(String msg, Object data) {
return new CommonResult(Type.ERROR, msg, data);
} /**
* 状态类型
*/
public enum Type {
/** 成功 */
SUCCESS(200),
/** 警告 */
WARN(301),
/** 错误 */
ERROR(500);
private final int value; Type(int value){
this.value = value;
} public int value() {
return this.value;
}
}
}

2.1.5 控制器

使用@Validated注解标识需要验证的类,使用BindingResult类接收错误信息

import com.ldx.valid.exception.BusinessException;
import com.ldx.valid.model.SysUser;
import com.ldx.valid.model.ValidationInterface;
import com.ldx.valid.util.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.NotEmpty;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; /**
* 用户管理控制器
*
* @author ludangxin
* @date 2021/8/5
*/
@Slf4j
@RestController
@RequestMapping("sys/user")
public class SysUserController {
private static final List<SysUser> USERS = new ArrayList<>(); // 数据初始化
static {
SysUser user = new SysUser();
user.setId(1L);
user.setUsername("zhangsan");
user.setPhone("13566666666");
user.setEmail("example@qq.com");
USERS.add(user);
SysUser user1 = new SysUser();
user1.setId(2L);
user1.setUsername("lisi");
user1.setPhone("13588888888");
user1.setEmail("example1@qq.com");
USERS.add(user1);
} /**
* 新增用户信息
* @param sysUser 用户信息
* @return 成功标识
*/
@PostMapping
public CommonResult add(@Validated @RequestBody SysUser sysUser, BindingResult result) {
FieldError fieldError = result.getFieldError(); if(Objects.nonNull(fieldError)) {
String field = fieldError.getField();
Object rejectedValue = fieldError.getRejectedValue();
String msg = "[" + fieldError.getDefaultMessage() + "]";
log.error("{}:字段=={}\t值=={}", msg, field, rejectedValue);
return CommonResult.error(msg);
} USERS.add(sysUser);
return CommonResult.success("新增成功");
}
}

2.1.5 启动测试

新增时,故意将email信息填错,测试结果符合预期。

log日志:

[nio-8080-exec-9] c.l.valid.controller.SysUserController   : [邮箱地址不合法]:字段==email	值==123

3. 分组校验

groups是用来干什么的?

因为一个实体不可能只干一种操作,一个实体必然存在增删改查操作,那么问题就来了
如果我要根据id进行更新操作,那么id肯定不能为空
这时候我还要进行新增操作,因为id是新增数据库操作才产生的,接受数据的时候我肯定是没有id的
所以就产生矛盾了
那么groups这个参数就起作用了,它可以表示我这个注解属于哪个组,这样就解决这个尴尬的问题了。

当在controller中校验表单数据时,如果使用了groups,那么没有在这个分组下的属性是不会校验的

3.1 添加分组接口

/**
* 用于表单验证的通用分组接口
* @author ludangxin
* @date 2021/8/5
*/
public interface ValidationInterface {
/**
* 新增分组
*/
interface add{} /**
* 删除分组
*/
interface delete{} /**
* 查询分组
*/
interface select{} /**
* 更新分组
*/
interface update{}
}

如果还有其它特殊的分组要求 直接在DO中创建interface即可

例:如果还有个需要验证username 和 password(只有这两个参数) 的 select操作

直接在SysUser中创建UsernamePasswordValidView 的接口即可

3.2 修改实体类

将属性进行分组

import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable; /**
* 用户信息管理
* @author ludangxin
* @date 2021/8/5
*/
@Data
public class SysUser implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@NotNull(message = "id不能为空", groups = {ValidationInterface.update.class})
private Long id; /**
* 用户名
*/
@NotEmpty(message = "用户名称不能为空", groups = {
ValidationInterface.update.class,
ValidationInterface.add.class})
private String username; /**
* 密码
*/
@Size(min = 6, max = 16, message = "密码长度必须在{min}-{max}之间", groups = {
ValidationInterface.update.class,
ValidationInterface.add.class})
private String password = "123456"; /**
* 邮箱地址
*/
@Email(message = "邮箱地址不合法", groups = {
ValidationInterface.update.class,
ValidationInterface.add.class,
ValidationInterface.select.class})
@NotEmpty(message = "邮箱不能为空", groups = ValidationInterface.add.class)
private String email; /**
* 电话
*/
@Size(min = 11, max = 11, message = "手机号不合法", groups = {
ValidationInterface.update.class,
ValidationInterface.add.class,
ValidationInterface.select.class})
@NotEmpty(message = "手机号不能为空",groups = {ValidationInterface.add.class})
private String phone;
}

3.3 修改控制器

添加操作方法,并且方法形参上指定验证的分组

import com.ldx.valid.exception.BusinessException;
import com.ldx.valid.model.SysUser;
import com.ldx.valid.model.ValidationInterface;
import com.ldx.valid.util.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.NotEmpty;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; /**
* 用户管理控制器
* @author ludangxin
* @date 2021/8/5
*/
@Slf4j
@RestController
@RequestMapping("sys/user")
public class SysUserController {
private static final List<SysUser> USERS = new ArrayList<>(); // 数据初始化
static {
SysUser user = new SysUser();
user.setId(1L);
user.setUsername("zhangsan");
user.setPhone("13566666666");
user.setEmail("example@qq.com");
USERS.add(user);
SysUser user1 = new SysUser();
user1.setId(2L);
user1.setUsername("lisi");
user1.setPhone("13588888888");
user1.setEmail("example1@qq.com");
USERS.add(user1);
} /**
* 根据手机号或邮箱查询用户信息
* @param sysUser 查询条件
* @return 用户list
*/
@GetMapping
public CommonResult queryList(@Validated(value = ValidationInterface.select.class) SysUser sysUser,
BindingResult result)
{
FieldError fieldError = result.getFieldError(); if(Objects.nonNull(fieldError)) {
return CommonResult.error(getErrorMsg(fieldError));
} String phone = sysUser.getPhone();
String email = sysUser.getEmail(); if(phone == null && email == null) {
return CommonResult.success(USERS);
} List<SysUser> queryResult = USERS.stream()
.filter(obj -> obj.getPhone().equals(phone) || obj.getEmail().equals(email))
.collect(Collectors.toList());
return CommonResult.success(queryResult);
} /**
* 新增用户信息
* @param sysUser 用户信息
* @return 成功标识
*/
@PostMapping
public CommonResult add(@Validated(value = ValidationInterface.add.class)
@RequestBody SysUser sysUser,
BindingResult result)
{
FieldError fieldError = result.getFieldError(); if(Objects.nonNull(fieldError)) {
return CommonResult.error(getErrorMsg(fieldError));
} Long id = (long) (USERS.size() + 1);
sysUser.setId(id);
USERS.add(sysUser);
return CommonResult.success("新增成功");
} /**
* 根据Id更新用户信息
* @param sysUser 用户信息
* @return 成功标识
*/
@PutMapping("{id}")
public CommonResult updateById(@PathVariable("id") Long id,
@Validated(value = ValidationInterface.update.class)
@RequestBody SysUser sysUser,
BindingResult result)
{
FieldError fieldError = result.getFieldError(); if(Objects.nonNull(fieldError)) {
return CommonResult.error(getErrorMsg(fieldError));
} for(int i = 0; i < USERS.size(); i++) {
if(USERS.get(i).getId().equals(id)) {
USERS.set(i,sysUser);
}
}
return CommonResult.success("更新成功");
} /**
* 根据Id删除用户信息
* @param id 主键
* @return 成功标识
*/
@DeleteMapping("{id}")
public CommonResult deleteById(@PathVariable Long id) {
USERS.removeIf(obj -> obj.getId().equals(id));
return CommonResult.success("删除成功");
} /**
* 获取表单验证错误msg
* @param fieldError 报错字段
* @return msg
*/
public String getErrorMsg(FieldError fieldError) {
String field = fieldError.getField();
Object rejectedValue = fieldError.getRejectedValue();
String msg = "[" + fieldError.getDefaultMessage() + "]";
log.error("{}:字段=={}\t值=={}", msg, field, rejectedValue);
return msg;
}
}

3.4 启动测试

查询:

​ 输入不合法手机号

新增:

​ 正常情况

​ 去掉邮箱

修改:

​ 去掉id

删除:

4. 自定义验证

很多时候框架提供的功能并不能满足我们的业务场景,这时我们需要自定义一些验证规则来完成验证。

4.1 添加注解

import com.ldx.valid.validation.PhoneValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME; /**
* 验证手机号是否合法
* @author ludangxin
* @date 2021/8/7
*/
@Documented
@Constraint(validatedBy = {PhoneValidator.class})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface Phone {
//默认错误消息
String message() default "不是一个合法的手机号"; //分组
Class<?>[] groups() default {}; //载荷 将某些元数据信息与给定的注解声明相关联的方法
Class<? extends Payload>[] payload() default {}; //指定多个时使用
@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Documented
@interface List {
Phone[] value();
}
}

4.2 编写验证逻辑

import javax.validation.ConstraintValidator;
import com.ldx.valid.annotation.Phone;
import javax.validation.ConstraintValidatorContext;
import java.util.Objects;
import java.util.regex.Pattern; /**
* 手机号校验器
* @author ludangxin
* @date 2021/8/7
*/
public class PhoneValidator implements ConstraintValidator<Phone, String> { /**
* 手机号正则表达式
*/
private static final String REGEXP_PHONE = "^1[3456789]\\d{9}$"; @Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if(Objects.isNull(value)) {
return true;
} return Pattern.matches(REGEXP_PHONE, value);
}
}

4.3 修改实体

SysUser.phone 属性添加注解@Phone

@Phone(groups = {
ValidationInterface.update.class,
ValidationInterface.add.class,
ValidationInterface.select.class})
@NotEmpty(message = "手机号不能为空", groups = {ValidationInterface.add.class})
private String phone;

4.4 启动测试

输入错误的手机号进行测试

4.5 @Pattern

当然validation也提供了基于正则匹配的注解@Pattern

@Pattern(message = "手机号不合法", regexp = "^1[3456789]\\d{9}$", groups = {ValidationInterface.add.class})
@NotEmpty(message = "手机号不能为空", groups = {ValidationInterface.add.class})
private String phone;

注意是javax.validation.constraints包下的

测试

5. 调用过程验证

有的时候我们在参数传输过程中需要对传入的对象做参数验证,但是上面介绍的都是对参数绑定时的验证,那能不能使用validation进行验证呢?

答案肯定是可以的。

5.1 使用 spring bean

5.1.1 注入validator

bean validator 是我们在config文件中定义的bean,如果使用了springboot默认的配置ValidationAutoConfiguration::defaultValidator(),直接注入bean name defaultValidator即可

@Resource(name = "validator")
javax.validation.Validator validator;

5.1.2 定义验证方法

public void validateParams(SysUser user) {
validator.validate(user, ValidationInterface.add.class).stream().findFirst().ifPresent(obj -> {
String objName = obj.getRootBean().getClass().getSimpleName();
String fieldName = obj.getPropertyPath().toString();
Object val = obj.getInvalidValue();
String msg = obj.getMessage();
String errMsg = MessageFormat.format(msg + ":对象:{0},字段:{1},值:{2}", objName, fieldName, val);
throw new RuntimeException(errMsg);
});

5.1.2 启动验证

调用新增方法,通过新增方法调用validateParams方法

报错日志如下

java.lang.RuntimeException: 手机号不合法:对象:SysUser,字段:phone,值:135999

5.2 非spring环境验证

5.2.1 定义验证方法

直接获取默认的工厂类,然后获取验证对象进行验证

public static void main(String[] args) {
ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
Validator validator = vf.getValidator();
SysUser user = new SysUser();
user.setId(1L);
user.setUsername("zhangsan");
user.setPhone("1356666");
user.setEmail("example@qq.com");
validator.validate(user, ValidationInterface.add.class).stream().findFirst().ifPresent(obj -> {
String objName = obj.getRootBean().getClass().getSimpleName();
String fieldName = obj.getPropertyPath().toString();
Object val = obj.getInvalidValue();
String msg = obj.getMessage();
String errMsg = MessageFormat.format(msg + ":对象:{0},字段:{1},值:{2}", objName, fieldName, val);
throw new RuntimeException(errMsg);
});
}

5.2.2 启动验证

报错信息如下,符合预期

Exception in thread "main" java.lang.RuntimeException: 手机号不合法:对象:SysUser,字段:phone,值:1356666
at com.ldx.valid.controller.SysUserController.lambda$main$4(SysUserController.java:215)
at java.util.Optional.ifPresent(Optional.java:159)
at com.ldx.valid.controller.SysUserController.main(SysUserController.java:209)

6. 方法参数验证

有的时候我们想在方法上直接进行参数验证,步骤如下

6.1 修改控制器

直接在类上添加注解@Validated,并在方法上直接进行验证

@Slf4j
@Validated
@RestController
@RequestMapping("sys/user")
public class SysUserController {
... 省略代码
/**
* 根据手机号和邮箱查询用户信息
* @param phone 手机号
* @return 用户list
*/
@GetMapping("selectByPhone")
public CommonResult queryByPhone(@NotEmpty(message = "手机号不能为空") String phone) {
List<SysUser> queryResult = USERS.stream()
.filter(obj -> obj.getPhone().equals(phone))
.collect(Collectors.toList());
return CommonResult.success(queryResult);
}
}

6.2 启动验证

不给phone字段赋值,操作结果符合预期

错误日志:

javax.validation.ConstraintViolationException: queryByPhone.phone: 手机号不能为空

7. 统一异常处理

在上面的参数验证中,验证的错误信息是通过BindingResult result参数进行接收的,在每个方法中异常处理如出一辙,特别麻烦。甚至在step 5,6都是直接将异常的堆栈信息返回给前端,这对于用来说是非常不友好的。而且有的情况下需要我们主动抛出业务异常,比方用户不能直接删除已绑定用户的角色。

所以,开撸。

7.1 创建业务异常类

/**
* 业务异常
* @author ludangxin
* @date 2021/8/5
*/
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L; protected final String message; public BusinessException(String message) {
this.message = message;
} public BusinessException(String message, Throwable e) {
super(message, e);
this.message = message;
} @Override
public String getMessage() {
return message;
}
}

7.2 创建全局异常处理器

import com.ldx.valid.util.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.Set; /**
* 全局异常处理器
*
* @author ludangxin
* @date 2021/8/5
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 参数绑定异常类 用于表单验证时抛出的异常处理
*/
@ExceptionHandler(BindException.class)
public CommonResult validatedBindException(BindException e){
log.error(e.getMessage(), e);
BindingResult bindingResult = e.getBindingResult();
FieldError fieldError = e.getFieldError();
String message = "[" + e.getAllErrors().get(0).getDefaultMessage() + "]";
return CommonResult.error(message);
} /**
* 用于方法形参中参数校验时抛出的异常处理
* @param e
* @return
*/
@ExceptionHandler(ConstraintViolationException.class)
public CommonResult handle(ValidationException e) {
log.error(e.getMessage(), e);
String errorInfo = "";
if(e instanceof ConstraintViolationException){
ConstraintViolationException exs = (ConstraintViolationException) e;
Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
for (ConstraintViolation<?> item : violations) {
errorInfo = errorInfo + "[" + item.getMessage() + "]";
}
}
return CommonResult.error(errorInfo);
} /**
* 请求方式不支持
*/
@ExceptionHandler({ HttpRequestMethodNotSupportedException.class })
public CommonResult handleException(HttpRequestMethodNotSupportedException e){
log.error(e.getMessage(), e);
return CommonResult.error("不支持' " + e.getMethod() + "'请求");
} /**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public CommonResult notFount(RuntimeException e) {
log.error("运行时异常:", e);
return CommonResult.error("运行时异常:" + e.getMessage());
} /**
* 系统异常
*/
@ExceptionHandler(Exception.class)
public CommonResult handleException(Exception e) {
log.error(e.getMessage(), e);
return CommonResult.error("服务器错误,请联系管理员");
} /**
* 业务异常
*/
@ExceptionHandler(BusinessException.class)
public CommonResult businessException(HttpServletRequest request, BusinessException e) {
log.error(e.getMessage());
return CommonResult.error(e.getMessage());
}
}

7.3 修改控制器

删除方法中的BindingResult result参数,将错误直接抛给统一异常处理类去解决即可。

import com.ldx.valid.exception.BusinessException;
import com.ldx.valid.model.SysUser;
import com.ldx.valid.model.ValidationInterface;
import com.ldx.valid.service.UserService;
import com.ldx.valid.util.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.*;
import javax.validation.constraints.NotEmpty;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors; /**
* 用户管理控制器
* @author ludangxin
* @date 2021/8/5
*/
@Slf4j
@Validated
@RestController
@RequestMapping("sys/user")
public class SysUserController {
private static final List<SysUser> USERS = new ArrayList<>(); // 数据初始化
static {
SysUser user = new SysUser();
user.setId(1L);
user.setUsername("zhangsan");
user.setPhone("13566666666");
user.setEmail("example@qq.com");
USERS.add(user);
SysUser user1 = new SysUser();
user1.setId(2L);
user1.setUsername("lisi");
user1.setPhone("13588888888");
user1.setEmail("example1@qq.com");
USERS.add(user1);
} /**
* 根据手机号或邮箱查询用户信息
* @param sysUser 查询条件
* @return 用户list
*/
@GetMapping
public CommonResult queryList(@Validated(value = ValidationInterface.select.class) SysUser sysUser) {
String phone = sysUser.getPhone();
String email = sysUser.getEmail(); if(phone == null && email == null) {
return CommonResult.success(USERS);
} List<SysUser> queryResult = USERS.stream()
.filter(obj -> obj.getPhone().equals(phone) || obj.getEmail().equals(email))
.collect(Collectors.toList());
return CommonResult.success(queryResult);
} /**
* 根据手机号和邮箱查询用户信息
* @param phone 手机号
* @return 用户list
*/
@GetMapping("selectByPhone")
public CommonResult queryByPhone(@NotEmpty(message = "手机号不能为空") String phone) {
List<SysUser> queryResult = USERS.stream()
.filter(obj -> obj.getPhone().equals(phone))
.collect(Collectors.toList());
return CommonResult.success(queryResult);
} /**
* 新增用户信息
* @param sysUser 用户信息
* @return 成功标识
*/
@PostMapping
public CommonResult add(@Validated(value = ValidationInterface.add.class) @RequestBody SysUser sysUser) {
Long id = (long) (USERS.size() + 1);
sysUser.setId(id);
USERS.add(sysUser);
return CommonResult.success("新增成功");
} /**
* 根据Id更新用户信息
* @param sysUser 用户信息
* @return 成功标识
*/
@PutMapping("{id}")
public CommonResult updateById(@PathVariable("id") Long id,
@Validated(value = ValidationInterface.update.class)
@RequestBody SysUser sysUser)
{
for(int i = 0; i < USERS.size(); i++) {
if(USERS.get(i).getId().equals(id)) {
USERS.set(i,sysUser);
}
}
return CommonResult.success("更新成功");
} /**
* 根据Id删除用户信息
* @param id 主键
* @return 成功标识
*/
@DeleteMapping("{id}")
public CommonResult deleteById(@PathVariable Long id) {
USERS.removeIf(obj -> obj.getId().equals(id));
return CommonResult.success("删除成功");
} /**
* 测试业务异常
*/
@GetMapping("testException")
public CommonResult testException(String name) {
if(!"张三".equals(name)){
throw new BusinessException("只有张三才可以访问");
}
return CommonResult.success();
}
}

7.4 启动测试

查询:

​ 输出错误的邮箱

根据手机号查询:

​ 输入空值手机号

新增:

​ 输入错误的手机号

测试主动抛出业务异常:

8. 自定义验证信息源

8.1 修改配置文件

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import javax.validation.Validator;
import java.util.Properties; /**
* 配置 Hibernate 参数校验
* @author ludangxin
* @date 2021/8/5
*/
@Configuration
public class ValidatorConfig {
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor(Validator validator) {
MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
postProcessor.setValidator(validator);
return postProcessor;
} /**
* 实体类字段校验国际化引入
*/
@Bean
public Validator validator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
// 设置messages资源信息
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
// 多个用逗号分割
messageSource.setBasenames("classpath:/messages/validation/messages");
// 设置字符集编码
messageSource.setDefaultEncoding("UTF-8");
validator.setValidationMessageSource(messageSource);
// 设置验证相关参数
Properties properties = new Properties();
// 快速失败,只要有错马上返回
properties.setProperty("hibernate.validator.fail_fast", "true");
validator.setValidationProperties(properties);
return validator;
}
}

8.2 添加信息源文件

├───resources
└── messages
└── validation
└── messages.properties
# messages.properties
name.not.empty=用户名不能为空
email.not.valid=${validatedValue}是邮箱地址?
email.not.empty=邮箱不能为空
phone.not.valid=${validatedValue}是手机号?
phone.not.empty=手机号不能为空
password.size.valid=密码长度必须在{min}-{max}之间
id.not.empty=主键不能为空

8.3 修改实体类

import com.ldx.valid.annotation.Phone;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.*;
import java.io.Serializable; /**
* 用户信息管理
* @author ludangxin
* @date 2021/8/5
*/
@Data
public class SysUser implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@NotNull(message = "{id.not.empty}", groups = {ValidationInterface.update.class})
private Long id; /**
* 用户名
*/
@NotEmpty(message = "{name.not.empty}", groups = {
ValidationInterface.update.class,
ValidationInterface.add.class})
private String username; /**
* 密码
*/
@Size(min = 6, max = 16, message = "{password.size.valid}", groups = {
ValidationInterface.update.class,
ValidationInterface.add.class})
private String password = "123456"; /**
* 邮箱地址
*/
@Email(message = "{email.not.valid}",
groups = {
ValidationInterface.update.class,
ValidationInterface.add.class,
ValidationInterface.select.class})
@NotEmpty(message = "{email.not.empty}", groups = ValidationInterface.add.class)
private String email; /**
* 电话
*/
@Pattern(message = "{phone.not.valid}", regexp = "^1[3456789]\\d{9}$",
groups = {ValidationInterface.add.class})
@NotEmpty(message = "{phone.not.empty}", groups = {ValidationInterface.add.class})
private String phone;
}

8.4 启动测试

​ 输入错误的邮箱地址测试:

9. 预置注解清单

注解 说明
@Null 限制只能为null
@NotNull 限制必须不为null
@AssertFalse 限制必须为false
@AssertTrue 限制必须为true
@DecimalMax(value) 限制必须为一个不大于指定值的数字
@DecimalMin(value) 限制必须为一个不小于指定值的数字
@Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Future 限制必须是一个将来的日期
@Max(value) 限制必须为一个不大于指定值的数字
@Min(value) 限制必须为一个不小于指定值的数字
@Past 限制必须是一个过去的日期
@Pattern(value) 限制必须符合指定的正则表达式
@Size(max,min) 限制字符长度必须在min到max之间
@Past 验证注解的元素值(日期类型)比当前时间早
@NotEmpty 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotBlank 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank
@Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

SpringBoot-表单验证-统一异常处理-自定义验证信息源的更多相关文章

  1. jQuery Validate 插件为表单提供了强大的验证功能

    之前项目开发中,表单校验用的jQuery Validate 插件,这个插件为表单提供了强大的验证功能,让客户端表单验证变得更简单,同时提供了大量的定制选项,满足应用程序各种需求.该插件捆绑了一套有用的 ...

  2. SpringBoot小技巧:统一异常处理

    SpringBoot小技巧:统一异常处理 情景描述 对于接口的定义,我们通常会有一个固定的格式,比如: 但是调用方在请求我们的API时把接口地址写错了,就会得到一个404错误,且不同于我们定义的数据格 ...

  3. amazeui的表单开关插件的自定义事件必须添加.bootstrapSwitch 命名空间,给了我们什么启示

    amazeui的表单开关插件的自定义事件必须添加.bootstrapSwitch 命名空间,给了我们什么启示 一.总结 一句话总结:详细看使用文档(说明文档说的真的是非常详细呢,不过循序渐进,不同阶段 ...

  4. easyui 表单验证validatetype——支持自定义验证

    easyui 的validatebox()提供了自定义验证的方法,为此我把一些常用的数据验证汇总了一下,代码如下: 代码 Code highlighting produced by Actipro C ...

  5. angularJS中的表单验证(包括自定义验证)

    表单验证是angularJS一项重要的功能,能保证我们的web应用不会被恶意或错误的输入破坏.Angular表单验证提供了很多表单验证指令,并且能将html5表单验证功能同他自己的验证指令结合起来使用 ...

  6. bootstrapValidator验证表单后清除当次验证的方法

    用bootstrapValidator的resetForm()方法: <!-- // create server begin --> <div class="modal f ...

  7. a标签指定的url,在表单提交前进行js验证的实现

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  8. JS:JS判断提交表单不能为空等验证

    这段代码在<form>中有οnsubmit="return on_submit()",如果 onsubmit ()返回 fasle,表单的元素就不会提交,即action ...

  9. 使用jquery.validate.js插件进行表单里控件的验证

    jsp中具体实现的代码: <%@ page language="java" contentType="text/html; charset=UTF-8" ...

随机推荐

  1. 每日三道面试题,通往自由的道路6——JVM

    茫茫人海千千万万,感谢这一秒你看到这里.希望我的面试题系列能对你的有所帮助!共勉! 愿你在未来的日子,保持热爱,奔赴山海! 每日三道面试题,成就更好自我 今天我们继续聊聊JVM的话题吧! 1. 那你知 ...

  2. 技术实践:教你用Python搭建gRPC服务

    摘要:gRPC是一个高性能.通用的开源RPC框架,其由Google主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf序列化协议开发,且支持众多开发语言. 本文分享自华为云社区& ...

  3. excel VBA数组运用

    Sub a()Dim i人数 As Integer'定义变量Dim i考试成绩() As Integer'定义数组Dim i As Integer'定义变量i人数 = InputBox("输 ...

  4. C. Learning Languages 求联通块的个数

    C. Learning Languages 1 #include <iostream> 2 #include <cstdio> 3 #include <cstring&g ...

  5. AcWing 903. 昂贵的聘礼

    年轻的探险家来到了一个印第安部落里. 在那里他和酋长的女儿相爱了,于是便向酋长去求亲. 酋长要他用10000个金币作为聘礼才答应把女儿嫁给他. 探险家拿不出这么多金币,便请求酋长降低要求. 酋长说:& ...

  6. docker安装redis主从以及哨兵

    docker安装redis主从以及哨兵 本文使用docker在四台机器上部署一主二从三哨兵的Redis主从结构. 服务器配置 192.168.102.128 主节点 centos7.5 192.168 ...

  7. 架构之:serverless架构

    目录 简介 什么是serverless serverless的例子 简单的三层服务 消息驱动 FaaS FaaS的缺点 FaaS的优点 总结 简介 不知道什么时候,出现了一个叫做Serverless架 ...

  8. Python中的json学习

    p.p1 { margin: 0; font: 14px ".PingFang SC"; color: rgba(53, 53, 53, 1) } p.p2 { margin: 0 ...

  9. JavaScript中使用eval()方法解析json串

    最近在js用到了eval()方法,在这里做个笔记 当时是这么用的:data = eval("("+data+")"); data为后台向前台传送的一个json串 ...

  10. webview和H5交互

    由于H5的灵活多变,动态可配的特点,也为了避免冗长 的审核周期,H5页面在app上的重要性正日益突显. iOS应用于H5交互的控件主要是UIWebView及WKWebView WKWebView是14 ...