Spring Boot 验证实体类两个中的一个不为空或者一个非空另一个必须为空

问题说明

对接银行接口的时候经常有平台订单号和银行订单号二选一即可,普通的验证注解满足不了,需要自己实现

解决方案

1. 两个中的一个不为空

创建自定义注解
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Constraint(validatedBy = AtLeastOneNotEmptyValidator.class)
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface AtLeastOneNotEmpty {
    String message() default "At least one of the fields must be not empty";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    String[] fieldNames();
}
创建验证器类
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.Field;

public class AtLeastOneNotEmptyValidator implements ConstraintValidator<AtLeastOneNotEmpty, Object> {

    private String[] fieldNames;

    @Override
    public void initialize(AtLeastOneNotEmpty constraintAnnotation) {
        this.fieldNames = constraintAnnotation.fieldNames();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        try {
            for (String fieldName : fieldNames) {
                Field field = value.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);
                Object fieldValue = field.get(value);
                if (fieldValue != null && !fieldValue.toString().trim().isEmpty()) {
                    return true;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
}
使用
/**
 *
 * 支付入参基类,新增需要继承
 * @author zsp
 * @date 2024/05/07
 */
@Data
@AtLeastOneNotEmpty(
    fieldNames = {"orderId", "bankDeductionOrderId"},
    message = "订单号或者银商订单号不能为空",
    groups = {Pay.class, Query.class, Refund.class}
)
public class PayBo {

使用@Validated注解即可触发

2. 一个非空另一个必须为空

创建自定义注解
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Constraint(validatedBy = OneNotEmptyOneEmptyValidator.class)
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface OneNotEmptyOneEmpty {
    String message() default "One field must be not empty and the other must be empty";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    String firstField();
    String secondField();
}
创建验证器类
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.Field;
import java.util.logging.Level;
import java.util.logging.Logger;

public class OneNotEmptyOneEmptyValidator implements ConstraintValidator<OneNotEmptyOneEmpty, Object> {

    private static final Logger LOGGER = Logger.getLogger(OneNotEmptyOneEmptyValidator.class.getName());

    private String firstFieldName;
    private String secondFieldName;

    @Override
    public void initialize(OneNotEmptyOneEmpty constraintAnnotation) {
        this.firstFieldName = constraintAnnotation.firstField();
        this.secondFieldName = constraintAnnotation.secondField();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        try {
            Field firstField = value.getClass().getDeclaredField(firstFieldName);
            Field secondField = value.getClass().getDeclaredField(secondFieldName);

            firstField.setAccessible(true);
            secondField.setAccessible(true);

            Object firstFieldValue = firstField.get(value);
            Object secondFieldValue = secondField.get(value);

            boolean isFirstFieldNotEmpty = firstFieldValue != null && !firstFieldValue.toString().trim().isEmpty();
            boolean isSecondFieldNotEmpty = secondFieldValue != null && !secondFieldValue.toString().trim().isEmpty();

            return (isFirstFieldNotEmpty && !isSecondFieldNotEmpty) || (!isFirstFieldNotEmpty && isSecondFieldNotEmpty);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            LOGGER.log(Level.SEVERE, e.getMessage(), e);
            return false;
        }
    }
}
使用
@OneNotEmptyOneEmpty(firstField = "field1", secondField = "field2", message = "field1 and field2 cannot both be empty or both be filled")
public class MyEntity {

    private String field1;
    private String field2;

使用@Validated注解即可触发

注意

异常捕获的时候要注意,跟原有异常信息不太一致,需要修改为如下

/**
 * 自定义验证异常
 */
@ExceptionHandler(MethodArgumentNotValidException.class)
public JgResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
    log.error(e.getMessage());
    if(e.getBindingResult().getFieldError() == null){
       //两个中的一个不为空的情况,e.getBindingResult().getFieldError()为null会报错
        return JgResponse.fail(StreamUtils.join(e.getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage, ", "));
    }else {
        return JgResponse.fail(e.getBindingResult().getFieldError().getDefaultMessage());
    }
}
### 修改后的 Spring Boot 接口实现 为了满足在 `@PostMapping("/importData")` 接口中添加编号参数,并将 `MultipartFile` 类型的 Excel 文件中的一列数据转换为 `List<String>` 集合的要求,可以按以下方式进行调整。 #### 后端接口实现 以下是完整的后端代码实现: ```java @PostMapping("/importData") public ResponseEntity<List<String>> importData( @RequestParam("file") MultipartFile file, @RequestParam("number") String number) throws Exception { // 检查文件是否为 if (file.isEmpty()) { throw new IllegalArgumentException("上传的文件不能为"); } // 使用 EasyPOI 工具读取 Excel 数据 ImportParams params = new ImportParams(); params.setTitleRows(1); // 设置标题行数 params.setHeadRows(2); // 设置头部行数 // 将 Excel 中的数据映射到实体类 List<EsaypoiTest> excelData = ExcelImportUtil.importExcel(file.getInputStream(), EsaypoiTest.class, params); // 提取指定列的数据并转化为 List<String> List<String> columnData = excelData.stream() .map(EsaypoiTest::getColumnValue) // 替换为实际的目标字段获取方法 .filter(Objects::nonNull) // 过滤掉值 .collect(Collectors.toList()); // 记录日志或打印调试信息 System.out.println("接收到的编号:" + number); System.out.println("提取的列数据:" + columnData); return ResponseEntity.ok(columnData); } ``` 在此代码中: - 新增了 `number` 参数并通过 `@RequestParam` 绑定。 - 利用了 Java Stream API 来提取目标列的数据并过滤掉可能存在的值[^1]。 - 返回的结果是一个 `List<String>` 集合,表示从 Excel 文件中提取出来的特定列数据。 --- #### 前端调用方式 针对上述后端接口的变化,前端需要按照新的要求发送请求。以下是具体的实现方式。 ##### 表单提交 如果使用传统 HTML 表单提交的方式,则需要确保表单中有两个输入项:一个是文件选择框 (`<input type="file">`),一个是编号输入框 (`<input type="text">`)。 ```html <form id="uploadForm" action="/importData" method="post" enctype="multipart/form-data"> <label for="file">选择文件:</label> <input type="file" id="file" name="file" required /> <br /><br /> <label for="number">编号:</label> <input type="text" id="number" name="number" required placeholder="请输入编号" /> <br /><br /> <button type="submit">上传</button> </form> ``` 此处需要注意设置 `enctype="multipart/form-data"` 属性以支持文件上传[^3]。 --- ##### AJAX 提交 如果是基于 JavaScript 或者框架(如 Axios/Vue.js),则可以通过 FormData 对象构建请求体。 ```javascript function uploadFileAndNumber(fileInput, numberValue) { const formData = new FormData(); formData.append('file', fileInput.files[0]); // 添加文件对象 formData.append('number', numberValue); // 添加编号参数 fetch('/importData', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { console.log('返回的列表数据:', data); }) .catch(error => console.error('错误:', error)); } // 调用示例 const fileInput = document.getElementById('file'); const numberValue = document.getElementById('number').value; if (fileInput && numberValue) { uploadFileAndNumber(fileInput, numberValue); } else { alert('请选择文件并填写编号!'); } ``` 此代码片段展示了如何利用 Fetch API 构建一个多部分表单请求,并将其发送至 `/importData` 接口[^2]。 --- ### 注意事项 1. **参数校验** 在实际开发中,应对传入的 `number` 参数进行要的验证以及其他业务逻辑约束。 2. **异常处理** 应考虑各种潜在的异常场景,例如文件格式不正确、Excel 数据缺失等情况,并提供友好的提示信息。 3. **性能优化** 如果涉及大规模数据导入操作,建议引入分页机制或者异步任务队列来提升系统的稳定性和响应速度。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值