可以将业务(自定义)验证放在 DTO 中吗?

Is it okay to put business(custom) validate in the DTO?

@Getter
public class PolicyRequestDto {

    @Getter
    @Builder
    @AllArgsConstructor
    public static class InsertPolicyRequest {
        @NotNull(message = "need typeNo")
        private Integer typeNo;
        private String typeDescription;
        private Long volume;
        private Integer retryCnt;
        private Integer authTime;

        public InsertPolicyRequest insertValidate() {

            PolicyTypeEnum policyEnumList = new PolicyTypeEnum();
            Stream<Integer> volumeList = policyEnumList.getVolumeList().stream();
            Stream<Integer> authTimeList = policyEnumList.getAuthTimeList().stream();
            Stream<Integer> retryCntList = policyEnumList.getRetryCntList().stream();

            switch (Objects.requireNonNull(typeNo, "no typeNo")) {
                case 0 :
                    // case 0
                    break;
                case 1 :
                    // case 1
                    break;
                case 2 :
                    // case 2
                    break;
                default : throw new IllegalArgumentException("Error");
            }

            return this;
        }

        public PolicyEntity toEntity () {
            return PolicyEntity.builder()
                    .typeNo(typeNo)
                    .volume(volume)
                    .retryCnt(retryCnt)
                    .authTime(authTime)
                    .build();
        }
    }
}
public PolicyResponseDto.CommonResponse addWifiServicePolicy(@Valid PolicyRequestDto.InsertPolicyRequest insertRequest) {

        insertRequest.insertValidate();
        PolicyEntity policyEntity = insertRequest.toEntity();

        ...

        return ...
}

如果无法使用@Validated 注释完成该服务所需的验证,是否可以在 DTO 中完成?

现在,验证过程的级别取决于输入参数,例如代码。该服务只想对通过此验证的数据执行业务逻辑。验证代码放在每个方法的顶部很烦人,但我认为这是必要的代码。

在DTO中这样做有什么问题吗?

p.s我想做一个service对象独立于controller,这样即使只分离service也没有问题

您可以创建自定义验证注释。

@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MyConstraintValidator.class)
public @interface MyConstraint {

    String message() default "default error message";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

这个注解作用于字段或方法级别。验证器必须实现 ConstraintValidator.

public class MyConstraintValidator implements ConstraintValidator<MyConstraint, Integer> {

    @Override
    public boolean isValid(Integer typeNo, ConstraintValidatorContext context) {
        PolicyTypeEnum policyEnumList = new PolicyTypeEnum();
        Stream<Integer> volumeList = policyEnumList.getVolumeList().stream();
        Stream<Integer> authTimeList = policyEnumList.getAuthTimeList().stream();
        Stream<Integer> retryCntList = policyEnumList.getRetryCntList().stream();

        switch (Objects.requireNonNull(typeNo, "no typeNo")) {
            case 0 :
                // case 0
                break;
            case 1 :
                // case 1
                break;
            case 2 :
                // case 2
                break;
            default :
                return false;
        }
        return true;
    }
}

如果您需要从其他字段访问数据,则需要更新注释以适用于类型级别 - @Target( { ElementType.TYPE }),并相应地更改验证器以使用类型 - InsertPolicyRequest:

public class MyConstraintValidator implements ConstraintValidator<MyConstraint, InsertPolicyRequest> {

    @Override
    public boolean isValid(InsertPolicyRequest request, ConstraintValidatorContext context) {
        PolicyTypeEnum policyEnumList = new PolicyTypeEnum();
        Stream<Integer> volumeList = policyEnumList.getVolumeList().stream();
        Stream<Integer> authTimeList = policyEnumList.getAuthTimeList().stream();
        Stream<Integer> retryCntList = policyEnumList.getRetryCntList().stream();

        switch (Objects.requireNonNull(request.getTypeNo(), "no typeNo")) {
            case 0 :
                // case 0
                break;
            case 1 :
                // case 1
                break;
            case 2 :
                // case 2
                break;
            default :
                return false;
        }
        return true;
    }
}

也检查这个 guide

编辑: 只需一个注解即可。有一个接口将负责提取公共数据,我们称之为 PolicyRequestData.

public interface PolicyRequestData {

  Integer getTypeNo();
  String getTypeName();
}

然后将上面的注释更改为在类型级别上工作,并将其重命名为 MyCommonConstraint。更新 ConstraintValidator 以使用 PolicyRequestData 而不是具体的 class.

public class MyCommonConstraintValidator implements ConstraintValidator<MyCommonConstraint, PolicyRequestData> {

  @Override
  public boolean isValid(PolicyRequestData request, ConstraintValidatorContext constraintValidatorContext) {
    PolicyTypeEnum policyEnumList = new PolicyTypeEnum();
    Stream<Integer> volumeList = policyEnumList.getVolumeList().stream();
    Stream<Integer> authTimeList = policyEnumList.getAuthTimeList().stream();
    Stream<Integer> retryCntList = policyEnumList.getRetryCntList().stream();

    switch (Objects.requireNonNull(request.getTypeNo(), "no typeNo")) {
      case 0 :
        // case 0
        break;
      case 1 :
        // case 1
        break;
      case 2 :
        // case 2
        break;
      default :
        return false;
    }
    return true;
  }
}

像这样注释将在 any class 实现 PolicyRequestData.

上工作
@MyCommonConstraint
public class InsertPolicyRequest implements PolicyRequestData {

  private Integer typeNo;
  private String typeName;

  @Override
  public Integer getTypeNo() {
    return this.typeNo;
  }

  @Override
  public String getTypeName() {
    return this.typeName;
  }

  //setters, if needed
}

UpdatePolicyRequest 完全相同,除了 class 名称。

如果您需要使用特定数据验证 classes,请扩展接口。

public interface SpecificPolicyRequestData extends PolicyRequestData {

  String getOtherData();
}

让特定的 class 实现它并创建注释以使用此接口。然后在 class 上应用两个注释(如果需要两个验证):

@MyCommonConstraint
@MySpecificConstraint
public class SomeSpecificPolicyRequest implements SpecificPolicyRequestData {

  @Override
  public Integer getTypeNo() {
    return null;
  }

  @Override
  public String getTypeName() {
    return null;
  }

  @Override
  public String getOtherData() {
    return null;
  }
}