Spring Validation 校验
1. 为什么需要校验分组?
因为 Spring Validation 的校验默认是没有顺序的
验证器的执行顺序不固定:
● 不同验证注解的执行顺序不确定
● 同一个字段的多个验证注解顺序不确定
2. 定义校验分组
ValidationGroups.java
package com.yu.cloudpicturebackend.common;import javax.validation.GroupSequence;/*** 校验分组定义 - 更清晰的设计*/
public interface ValidationGroups {// ================== 校验级别 ==================interface NotNullCheck {} // 非空校验interface LengthCheck {} // 长度校验interface FormatCheck {} // 格式校验interface BusinessCheck {} // 业务校验// ================== 业务场景组合 ==================@GroupSequence({NotNullCheck.class, LengthCheck.class, FormatCheck.class})interface RegisterScenario {} // 注册场景校验顺序@GroupSequence({NotNullCheck.class, FormatCheck.class})interface LoginScenario {} // 登录场景校验顺序@GroupSequence({NotNullCheck.class, LengthCheck.class, FormatCheck.class})interface CreateScenario {} // 创建场景校验顺序@GroupSequence({NotNullCheck.class})interface deleteScenario {} // 删除场景校验顺序@GroupSequence({LengthCheck.class, FormatCheck.class})interface updateScenario {} // 更新场景校验顺序@GroupSequence({NotNullCheck.class})interface QueryScenario {} // 查询场景校验顺序}
3. 注册请求DTO使用注解添加校验规则
RegisterRequest.java
package com.yu.cloudpicturebackend.model.dto;import com.yu.cloudpicturebackend.common.ValidationGroups;
import lombok.Data;import javax.validation.constraints.*;@Data
public class RegisterRequest {/*** 邮箱*/@NotEmpty(message = "邮箱不能为空", groups = ValidationGroups.NotNullCheck.class)@Email(message = "邮箱格式不正确", groups = ValidationGroups.FormatCheck.class)private String email;/*** 密码*/@NotBlank(message = "密码不能为空", groups = ValidationGroups.NotNullCheck.class)@Size(min = 8, max = 15, message = "密码长度应为8-15位", groups = ValidationGroups.LengthCheck.class)@Pattern(regexp = "^(?:(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])|(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>/?])|(?=.*[a-z])(?=.*[0-9])(?=.*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>/?])|(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>/?])).+$", message = "密码需要包含大小写字母+数字+特殊符号任三种", groups = ValidationGroups.FormatCheck.class)private String password;}
4. 用户接口层应用校验
UserController.java
package com.yu.cloudpicturebackend.controller;import com.yu.cloudpicturebackend.common.ResultUtils;
import com.yu.cloudpicturebackend.common.ValidationGroups;
import com.yu.cloudpicturebackend.exception.BaseResponse;
import com.yu.cloudpicturebackend.model.dto.RegisterRequest;
import com.yu.cloudpicturebackend.model.entity.User;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.BeanUtils;
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.RestController;@RestController
@RequestMapping("/user")
@Api(tags = "用户接口")
public class UserController {@PostMapping("/login")@ApiOperation("用户注册接口")public BaseResponse<String> register(@RequestBody @Validated(ValidationGroups.RegisterScenario.class) RegisterRequest registerRequest) {User user = new User();BeanUtils.copyProperties(registerRequest, user);return ResultUtils.success("ok");}
}
5. 全局异常处理器中拦截字段异常
GlobalExceptionHandler.java
package com.yu.cloudpicturebackend.exception;import com.yu.cloudpicturebackend.common.ResultUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.View;/*** 全局异常处理器*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {private final View error;public GlobalExceptionHandler(View error) {this.error = error;}@ExceptionHandler(BindException.class)public BaseResponse<?> handleBindException(BindException e) {// 使用 BindingResult 获取字段错误BindingResult bindingResult = e.getBindingResult();String errorMessage = bindingResult.getFieldErrors().stream().findFirst() // 取第一个错误.map(DefaultMessageSourceResolvable::getDefaultMessage).orElse("参数校验失败");return ResultUtils.error(ErrorCode.PARAMS_ERROR, errorMessage);}@ExceptionHandler(BusinessException.class)public BaseResponse<?> businessExceptionHandler(BusinessException e) {log.error("BusinessException:", e);return ResultUtils.error(e.getCode(), e.getMessage());}@ExceptionHandler(RuntimeException.class)public BaseResponse<?> businessExceptionHandler(RuntimeException e) {log.error("RuntimeException:", e);return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "系统错误");}}
6. 常用的验证注解
@NotNull: 验证字段不能为null。
@NotBlank: 验证字符串不能为空,且至少包含一个非空字符。
@NotEmpty: 验证字符串、集合或数组不能为空,不同于@NotBlank,它不要求至少包含一个非空字符。
@Min(value): 验证数字必须大于等于指定的最小值。
@Max(value): 验证数字必须小于等于指定的最大值。
@Size(max, min): 验证字符串、集合或数组的大小必须在指定的范围内。
@Email: 验证字符串是否为合法的电子邮件地址。
@Pattern(regexp): 验证字符串是否符合指定的正则表达式。
@Digits(integer, fraction): 验证数字是否符合指定的位数要求,包括整数和小数部分。
@Positive: 验证数字必须为正数。
@Negative: 验证数字必须为负数。
@Past: 验证日期必须为过去的时间。
@Future: 验证日期必须为将来的时间。
@AssertTrue: 验证字段必须为true。
@AssertFalse: 验证字段必须为false。
@CreditCardNumber: 验证字符串是否为合法的信用卡号。
@URL: 验证字符串是否为合法的URL。
@Valid: 用于标记需要嵌套验证的对象。