AOP + 注解 实现通用的接口参数校验

举报
悟空码字 发表于 2023/04/09 13:09:33 2023/04/09
【摘要】 写移动端接口的时候,为了校验参数,传统的做法是加各种判断,写了很多重复的代码,而且也不美观。为了增加代码复用性,美观的校验参数,采用AOP + 注解的方式来实现接口的参数校验(使用拦截器也可以实现),在需要校验参数的方法上加上自定义的注解即可。

写移动端接口的时候,为了校验参数,传统的做法是加各种判断,写了很多重复的代码,而且也不美观。为了增加代码复用性,美观的校验参数,采用AOP + 注解的方式来实现接口的参数校验(使用拦截器也可以实现),在需要校验参数的方法上加上自定义的注解即可。

代码文件目录

image.png

代码实现

自定义异常:RRException

/**
 * @description
 */
public class RRException extends RuntimeException {
  private static final long serialVersionUID = 1L;
  
    private String msg;
    private int code = 500;
    
    public RRException(String msg) {
    super(msg);
    this.msg = msg;
  }
  
  public RRException(String msg, Throwable e) {
    super(msg, e);
    this.msg = msg;
  }
  
  public RRException(String msg, int code) {
    super(msg);
    this.msg = msg;
    this.code = code;
  }
  
  public RRException(String msg, int code, Throwable e) {
    super(msg, e);
    this.msg = msg;
    this.code = code;
  }

  public String getMsg() {
    return msg;
  }

  public void setMsg(String msg) {
    this.msg = msg;
  }

  public int getCode() {
    return code;
  }

  public void setCode(int code) {
    this.code = code;
  }
}

全局异常处理器:RRExceptionHandler

/**
 * @description
 */
@RestControllerAdvice
public class RRExceptionHandler {
  private Logger logger = LoggerFactory.getLogger(getClass());

  /**
   * 处理自定义异常
   */
  @ExceptionHandler(RRException.class)
  public R handleRRException(RRException e){
    R r = new R();
    r.put("code", e.getCode());
    r.put("msg", e.getMessage());

    return r;
  }
}

响应数据封装:R

 * @description
 */
public class R extends HashMap<String, Object> {
  private static final long serialVersionUID = 1L;
  
  public R() {
    put("code", 0);
    put("msg", "success");
  }
  
  public static R error() {
    return error(500, "未知异常,请联系管理员");
  }
  
  public static R error(String msg) {
    return error(500, msg);
  }

  public static R ok(int code, String msg) {
    R r = new R();
    r.put("code", code);
    r.put("msg", msg);
    return r;
  }

  public static R error(int code, String msg) {
    R r = new R();
    r.put("code", code);
    r.put("msg", msg);
    return r;
  }

  public static R ok(String msg) {
    R r = new R();
    r.put("msg", msg);
    return r;
  }
  
  public static R ok(Map<String, Object> map) {
    R r = new R();
    r.putAll(map);
    return r;
  }
  
  public static R ok(List<Object> list) {
    R r = new R();
    r.put("msg", list);
    return r;
  }
  
  public static R ok() {
    return new R();
  }

  @Override
  public R put(String key, Object value) {
    super.put(key, value);
    return this;
  }
}

注解:ParamCheck

/**
 * @description
 */
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RUNTIME)
public @interface ParamCheck {

    //字段校验规则,格式:字段名+校验规则+冒号+错误信息,例如:name<11:名字必须少于11位
    String[] value();
}

工具类:CheckUtil

 * @description
 */
@Component
public class CheckUtil {

    private static final Logger logger = LoggerFactory.getLogger(CheckUtil.class);

    private static final String SEPARATOR = ":";

    public String doCheck(ProceedingJoinPoint point) {
        // 获取方法参数值
        Object[] arguments =point.getArgs();
        // 获取方法
        Method method = getMethod(point);
        // 默认的错误信息
        String methodInfo = StringUtils.isEmpty(method.getName()) ? "" : "while calling " + method.getName();
        String msg = "";
        if (isCheck(method,arguments)) {
            ParamCheck annotation = method.getAnnotation(ParamCheck.class);
            String[] fields = annotation.value();
            // 只支持对第一个参数进行校验
            Object vo = arguments[0];
            if (vo == null) {
                msg = "param can not be null";
            } else {
                for (String field : fields) {
                    // 解析字段
                    FieldInfo info = resolveField(field, methodInfo);
                    // 获取字段的值
                    Object value = ReflectionUtil.invokeGetter(vo, info.getField());
                    // 执行校验规则
                    Boolean isValid = info.getOptEnum().fun.apply(value, info.getOperatorNum());
                    msg = isValid ? msg : info.getPromptMsg();
                }
            }
        }
        return msg;
    }

    /**
     * 判断是否符合参数规则
     * @param method    方法
     * @param arguments 方法参数
     * @return 是否符合
     */
    private Boolean isCheck(Method method,Object[] arguments) {
        Boolean isCheck = Boolean.TRUE;
        if (!method.isAnnotationPresent(ParamCheck.class) || arguments == null || arguments.length != 1) {
            isCheck = Boolean.FALSE;
        }
        return isCheck;
    }

    /**
     * 获取方法
     * @param point ProceedingJoinPoint
     * @return 方法
     */
    private Method getMethod(ProceedingJoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        if (method.getDeclaringClass().isInterface()) {
            try {
                method = point
                        .getTarget()
                        .getClass()
                        .getDeclaredMethod(point.getSignature().getName(),
                                method.getParameterTypes());
            } catch (SecurityException | NoSuchMethodException e) {
                logger.error("" + e);
            }
        }
        return method;
    }

    /**
     * 解析字段
     * @param fieldStr   字段字符串
     * @param methodInfo 方法信息
     * @return 字段信息实体类
     */
    private FieldInfo resolveField(String fieldStr, String methodInfo) {
        FieldInfo fieldInfo = new FieldInfo();
        String innerMsg = "";
        // 解析提示信息
        if (fieldStr.contains(SEPARATOR)) {
            innerMsg = fieldStr.split(SEPARATOR)[1];
            fieldStr = fieldStr.split(SEPARATOR)[0];
        }
        // 解析操作符
        if (fieldStr.contains(OperatorEnum.GREATER_THAN_EQUAL.value)) {
            fieldInfo.setOptEnum(OperatorEnum.GREATER_THAN_EQUAL);
        } else if (fieldStr.contains(OperatorEnum.LESS_THAN_EQUAL.value)) {
            fieldInfo.setOptEnum(OperatorEnum.LESS_THAN_EQUAL);
        } else if (fieldStr.contains(OperatorEnum.GREATER_THAN.value)) {
            fieldInfo.setOptEnum(OperatorEnum.GREATER_THAN);
        } else if (fieldStr.contains(OperatorEnum.LESS_THAN.value)) {
            fieldInfo.setOptEnum(OperatorEnum.LESS_THAN);
        } else if (fieldStr.contains(OperatorEnum.NOT_EQUAL.value)) {
            fieldInfo.setOptEnum(OperatorEnum.NOT_EQUAL);
        } else {
            fieldInfo.setOptEnum(OperatorEnum.NOT_NULL);
        }
        // 不等于空,直接赋值字段
        OperatorEnum operatorEnum = fieldInfo.getOptEnum();
        if (operatorEnum == OperatorEnum.NOT_NULL) {
            fieldInfo.setField(fieldStr);
            fieldInfo.setOperatorNum("");
        }
        // 其他操作符,需要分离出字段和操作数
        else {
            fieldInfo.setField(fieldStr.split(operatorEnum.value)[0]);
            fieldInfo.setOperatorNum(fieldStr.split(operatorEnum.value)[1]);
        }
        fieldInfo.setOperator(operatorEnum.value);
        // 处理错误信息
        String defaultMsg = fieldInfo.getField() + " must " + fieldInfo.getOperator() + " " + fieldInfo.getOperatorNum() + methodInfo;
        fieldInfo.setPromptMsg(StringUtils.isEmpty(innerMsg) ? defaultMsg : innerMsg);
        return fieldInfo;
    }
}

工具类:ReflectionUtil

 * @description
 */
public class ReflectionUtil {

    private static final String SETTER_PREFIX = "set";

    private static final String GETTER_PREFIX = "get";

    private static final String CGLIB_CLASS_SEPARATOR = "$$";

    private static Logger logger = LoggerFactory.getLogger(ReflectionUtil.class);

    /**
     * 调用Getter方法.
     */
    public static Object invokeGetter(Object obj, String propertyName) {
        String getterMethodName = GETTER_PREFIX
                + StringUtils.capitalize(propertyName);
        return invokeMethod(obj, getterMethodName, new Class[]{},
                new Object[]{});
    }
    /**
     * 直接调用对象方法, 无视private/protected修饰符.
     * 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用. 同时匹配方法名+参数类型,
     */
    public static Object invokeMethod(final Object obj,
                                      final String methodName, final Class<?>[] parameterTypes,
                                      final Object[] args) {
        Method method = getAccessibleMethod(obj, methodName, parameterTypes);
        if (method == null) {
            throw new IllegalArgumentException("Could not find method ["
                    + methodName + "] on target [" + obj + "]");
        }
        try {
            return method.invoke(obj, args);
        } catch (Exception e) {
            throw convertReflectionExceptionToUnchecked(e);
        }
    }
    /**
     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. 如向上转型到Object仍无法找到, 返回null.
     * 匹配函数名+参数类型。
     * <p>
     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object...
     * args)
     */
    public static Method getAccessibleMethod(final Object obj,
                                             final String methodName, final Class<?>... parameterTypes) {
        Validate.notNull(obj, "object can't be null");
        Validate.notBlank(methodName, "methodName can't be blank");

        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType
                .getSuperclass()) {
            try {
                Method method = searchType.getDeclaredMethod(methodName,
                        parameterTypes);
                makeAccessible(method);
                return method;
            } catch (NoSuchMethodException e) {
                // Method不在当前类定义,继续向上转型
            }
        }
        return null;
    }
    /**
     * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
     */
    public static void makeAccessible(Method method) {
        if ((!Modifier.isPublic(method.getModifiers()) || !Modifier
                .isPublic(method.getDeclaringClass().getModifiers()))
                && !method.isAccessible()) {
            method.setAccessible(true);
        }
    }
    /**
     * 将反射时的checked exception转换为unchecked exception.
     */
    public static RuntimeException convertReflectionExceptionToUnchecked(
            Exception e) {
        if ((e instanceof IllegalAccessException)
                || (e instanceof IllegalArgumentException)
                || (e instanceof NoSuchMethodException)) {
            return new IllegalArgumentException(e);
        } else if (e instanceof InvocationTargetException) {
            return new RuntimeException(
                    ((InvocationTargetException) e).getTargetException());
        } else if (e instanceof RuntimeException) {
            return (RuntimeException) e;
        }
        return new RuntimeException("Unexpected Checked Exception.", e);
    }
}

枚举:OperatorEnum

/**
 * @description 操作枚举,封装操作符和对应的校验规则
 */
public enum OperatorEnum {

    /**
     * 大于
     */
    GREATER_THAN(">", DiffTypeParamCheck::isGreaterThan),
    /**
     * 大于等于
     */
    GREATER_THAN_EQUAL(">=", DiffTypeParamCheck::isGreaterThanEqual),
    /**
     * 小于
     */
    LESS_THAN("<", DiffTypeParamCheck::isLessThan),
    /**
     * 小于等于
     */
    LESS_THAN_EQUAL("<=", DiffTypeParamCheck::isLessThanEqual),
    /**
     * 不等于
     */
    NOT_EQUAL("!=", DiffTypeParamCheck::isNotEqual),
    /**
     * 不为空
     */
    NOT_NULL("not null", DiffTypeParamCheck::isNotNull);

    public String value;
    public BiFunction<Object,String,Boolean> fun;

    OperatorEnum(String value, BiFunction<Object,String,Boolean> fun) {
        this.value = value;
        this.fun = fun;
    }
}

字段信息:FieldInfo

 * @description
 */
@Data
public class FieldInfo {

    /**
     * 字段
     */
    private String field;
    /**
     * 提示信息
     */
    private String promptMsg;
    /**
     * 操作符
     */
    private String operator;
    /**
     * 操作数
     */
    private String operatorNum;
    /**
     * 操作枚举
     */
    private OperatorEnum optEnum;
}

对不同类型的值进行校验:DiffTypeParamCheck

 * @description 对不同类型的值进行校验
 */
public class DiffTypeParamCheck {

    /**
     * 是否不为空
     * @param value       字段值
     * @param operatorNum 操作数,这里不需要,只是为了参数统一
     * @return 是否不为空
     */
    public static Boolean isNotNull(Object value, String operatorNum) {
        Boolean isNotNull = Boolean.TRUE;
        Boolean isStringNull = (value instanceof String) && StringUtils.isEmpty((String) value);
        Boolean isCollectionNull = (value instanceof Collection) && CollectionUtils.isEmpty((Collection) value);
        if (value == null) {
            isNotNull = Boolean.FALSE;
        } else if (isStringNull || isCollectionNull) {
            isNotNull = Boolean.FALSE;
        }
        return isNotNull;
    }

    /**
     * 是否大于
     * @param value       字段值
     * @param operatorNum 操作数
     * @return 是否大于
     */
    public static Boolean isGreaterThan(Object value, String operatorNum) {
        Boolean isGreaterThan = Boolean.FALSE;
        if (value == null) {
            return Boolean.FALSE;
        }
        Boolean isStringGreaterThen = (value instanceof String) && ((String) value).length() > Integer.valueOf(operatorNum);
        Boolean isLongGreaterThen = (value instanceof Long) && ((Long) value) > Long.valueOf(operatorNum);
        Boolean isIntegerGreaterThen = (value instanceof Integer) && ((Integer) value) > Integer.valueOf(operatorNum);
        Boolean isShortGreaterThen = (value instanceof Short) && ((Short) value) > Short.valueOf(operatorNum);
        Boolean isFloatGreaterThen = (value instanceof Float) && ((Float) value) > Float.valueOf(operatorNum);
        Boolean isDoubleGreaterThen = (value instanceof Double) && ((Double) value) > Double.valueOf(operatorNum);
        Boolean isCollectionGreaterThen = (value instanceof Collection) && ((Collection) value).size() > Integer.valueOf(operatorNum);
        if (isStringGreaterThen || isLongGreaterThen || isIntegerGreaterThen ||
                isShortGreaterThen || isFloatGreaterThen || isDoubleGreaterThen || isCollectionGreaterThen) {
            isGreaterThan = Boolean.TRUE;
        }
        return isGreaterThan;
    }

    /**
     * 是否大于等于
     * @param value       字段值
     * @param operatorNum 操作数
     * @return 是否大于等于
     */
    public static Boolean isGreaterThanEqual(Object value, String operatorNum) {
        Boolean isGreaterThanEqual = Boolean.FALSE;
        if (value == null) {
            return Boolean.FALSE;
        }
        Boolean isStringGreaterThenEqual = (value instanceof String) && ((String) value).length() >= Integer.valueOf(operatorNum);
        Boolean isLongGreaterThenEqual = (value instanceof Long) && ((Long) value) >= Long.valueOf(operatorNum);
        Boolean isIntegerGreaterThenEqual = (value instanceof Integer) && ((Integer) value) >= Integer.valueOf(operatorNum);
        Boolean isShortGreaterThenEqual = (value instanceof Short) && ((Short) value) >= Short.valueOf(operatorNum);
        Boolean isFloatGreaterThenEqual = (value instanceof Float) && ((Float) value) >= Float.valueOf(operatorNum);
        Boolean isDoubleGreaterThenEqual = (value instanceof Double) && ((Double) value) >= Double.valueOf(operatorNum);
        Boolean isCollectionGreaterThenEqual = (value instanceof Collection) && ((Collection) value).size() >= Integer.valueOf(operatorNum);
        if (isStringGreaterThenEqual || isLongGreaterThenEqual || isIntegerGreaterThenEqual ||
                isShortGreaterThenEqual || isFloatGreaterThenEqual || isDoubleGreaterThenEqual || isCollectionGreaterThenEqual) {
            isGreaterThanEqual = Boolean.TRUE;
        }
        return isGreaterThanEqual;
    }

    /**
     * 是否少于
     * @param value       字段值
     * @param operatorNum 操作数
     * @return 是否少于
     */
    public static Boolean isLessThan(Object value, String operatorNum) {
        Boolean isLessThan = Boolean.FALSE;
        if (value == null) {
            return Boolean.FALSE;
        }
        Boolean isStringLessThen = (value instanceof String) && ((String) value).length() < Integer.valueOf(operatorNum);
        Boolean isLongLessThen = (value instanceof Long) && ((Long) value) < Long.valueOf(operatorNum);
        Boolean isIntegerLessThen = (value instanceof Integer) && ((Integer) value) < Integer.valueOf(operatorNum);
        Boolean isShortLessThen = (value instanceof Short) && ((Short) value) < Short.valueOf(operatorNum);
        Boolean isFloatLessThen = (value instanceof Float) && ((Float) value) < Float.valueOf(operatorNum);
        Boolean isDoubleLessThen = (value instanceof Double) && ((Double) value) < Double.valueOf(operatorNum);
        Boolean isCollectionLessThen = (value instanceof Collection) && ((Collection) value).size() < Integer.valueOf(operatorNum);
        if (isStringLessThen || isLongLessThen || isIntegerLessThen ||
                isShortLessThen || isFloatLessThen || isDoubleLessThen || isCollectionLessThen) {
            isLessThan = Boolean.TRUE;
        }
        return isLessThan;
    }

    /**
     * 是否少于等于
     * @param value       字段值
     * @param operatorNum 操作数
     * @return 是否少于等于
     */
    public static Boolean isLessThanEqual(Object value, String operatorNum) {
        Boolean isLessThanEqual = Boolean.FALSE;
        if (value == null) {
            return Boolean.FALSE;
        }
        Boolean isStringLessThenEqual = (value instanceof String) && ((String) value).length() <= Integer.valueOf(operatorNum);
        Boolean isLongLessThenEqual = (value instanceof Long) && ((Long) value) <= Long.valueOf(operatorNum);
        Boolean isIntegerLessThenEqual = (value instanceof Integer) && ((Integer) value) <= Integer.valueOf(operatorNum);
        Boolean isShortLessThenEqual = (value instanceof Short) && ((Short) value) <= Short.valueOf(operatorNum);
        Boolean isFloatLessThenEqual = (value instanceof Float) && ((Float) value) <= Float.valueOf(operatorNum);
        Boolean isDoubleLessThenEqual = (value instanceof Double) && ((Double) value) <= Double.valueOf(operatorNum);
        Boolean isCollectionLessThenEqual = (value instanceof Collection) && ((Collection) value).size() <= Integer.valueOf(operatorNum);
        if (isStringLessThenEqual || isLongLessThenEqual || isIntegerLessThenEqual ||
                isShortLessThenEqual || isFloatLessThenEqual || isDoubleLessThenEqual || isCollectionLessThenEqual) {
            isLessThanEqual = Boolean.TRUE;
        }
        return isLessThanEqual;
    }

    /**
     * 是否不等于
     * @param value       字段值
     * @param operatorNum 操作数
     * @return 是否不等于
     */
    public static Boolean isNotEqual(Object value, String operatorNum) {
        Boolean isNotEqual = Boolean.FALSE;
        if (value == null) {
            return Boolean.FALSE;
        }
        Boolean isStringNotEqual = (value instanceof String) && !value.equals(operatorNum);
        Boolean isLongNotEqual = (value instanceof Long) && !value.equals(Long.valueOf(operatorNum));
        Boolean isIntegerNotEqual = (value instanceof Integer) && !value.equals(Integer.valueOf(operatorNum));
        Boolean isShortNotEqual = (value instanceof Short) && !value.equals(Short.valueOf(operatorNum));
        Boolean isFloatNotEqual = (value instanceof Float) && !value.equals(Float.valueOf(operatorNum));
        Boolean isDoubleNotEqual = (value instanceof Double) && !value.equals(Double.valueOf(operatorNum));
        Boolean isCollectionNotEqual = (value instanceof Collection) && ((Collection) value).size() != Integer.valueOf(operatorNum);
        if (isStringNotEqual || isLongNotEqual || isIntegerNotEqual ||
                isShortNotEqual || isFloatNotEqual || isDoubleNotEqual || isCollectionNotEqual) {
            isNotEqual = Boolean.TRUE;
        }
        return isNotEqual;
    }

切面类:ParamCheckAspect

 * @description
 */
@Aspect
@Component
public class ParamCheckAspect {

    private static final Logger logger = LoggerFactory.getLogger(ParamCheckAspect.class);

    @Autowired
    private CheckUtil checkUtil;

    @Around(value = "@annotation(com.smartMap.media.common.paramcheck.annotation.ParamCheck)")
    public Object check(ProceedingJoinPoint point) throws Throwable {
        Object obj;
        // 参数校验
        String msg = checkUtil.doCheck(point);
        if (!StringUtils.isEmpty(msg)) {
            throw new RRException(msg, 400);
        }
        // 通过校验,继续执行原有方法
        obj = point.proceed();
        return obj;
    }
}

测试验证

参数实体类:SelectorObj

 * @description
 */
@Data
public class SelectorObj {
    private String value;
    private String label;
}

控制器:TestController

/**
 * @description
 */
@RestController
@RequestMapping("/mobile/test")
public class TestController {
    @ParamCheck({"value:value 不能为空","label!=123:label 不能为123"})
    @RequestMapping("testParamCheck")
    public R testParamCheck(SelectorObj obj) {
        System.out.println(obj);
        return R.ok().put("obj",obj);
    }
}

结果:

1、非空检验

image.png

image.png

2、非特定值校验

image.png

image.png

您的一键三连,是我更新的最大动力,谢谢

山水有相逢,来日皆可期,谢谢阅读,我们再会

我手中的金箍棒,上能通天,下能探海

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。