最近项目中用到了一些枚举类,而接口API中提供的是String类型来接收,这样的话,调用方随便传什么,如果传的并不是后台定义的枚举类型,那么处理起来肯定会有问题。所以我们需要对调用方传来的枚举进行校验。
简单粗暴的方法就是,拿传入的参数跟枚举类型一个个比较,直到找到相同的才认为输入的值合法。这样的话需要写很多的if else来判断。那有没有优雅点的处理方式呢?
之前写过一篇通过注解校验参数的博文,如下:
https://blog.youkuaiyun.com/Lieforlove/article/details/90271377
参考这种方式,我们可以自定义一个注解,用来校验枚举类型。具体做法如下:
package com.test.utils;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Payload;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumValue.Validator.class)
public @interface EnumValue {
String message() default "{enum.value.invalid}"; // 错误信息
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends Enum<?>> enumClass(); // 枚举类
String enumMethod() default "isValidEnum"; // 枚举校验方法
boolean allowNull() default false; // 是否允许为空
class Validator implements ConstraintValidator<EnumValue, Object> {
private Class<? extends Enum<?>> enumClass;
private String enumMethod;
private boolean allowNull;
@Override
public void initialize(EnumValue enumValue) {
enumMethod = enumValue.enumMethod();
enumClass = enumValue.enumClass();
allowNull = enumValue.allowNull();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
if (value == null) {
return allowNull;
}
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));
}
if(!Modifier.isStatic(method.getModifiers())) {
throw new RuntimeException(String.format("%s method is not static method in the %s class", enumMethod, enumClass));
}
Boolean result = (Boolean)method.invoke(null, value);
return result == null ? Boolean.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);
}
}
}
}
在枚举类中增加一个校验枚举类型的方法:
package com.test.common;
public enum FundStatusEnum {
SUBMITTED("Submitted", "已提交"),
SETTLED("Settled", "已结算"),
PENDING_SETTLEMENT("PS", "待结算"),
PENDING_TOPUP("PT", "待充值"),
CANCELLED("Cancelled", "已取消");
private final String status;
private final String description;
FundStatusEnum(String status, String description) {
this.status = status;
this.description = description;
}
public String status() {
return status;
}
public String description() {
return description;
}
/**
* 校验是否是该枚举
*
* @param code 枚举字符串
* @return true or false
*/
public static boolean isValidEnum(String code) {
for (FundStatusEnum fundStatusEnum : FundStatusEnum.values()) {
if (fundStatusEnum.status().equals(code)) {
return true;
}
}
return false;
}
}
对外接口Request参数VO中定义如下:
package com.test.vo;
import com.test.common.FundStatusEnum;
import com.test.utils.EnumValue;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotBlank;
@ApiModel(value = "更新交易请求实体", description = "更新交易请求对象")
public class UpdateTxnReqVO {
@NotBlank(message = "The field 'gsRefID' can't be empty.")
@ApiModelProperty(value = "业务系统交易ID", name = "gsRefID", example = "TXN-REF-TEST-001")
private String gsRefID;
// 可选设置的参数 enumMethod = "isValidEnum", allowNull = false
/**因为这两个在我们自定义的枚举校验注解中用了默认值*/
@EnumValue(enumClass = FundStatusEnum.class, message = "The field 'fundStatus' is missing or invalid.")
@ApiModelProperty(value = "资金状态", name = "fundStatus", example = "Settled")
private String fundStatus;
public String getGsRefID() {
return gsRefID;
}
public void setGsRefID(String gsRefID) {
this.gsRefID = gsRefID;
}
public String getFundStatus() {
return fundStatus;
}
public void setFundStatus(String fundStatus) {
this.fundStatus = fundStatus;
}
}
Controller中方法参数中,还是通过@Valid注解 + BindingResult来获取校验信息。如下:
@PutMapping(value = "/record")
public String update(@Valid @RequestBody UpdateTxnReqVO updateTxnReqVO, BindingResult bindingResult) {
logger.debug("update txn starts------");
if (bindingResult.hasErrors()) {
// TODO 处理逻辑
}
return "OK";
}
这样,我们在Call这个update API的时候,如果传入的fundStatus不是FundStatusEnum中的任意一个,都会报错。
错误信息就是我们自定义的message,即:The field 'fundStatus' is missing or invalid.