一、枚举处理
在后端经常会有类型、状态的枚举字段,经常不知道各层怎么处理,怎么做校验,这里根据看到的开源代码(daxpay)还有平时的编码习惯,给出一个最佳实践出来
- 数据库层面:直接使用原始类型,比如string,int等,并在注释中说明每个值的具体含义
- 后端api层面
-
- 方法一、业务逻辑层校验:dto中,直接使用原始类型,不对参数类型进行校验
- 方法二、接口层执行校验:dto中,使用自定义注解+hibernate validator对传入的枚举值进行校验
- enum类:在类中定义方法,实现原始值到枚举对象的转换,若不存在抛出枚举不存在的异常
- service代码:根据原始值,转换为enum,然后用enum来进行业务逻辑判断
- 前端:前端根据接口文档,定义对应的枚举,在前端进行初步的校验
方法一、在业务逻辑层进行校验
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接口响应,需要制定一些统一的规范,这里对我个人经验进行总结归纳