当前位置: 首页 > news >正文

使用自定义注解校验请求参数

1、@Valid启用校验器

@PostMapping @Operation(summary = "新增") public ResponseEntity<ProductionProcessResponse> add(@Valid @RequestBody ProductionProcessRequest productionProcessRequest) { ProductionProcess save = productionProcessService.add(productionProcessRequest); return ResponseEntity.ok(ProductionProcessFactory.toResponse(save)); }

当请求体里嵌套了对象,需要在对象属性上也加上@Valid,开启嵌套对象里的校验器

public class LogTemplateRequest implements Serializable { @Valid // 让集合对象里的校验注解生效 @UniqueField(field = "fieldCode", message = "模板字段 的 字段编码 不能重复") private List<LogTemplateFieldEvo> templateFields;

2、自定义属性校验器

2.1、创建自定义校验器

@Documented @Constraint(validatedBy = {}) @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @NotBlank(message = "不能为空") @Pattern(regexp = "^[a-zA-Z0-9_]*$", message = "只能包含字母、数字和下划线") public @interface AlphanumericUnderscore { String message() default "只能包含字母、数字和下划线"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }

2.2、使用自定义校验器

@AlphanumericUnderscore private String processCode;

3、自定义类校验器

当需要多个属性值在一起才能完成的校验,需要使用类校验器,这样校验器中可以获取整个请求对象,可以通过反射获取类中的多个字段的值,来完成校验任务,比如code值唯一性校验,当修改对象时,需要排除当前编辑对象的id,这里就要用到 id 和 code 两个字段的值。

3.1、创建自定义校验器

import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; /** * 校验字段在数据库中是否唯一 */ @Documented @Constraint(validatedBy = {ClassUniqueValidator.class}) // 指定自定义校验器 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Unique { String message() default "该值已存在"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; /** * 实体类类型 */ Class<?> entityClass(); /** * 唯一性约束字段,支持多个字段组合 */ String[] fields(); /** * 忽略的ID(用于更新操作) */ String ignoreIdField() default "id"; }
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import javax.persistence.EntityManager; import javax.persistence.criteria.*; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; @Component public class ClassUniqueValidator implements ConstraintValidator<Unique, Object> { private static final Logger log = LoggerFactory.getLogger(ClassUniqueValidator.class); @Autowired private EntityManager entityManager; private Class<?> entityClass; private String[] fields; private String ignoreIdField; private String message; @Override public void initialize(Unique constraintAnnotation) { this.entityClass = constraintAnnotation.entityClass(); this.fields = constraintAnnotation.fields(); this.ignoreIdField = constraintAnnotation.ignoreIdField(); this.message = constraintAnnotation.message(); } @Override public boolean isValid(Object requestObj, ConstraintValidatorContext context) { if (requestObj == null) { return true; } try { // 1. 获取要忽略的ID(用于更新操作) Object ignoreIdValue = getIgnoreIdValue(requestObj); // 2. 获取字段值 Map<String, Object> fieldValues = getFieldValues(requestObj); // 3. 检查是否有空值 if (hasNullValue(fieldValues)) { return true; // 如果组合字段中有空值,不进行唯一性校验 } // 4. 查询数据库 boolean exists = checkExistsInDatabase(fieldValues, ignoreIdValue); if (exists) { // 构建错误消息 buildErrorMessages(context, fieldValues); // 打印默认消息模板 // String defaultTemplate = context.getDefaultConstraintMessageTemplate(); // log.info("默认消息模板: {}", defaultTemplate); return false; } return true; } catch (Exception e) { // 发生异常时返回true,避免影响主流程 return true; } } /** * 获取要忽略的ID值 */ private Object getIgnoreIdValue(Object requestObj) throws Exception { if (StringUtils.hasText(ignoreIdField)) { Field idField = getField(requestObj.getClass(), ignoreIdField); if (idField != null) { idField.setAccessible(true); Object idValue = idField.get(requestObj); return idValue != null ? idValue : null; } } return null; } /** * 获取所有字段的值 */ private Map<String, Object> getFieldValues(Object requestObj) throws Exception { Map<String, Object> values = new HashMap<>(); for (String fieldName : fields) { String[] parts = fieldName.split(":"); String requestField = parts[0]; String entityField = parts.length > 1 ? parts[1] : parts[0]; Field field = getField(requestObj.getClass(), requestField); if (field != null) { field.setAccessible(true); Object value = field.get(requestObj); values.put(entityField, value); } } return values; } /** * 递归获取字段(包括父类) */ private Field getField(Class<?> clazz, String fieldName) { try { return clazz.getDeclaredField(fieldName); } catch (NoSuchFieldException e) { if (clazz.getSuperclass() != null) { return getField(clazz.getSuperclass(), fieldName); } return null; } } /** * 检查是否有空值 */ private boolean hasNullValue(Map<String, Object> fieldValues) { for (Object value : fieldValues.values()) { if (value == null || (value instanceof String && !StringUtils.hasText((String) value))) { return true; } } return false; } /** * 检查数据库中是否存在记录 */ private boolean checkExistsInDatabase(Map<String, Object> fieldValues, Object ignoreIdValue) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Long> query = cb.createQuery(Long.class); // Long查询结果的类型 Root<?> root = query.from(entityClass); // 构建查询条件 Predicate predicate = buildPredicate(cb, root, fieldValues); // 添加ID排除条件(用于更新操作) if (ignoreIdValue != null && StringUtils.hasText(ignoreIdValue.toString())) { Predicate idPredicate = cb.notEqual(root.get("id"), ignoreIdValue); predicate = cb.and(predicate, idPredicate); } query.select(cb.count(root)).where(predicate); Long count = entityManager.createQuery(query).getSingleResult(); return count > 0; } /** * 构建查询条件 */ private Predicate buildPredicate(CriteriaBuilder cb, Root<?> root, Map<String, Object> fieldValues) { Predicate predicate = cb.conjunction(); for (Map.Entry<String, Object> entry : fieldValues.entrySet()) { if (entry.getValue() != null) { Predicate fieldPredicate = cb.equal(root.get(entry.getKey()), entry.getValue()); predicate = cb.and(predicate, fieldPredicate); } } return predicate; } /** * 构建错误消息 */ private void buildErrorMessages(ConstraintValidatorContext context, Map<String, Object> fieldValues) { // 禁用默认消息 context.disableDefaultConstraintViolation(); // 为每个字段添加错误消息 for (String fieldName : fieldValues.keySet()) { context.buildConstraintViolationWithTemplate(message).addPropertyNode(fieldName).addConstraintViolation(); } } }

3.2、使用自定义校验器

@Unique( entityClass = FinishedProduct.class, fields = { "productCode" }, message = "产成品编码 已存在" ) public class FinishedProductRequest implements Serializable {

请求体对象的字段名和entity对象的字段名不一致时

@Unique( entityClass = XxxEntity.class, fields = { "productCode", // request.productCode -> entity.productCode "productType:type", // request.productType -> entity.type "factoryId:factory.id" // 支持嵌套属性 }, ignoreIdField = "myRequestId", // 一般情况下都是 id,保持默认值即可 message = "相同编码、类型和工厂的产品已存在" )

4、自定义集合属性校验器

4.1、创建自定义集合属性校验器

import javax.validation.*; import java.lang.annotation.*; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = UniqueFieldValidator.class) public @interface UniqueField { String message() default "集合中存在重复的字段值"; String field() default ""; // 要校验的属性名 Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public class UniqueFieldValidator implements ConstraintValidator<UniqueField, List<?>> { private String fieldName; private String message; @Override public void initialize(UniqueField constraintAnnotation) { this.fieldName = constraintAnnotation.field(); this.message = constraintAnnotation.message(); } @Override public boolean isValid(List<?> list, ConstraintValidatorContext context) { if (list == null || list.isEmpty()) { return true; } try { Set<Object> values = new HashSet<>(); for (Object obj : list) { // 通过反射获取属性值 // Method getter = obj.getClass().getMethod("get" + // fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1)); // Object value = getter.invoke(obj); // if (value != null && !values.add(value)) { // return false; // 有重复 // } // 使用反射工具类(更安全) // BeanWrapper wrapper = new BeanWrapperImpl(obj); // Object value = wrapper.getPropertyValue(fieldName); // if (value != null && !values.add(value)) { // return false; // 有重复 // } // 支持父类属性 Field field = getField(obj.getClass(), fieldName); if (field != null) { field.setAccessible(true); Object value = field.get(obj); if (value != null && !values.add(value)) { buildErrorMessages(context, value); return false; // 有重复 } } else { return true; } } return true; } catch (Exception e) { throw new RuntimeException("校验字段" + fieldName + "时出错", e); } } /** * 构建错误消息 */ private void buildErrorMessages(ConstraintValidatorContext context, Object value) { // 禁用默认消息 context.disableDefaultConstraintViolation(); // 添加错误消息 context.buildConstraintViolationWithTemplate(message + ":" + value).addConstraintViolation(); } /** * 递归获取字段(包括父类) */ private Field getField(Class<?> clazz, String fieldName) { try { return clazz.getDeclaredField(fieldName); } catch (NoSuchFieldException e) { if (clazz.getSuperclass() != null) { return getField(clazz.getSuperclass(), fieldName); } return null; } } }

4.2、使用自定义集合属性校验器

@Valid // 让集合对象里的校验注解生效 @UniqueField(field = "fieldCode", message = "模板字段 的 字段编码 不能重复") private List<LogTemplateFieldEvo> templateFields;
http://www.jsqmd.com/news/115883/

相关文章:

  • 12月20日总结 - 作业----
  • python django flask嗨玩-旅游线路社区交流商城网站_mvyi06ne--论文
  • 熬夜刷手机不愿睡觉,这是一种心理问题吗?
  • 性价比高的循环水处理专业的源头厂家
  • 第10章 泛型算法
  • enum class
  • C020基于博途西门子1200PLC鸡饲料生产线控制系统仿真
  • 共享资源和实例数据-–-behaviac
  • 专业的康有利到家理疗小程序哪家好
  • 云计算IP大纲
  • 第9章 顺序容器
  • 回眸的狼耳圣女与荧光百合
  • 基于SpringBoot+Vue的乡镇农村建设用地管理系统的设计与实现
  • 空操作节点-–-behaviac
  • Git 与 SVN 区别 - 详解
  • 第四章 作业
  • 亲测十大灵活用工平台复盘
  • 等待信号节点-–-behaviac
  • P3951 [NOIP 2017 提高组] 小凯的疑惑 - Harvey
  • 第7章 类
  • 目录---behaviac
  • python django flask基于Web的医院挂号预约管理系统的设计与实现_tx5w3g1r
  • 完整教程:FFmepg--25-h265解码yuv格式
  • 提示工程架构师必备,实用工具箱大放送
  • 2025年大模型使用全景图:6大趋势助你抢占AI先机
  • 20251220
  • 在duckdb 递归CTE中实现深度优先搜索DFS
  • 线索二叉树
  • 实用指南:【javaEE】多线程进阶--CAS与原子类
  • 第3章 字符串向量数组