枚举和统一响应最佳实践

101 阅读2分钟

一、枚举处理

在后端经常会有类型、状态的枚举字段,经常不知道各层怎么处理,怎么做校验,这里根据看到的开源代码(daxpay)还有平时的编码习惯,给出一个最佳实践出来

  1. 数据库层面:直接使用原始类型,比如string,int等,并在注释中说明每个值的具体含义
  2. 后端api层面
    1. 方法一、业务逻辑层校验:dto中,直接使用原始类型,不对参数类型进行校验
    2. 方法二、接口层执行校验:dto中,使用自定义注解+hibernate validator对传入的枚举值进行校验
  1. enum类:在类中定义方法,实现原始值到枚举对象的转换,若不存在抛出枚举不存在的异常
  2. service代码:根据原始值,转换为enum,然后用enum来进行业务逻辑判断
  3. 前端:前端根据接口文档,定义对应的枚举,在前端进行初步的校验

方法一、在业务逻辑层进行校验

public class PayOrder{
    private String refundStatus;
}
@Getter
@AllArgsConstructor
public enum PayOrderRefundStatusEnum {
    NO_REFUND("no_refund","未退款"),
    REFUNDING("refunding","退款中"),
    PARTIAL_REFUND("partial_refund","部分退款"),
    REFUNDED("refunded","全部退款"),
    ;
    private final String code;
    private final String name;

    /**
     * 根据编码获取枚举
     */
    public static PayOrderRefundStatusEnum findByCode(String code){
        return Arrays.stream(values())
                .filter(payStatusEnum -> Objects.equals(payStatusEnum.getCode(), code))
                .findFirst()
                .orElseThrow(() -> new DataNotExistException("该枚举不存在"));
    }

}
PayOrderRefundStatusEnum beforePayStatus = PayOrderRefundStatusEnum.findByCode(payOrder.getRefundStatus());

方法二、在接口层校验

  • 需要定义一个@EnumValue注解,用于对接口dto的参数进行校验
    • enumClass指定需要用哪个Enum校验
    • enumMethod指定该Enum中的校验函数
  • 所有的Enum内要自己实现对当前enum进行校验的函数,并传入@EnumValue的enumMethod参数
public class Stage {
    @EnumValue(enumClass = TimeUnitEnum.class, enumMethod = "isValid")
    private String maxExecDurationUnit;
}
/**
 * 接口枚举类检测标注类
 */
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumValue.Validator.class)
public @interface EnumValue {

    String message() default "custom.value.invalid";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    Class<? extends Enum<?>> enumClass();

    String enumMethod();

    class Validator implements ConstraintValidator<EnumValue, Object> {

        private Class<? extends Enum<?>> enumClass;
        private String enumMethod;

        @Override
        public void initialize(EnumValue enumValue) {
            enumMethod = enumValue.enumMethod();
            enumClass = enumValue.enumClass();
        }

        @Override
        public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
            if (value == null) {
                return Boolean.TRUE;
            }

            if (enumClass == null || enumMethod == null) {
                return Boolean.TRUE;
            }
            Class<?> valueClass = value.getClass();

            try {
                Method method = enumClass.getMethod(enumMethod, valueClass);
                if (!Boolean.TYPE.equals(method.getReturnType()) && !Boolean.class.equals(method.getReturnType())) {
                    throw new RuntimeException(String.format("%s method return is not boolean type in the %s class", enumMethod, enumClass));
                }

                Boolean result = (Boolean)method.invoke(null, value);
                return result == null ? false : result;
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                throw new RuntimeException(e);
            } catch (NoSuchMethodException | SecurityException e) {
                throw new RuntimeException(String.format("This %s(%s) method does not exist in the %s", enumMethod, valueClass, enumClass), e);
            }
        }

    }
}
@Getter
public enum TimeUnitEnum {

    DAY("day", "日"),
    HOUR("hour", "小时"),
    MIN("min", "分钟");

    TimeUnitEnum(String value, String name) {
        this.value = value;
        this.name = name;
    }

    private String value;
    private String name;


    /**
     * 标注类型校验 用户web端接口调用时参数校验
     */
    public static boolean isValid(String value) {
        for (TimeUnitEnum timeUnitEnum : TimeUnitEnum.values()) {
            if (timeUnitEnum.value.equals(value)) {
                return true;
            }
        }
        return false;
    }
}

二、统一响应

一个项目的团队开发中,对于api接口响应,需要制定一些统一的规范,这里对我个人经验进行总结归纳