SpringBoot实现参数校验拦截(采用AOP方式)

一、AOP是什么?

目的:分离横切关注点(如日志记录、事务管理)与核心业务逻辑。

优势:提高代码的可读性和可维护性。

关键概念

  • 切面(Aspect):包含横切关注点代码的模块。
  • 通知(Advice):切面中的具体动作,比如方法调用之前或之后执行的代码。
  • 连接点(Join Point):程序执行的某个具体点,比如方法调用。
  • 切入点(Pointcut):定义在哪些连接点应用通知。

二、使用步骤

1.引入库

代码如下(示例):

<dependencies>
    <!-- 引入SpringBoot Aop依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

    <!-- 引入Aspectj依赖 -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.7</version>
    </dependency>
</dependencies>

2.定义注解

定义注解GlobalInterceptor

代码示例:如下

@Target({ElementType.METHOD})//注解的目标类型是方法
@Retention(RetentionPolicy.RUNTIME)//注解在运行的时候生效
@Documented
@Mapping
public @interface GlobalInterceptor {
    /**
     * 校验参数
     * @return
     */
    boolean checkParams() default false;
}

 定义注解用来校验具体参数

@Retention(RetentionPolicy.RUNTIME)//运行时校验
@Target({ElementType.PARAMETER,ElementType.FIELD})// 指定该注解可以应用的目标类型为参数和字段
public @interface VerifyParam {

    int min() default -1;//校验最小长度

    int max() default -1;//检验最大长度

    boolean required() default false; //校验是否必传

    VerifyRegexEnum regex() default VerifyRegexEnum.NO;//校验正则,默认状态是不校验的

}

 可以看到上方的VerifyRegexEnum,这里是一个枚举,主要是来校验参数的,那么枚举代码示例如下:

public enum VerifyRegexEnum {
    NO("","不校验"),
    EMAII("^[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+$","邮箱"),
    PASSWORD("^(?=.*\\d)(?=.*[a-zA-Z])[\\da-zA-Z~!@#$号^&* ]{8,}$","只能是数字,字母,特殊字符 8-18位");

    private String regex;
    private String desc;

    VerifyRegexEnum(String regex, String desc) {
        this.regex = regex;
        this.desc = desc;
    }

    public String getRegex() {
        return regex;
    }

    public String getDesc() {
        return desc;
    }
}

 由于这里我的项目中只是简单的校验了一下邮箱和密码,需要的话,大家可以自行加入校验方式

 3.定义切面类

@Aspect//表明这是一个切面类
@Component("globalOperatcionAspect")// 交给Spring管理
public class GlobalOperatcionAspect {

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

    private static final String[] TYPE_BASE = {"java.lang.String","java.lang.Integer","java.lang.Long"};

    //@Pointcut 定义切入点表达式,用于匹配目标方法,此处匹配带有@GlobalInterceptor注解的方法
    @Pointcut("@annotation(com.easypan.annotation.GlobalInterceptor)")
    private void requestInterceptor(){
        // 方法体为空,只是作为一个切入点标识
    }
    //@Before 在目标方法执行前执行
    @Before("requestInterceptor()")
    public void interceptorDo(JoinPoint point) throws BusinessException {
        try {
            Object target = point.getTarget();// 获取目标对象
            Object[] arguments = point.getArgs(); // 获取方法参数
            String methodName = point.getSignature().getName(); // 获取方法名
            Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes(); // 获取方法参数类型
            Method method = target.getClass().getMethod(methodName, parameterTypes); // 获取目标方法
            GlobalInterceptor interceptor = method.getAnnotation(GlobalInterceptor.class); // 获取方法上的全局拦截器注解
            if (null == interceptor) { // 如果注解为空则不执行拦截器逻辑
                return;
            }

            /**
             * 检验参数
             */
            if (interceptor.checkParams()) { // 如果需要检验参数
                validateParams(method, arguments); // 执行参数校验
            }

        } catch (BusinessException e) {
            logger.error("全局拦截器异常", e); // 记录异常日志
            throw e; // 抛出业务异常
        } catch (Exception e) {
            logger.error("全局拦截器异常", e); // 记录异常日志
            throw new BusinessException(ResponseCodeEnum.CODE_500); // 抛出业务异常
        } catch (Throwable e) {
            logger.error("全局拦截器异常", e); // 记录异常日志
            throw new BusinessException(ResponseCodeEnum.CODE_500); // 抛出业务异常
        }
    }

    /**
     * 检验规则
     * @param method 方法
     * @param arguments 参数列表
     */
    private void validateParams(Method method, Object[] arguments) {
        Parameter[] parameters = method.getParameters(); // 获取方法参数列表
        for (int i = 0; i < parameters.length; i++) { // 遍历参数列表
            Parameter parameter = parameters[i]; // 获取参数
            Object value = arguments[i]; // 获取参数值
            VerifyParam verifyParam = parameter.getAnnotation(VerifyParam.class); // 获取参数上的校验注解
            if (verifyParam == null) { // 如果注解为空则跳过
                continue;
            }
            if (ArrayUtils.contains(TYPE_BASE, parameter.getParameterizedType().getTypeName())) { // 如果是基本类型
                checkValue(value, verifyParam); // 执行值校验
            } else {
                checkBObjValue(parameter, value); // 执行对象值校验
            }
        }
    }

    /**
     * 对象值校验
     * @param parameter 参数
     * @param value 参数值
     */
    private void checkBObjValue(Parameter parameter, Object value) {
        try {
            String typeName = parameter.getParameterizedType().getTypeName(); // 获取参数类型名
            Class classz = Class.forName(typeName); // 获取类对象
            Field[] fields = classz.getDeclaredFields(); // 获取类的所有字段
            for (Field field : fields) { // 遍历字段
                VerifyParam fieldVerifyParam = field.getAnnotation(VerifyParam.class); // 获取字段上的校验注解
                if (fieldVerifyParam == null) { // 如果注解为空则跳过
                    continue;
                }
                field.setAccessible(true); // 设置字段可访问
                Object resultValue = field.get(value); // 获取字段值
                checkValue(resultValue, fieldVerifyParam); // 执行值校验
            }
        } catch (BusinessException e) {
            logger.error("校验参数失败", e); // 记录异常日志
            throw e; // 抛出业务异常
        } catch (Exception e) {
            logger.error("校验参数失败", e); // 记录异常日志
            throw new BusinessException(ResponseCodeEnum.CODE_600); // 抛出业务异常
        }
    }

    /**
     * 值校验
     * @param value 值
     * @param verifyParam 校验参数
     */
    private void checkValue(Object value, VerifyParam verifyParam) {
        Boolean isEmpty = value == null || StringTools.isEmpty(value.toString()); // 判断值是否为空
        Integer length = value == null ? 0 : value.toString().length(); // 获取值长度
        /**
         * 检验空
         */
        if (isEmpty && verifyParam.required()) { // 如果值为空且需要校验空
            throw new BusinessException(ResponseCodeEnum.CODE_600); // 抛出业务异常
        }

        /**
         * 检验长度
         */
        if (!isEmpty && (verifyParam.max() != -1 && verifyParam.max() < length) || (verifyParam.min() != -1 && verifyParam.min() > length)) { // 如果值不为空且长度不符合规则
            throw new BusinessException(ResponseCodeEnum.CODE_600); // 抛出业务异常
        }

        /**
         * 校验正则
         */
        if (!isEmpty && !StringTools.isEmpty(verifyParam.regex().getRegex()) && !VerifyUtils.verify(verifyParam.regex(), String.valueOf(value))) { // 如果值不为空且不符合正则规则
            throw new BusinessException(ResponseCodeEnum.CODE_600); // 抛出业务异常
        }
    }
}

总结

 去浏览器直接调用这个路径,没有传参数的话,报错

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值