HibernateValidatorhttps://hibernate.org/validator/
引入依赖项
首先,确保已将Hibernate Validator添加到Maven或Gradle依赖项中:
<!-- Maven 依赖 -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.0.Final</version>
</dependency>
在Spring配置中,添加以下代码以启用Hibernate Validator:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
@Configuration
public class Config {
@Bean
public Validator validator(AutowireCapableBeanFactory autowireCapableBeanFactory) {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// autowireCapableBeanFactory 是为了方便自定义验证器时候使用Spring Bean
.constraintValidatorFactory(new SpringConstraintValidatorFactory(autowireCapableBeanFactory))
// 快速失败模式
// .failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
}
添加异常解析器
import lombok.Data;
import com.alibaba.fastjson.JSON;
import org.springframework.web.bind.annotation.*;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
@ControllerAdvice
public class ControllerAdvice {
/**
* RequestParam 注解的校验异常
* @param e
* @return
*/
@ResponseBody
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(MissingServletRequestParameterException.class)
public Response processRequestParameterException(MissingServletRequestParameterException e) {
return Response.illegalParameter(e.getMessage(), e.getMessage());
}
/**
* Validated 注解的校验异常
*/
@ResponseBody
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Response<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
return wrapErrors(e.getBindingResult().getAllErrors());
}
/**
* 提取 validate 异常信息
*
* @param errors
* @return
*/
private Response<?> wrapErrors(List<ObjectError> errors) {
if (CollectionUtils.isEmpty(errors)) {
return SimpleResponse.illegalParameter("请求参数错误");
}
List<String> errorMsgList = errors.stream().map(
objectError -> objectError.getDefaultMessage())
.collect(Collectors.toList());
String msg = JSON.toJSONString(errorMsgList).replaceAll("\"", "");
return SimpleResponse.illegalParameter(msg, msg);
}
}
@Data
class Response{
private String respCode;
private String msg;
private Data data;
public SimpleResponse(){}
/**
* 数据校验失败
*
* @param msg 校验失败的原因
* @param <T> 泛型
* @return 泛型
*/
public static <T> Response<T> illegalParameter(String msg) {
SimpleResponse<T> response = new SimpleResponse<>();
response.setRespCode(VERIFY_FAIL.getCode());
response.setMsg(msg);
return response;
}
/**
* 数据校验失败
*
* @param msg 校验失败的原因
* @param <T> 泛型
* @return 泛型
*/
public static <T> Response<T> illegalParameter(String msg,T data) {
SimpleResponse<T> response = new SimpleResponse<>();
response.setRespCode(VERIFY_FAIL.getCode());
response.setMsg(msg);
response.setData(data);
return response;
}
}
使用方法
在Controller 层的使用
注解方式的使用 @Validated 支持分组验证 @RequestParam 自带校验
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
@RestController
@RequestMapping("/export")
public class ExportController {
@Resource
LogService logService;
@PostMapping(name = "添加", value = "/insert.json")
public Response<?> insert(@RequestBody @Validated({LogService.insert.class})
LogDTO dto) {
logService.insert(dto);
return Response.ofSuccess();
}
@GetMapping(name = "重新生成", value = "/reGenerate.json")
public Response<?> reGenerate(@RequestParam(value = "id") Long id) {
return logService.reGenerate(id);
}
@GetMapping(name = "删除", value = "/logicDelete.json")
public Response<?> logicDelete(@RequestParam(value = "id") Long id) {
return logService.logicDelete(id, getUsername());
}
@PostMapping(name = "导出文件", value = "/downloadFile.json")
public void downloadFile(HttpServletResponse response, @RequestBody @Validated({LogService.downloadFile.class}) LogDTO dto) {
omcCloseOrderExportLogService.downloadFile(response, dto);
}
}
Bean注解的应用
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import java.util.Date;
@Data
@AllArgsConstructor
public class LogDTO {
/**
* 主键
*/
@Null(message = "无需id主键", groups = {LogService.insert.class})
@NotNull(message = "主键id不得为空", groups = {LogService.reGenerate.class,
LogService.logicDelete.class,
LogService.downloadFile.class})
private Long id;
private String traceId;
/**
* 用户身份证号
*/
@NotNull(message = "用户身份证号不得为空", groups = {OmcCloseOrderExportLogService.insert.class})
private String idCard;
/**
* 操作人
*/
@NotNull(message = "操作人不得为空", groups = {LogService.insert.class})
private String operator;
/**
* 下载类型
*/
@NotNull(message = "下载类型不得为空", groups = {LogService.downloadFile.class})
private String downloadType;
public LogDTO() {
}
}
接口定义
public interface LogService {
/**
* 验证组-添加
*/
@interface insert{}
/**
* 验证组-重新生成
*/
@interface reGenerate{}
/**
* 验证组-逻辑删除
*/
@interface logicDelete{}
/**
* 验证组-下载文件
*/
@interface downloadFile{}
/**
* 添加记录
*/
void insert(LogDTO logDTO);
/**
* 更新 记录的状态、数据
*/
Response<?> reGenerate(Long id);
/**
* 删除数据
*/
Response<?> logicDelete(Long id, String operator);
/**
* 下载文件
*/
void downloadFile(HttpServletResponse response, LogDTO logDTO);
在非Controller层的使用
本质上是代理
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.AssertTrue;
@Service
@Validated
public class OServiceImpl implements OService {
@Resource
OMapper oMapper;
/**
* 获取当前类的代理对象
*
*/
private OServiceImpl getProxy() {
return (OServiceImpl) AopContext.currentProxy();
}
// 存在自己调用自己的情况(isExistName、isExistFullName 都被增强了)所以需要通过代理上下文调用
@Override
public void validSave(OrganizationDTO organizationDTO) {
OServiceImpl oService = getProxy();
oService.isExistName(organizationDTO.getOrganizationName());
oService.isExistFullName(organizationDTO.getFullName());
oService.isNotExistUserPhone(organizationDTO.getPhone());
}
@AssertTrue(message = "重复的名")
public Boolean isExistName(String name) {
return organizationMapper.nameIsExist(name) == null;
}
@AssertTrue(message = "重复的全称")
public Boolean isExistFullName(String fullName) {
return oMapper.fullNameIsExist(fullName) == null;
}
@AssertTrue(message = "重复的手机号")
public Boolean isNotExistPhone(String phone) {
return oMapper.phoneIsExist(phone) == null;
}
}
class OMapper{
/**
* 检查名字是否已经存在
*/
@Select("SELECT 1 FROM db WHERE f_name = #{name} LIMIT 1")
Integer nameIsExist(String name);
/**
* 检查手机号是否已经存在
*/
@Select("SELECT 1 FROM db WHERE f_phone = #{phone} LIMIT 1")
Integer phoneIsExist(@Param("phone") String phone);
}
public Response<?> save(ODTO odto){
oService.validSave(odto);
oService.save(odto);
return Response.ofSuccess();
}
实体中带有isXX方式的自动验证
@Controller 添加验证注解时候,ODTO 的isBeforeDate 会被自动执行
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotEmpty;
import javax.validation.constraints.Future;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.AssertTrue;
import java.io.Serializable;
import java.util.Date;
@Data
public class ODTO {
/**
* 业务ID
*/
@NotNull(groups = OService.updateById.class, message = " 业务ID不得为空")
private String oNo;
/**
* 全称
*/
@NotEmpty(message = "全称不得为空")
@Length(min = 3, max = 100, message = "全称字符长度应为{min}-{max}")
private String fullName;
/**
* 编码
*/
@Null(message = "不需要编码")
private String code;
/**
* 负责人
*/
@NotEmpty
@Length(min = 2, max = 50, message = "联系人,字符长度应为{min}-{max}")
private String person;
/**
* 手机号
*/
@NotEmpty
@Pattern(regexp = "(13\\d|14[579]|15[^4\\D]|17[^49\\D]|18\\d|19\\d)\\d{8}",
message = "请检查手机号格式")
private String phone;
/**
* 模式, 自定义注解CheckEnum 用以检查固定的值枚举值
*/
@NotNull
@CheckEnum(DicEnum.chargingMode)
private Integer chargingMode;
/**
* 开始时间
*/
@NotNull
private Date startDate;
/**
* 结束时间
*/
@NotNull
@Future(message = "结束时间应大于当前日期")
private Date endDate;
public ODTO() {
}
/**
* 标注了validation的非Get/Set 方法应以is开头
* @return
*/
@AssertTrue(message = "开始时间 应小于 结束时间")
public boolean isBeforeDate(){
return this.startDate.before(this.endDate);
}
}
自定义验证器
/**
* 定义注解
*/
@Documented
@Retention(RUNTIME)
// 定义 接口约束验证器
@Constraint(validatedBy = {CheckEnumValidatorForString.class, CheckEnumValidatorForNumber.class})
@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE})
public @interface CheckEnum {
/**
* 多个值分割的形式
*/
String MULTIPLE_VALUE_SEPARATOR = ",";
String message() default "枚举检查失败";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
DicEnum value();
}
// 定义枚举
public enum DicEnum {
/**
* 类型
*/
oType
}
import javax.annotation.Resource;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.List;
@Component
public class CheckEnumValidatorForString implements ConstraintValidator<CheckEnum, String> {
/**
* 多个值分割的形式
*/
String MULTIPLE_VALUE_SEPARATOR = ",";
private List<String> codeListCache;
CheckEnum constraintAnnotation;
@Resource
DictionaryCache dictionaryCache;
@Override
public void initialize(CheckEnum constraintAnnotation) {
// 先执行的方法
this.constraintAnnotation = constraintAnnotation;
this.codeListCache = dictionaryCache.logicExpirationDictCodeListCache(constraintAnnotation.value());
}
/**
* true 通过校验
*
* @param value
* @param context
* @return
*/
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
this.codeListCache = dictionaryCache.logicExpirationDictCodeListCache(constraintAnnotation.value());
List<String> cachedDictCodeList = this.codeListCache;
if (cachedDictCodeList.size() == 0) {
return false;
}
if (value.contains(MULTIPLE_VALUE_SEPARATOR)) {
String[] str = value.split(MULTIPLE_VALUE_SEPARATOR);
return cachedDictCodeList.containsAll(Arrays.asList(str));
}
return cachedDictCodeList.contains(value);
}
}
更多实现
休眠验证程序 8.0.0.Final - Jakarta Bean 验证参考实现:参考指南 (jboss.org)