SpringBoot-表单验证-统一异常处理-自定义验证信息源

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格式

__EOF__

本文作者张铁牛
本文链接https://www.cnblogs.com/ludangxin/p/15113954.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   张铁牛  阅读(1067)  评论(2编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?
点击右上角即可分享
微信分享提示