讯联云库项目开发日志(二)AOP参数拦截

news2025/5/16 10:19:46

目录

利用AOP实现参数拦截:

一、​​HTTP请求进入Controller​(发送邮件验证码)

二、AOP切面触发

1. 切面拦截(GlobalOperactionAspect.class)

method.getAnnotation()​​

 null == interceptor 判断​​

2.参数校验注解

3. 参数校验入口​​(valiadateParams方法)

三. 对象类型递归校验​​(checkObValue方法)[这段代码没有调用这个方法,下面有解释]

四. 基础类型校验​​(checkValue方法)

五、工具类调用

六、流程图 

​编辑

七、典型校验失败场景示例

六、设计亮点解析

总结流程

疑问:


利用AOP实现参数拦截:

一、​​HTTP请求进入Controller​(发送邮件验证码)

@RequestMapping("/sendEmailCode")
@GloballInterceptor(checkParams=true) // 触发AOP拦截
public ResponseVO sendEmailCode(HttpSession session, 
                              @VerifyParam(required = true) String email,
                              @VerifyParam(required = true) String checkCode,
                              @VerifyParam(required = true) Integer type){
    // 实际方法体执行前会先被AOP拦截
}

二、AOP切面触发

0.首先我们先定义一个切点方法

    @Pointcut("@annotation(com.cjl.annotation.GloballInterceptor)")//  表示下面方法为切点方法
    private void requestIntercector() {
        System.out.println("请求拦截");
    }
  1. 切点(@Pointcut)​​:定义“在哪里拦截”,不包含逻辑。
  2. ​增强(@Before/@Around)​​:定义“拦截后做什么”,需引用切点。
  3. ​注解匹配​​:通过 @annotation 实现声明式拦截,避免硬编码。
1. 切面拦截(GlobalOperactionAspect.class)

这是一个全局拦截器

@Before("requestIntercector()") // 声明在切点方法执行前运行
public void interceptor(JoinPoint point) throws BusinessException {
    try {
        // 1. 获取目标方法元信息
        Object target = point.getTarget(); // 获取被代理的目标对象实例
        Object[] arguments = point.getArgs(); // 获取方法调用参数值数组
        String methodName = point.getSignature().getName(); // 获取目标方法名称
        
        // 2. 通过方法签名获取精确的方法定义(考虑方法重载情况)
        MethodSignature signature = (MethodSignature) point.getSignature();
        Class<?>[] parameterTypes = signature.getMethod().getParameterTypes();
        Method method = target.getClass().getMethod(methodName, parameterTypes);
        
        // 3. 检查方法上的拦截器注解
        GloballInterceptor interceptor = method.getAnnotation(GloballInterceptor.class);
        if (null == interceptor) {
            return; // 无拦截注解则直接放行
        }
        
        // 4. 执行参数校验(根据注解配置决定)
        if (interceptor.checkParams()) {
            valiadateParams(method, arguments); // 进入核心校验流程
        }
        
    } catch (BusinessException e) {
        // 业务级异常处理(如参数校验失败)
        log.error("全局拦截器拦截到业务异常", e);
        throw e; // 原样抛出保持异常链
        
    } catch (Exception e) {
        // 系统级异常处理(如反射异常)
        log.error("全局拦截器发生系统异常", e);
        throw new BusinessException("系统繁忙,请稍后重试");
        
    } catch (Throwable e) {
        // 兜底处理所有错误(包括Error)
        log.error("全局拦截器捕获不可预期错误", e);
        throw new BusinessException("系统服务不可用");
    }
}
method.getAnnotation()
  • ​作用​​:通过反射获取方法上的 @GloballInterceptor 注解实例
 null == interceptor 判断​
  • ​条件成立​​:表示该方法​​没有标注​​ @GloballInterceptor
  • ​执行逻辑​​:直接退出拦截器,​​不进行任何校验​​,让方法正常执行

2.参数校验注解
@Retention(RetentionPolicy.RUNTIME)//  表示注解在运行时存在
@Target({ElementType.PARAMETER,ElementType.FIELD})//  表示该注解用于方法参数和字段上
public @interface VerifyParam {
    int min() default -1;
    int max() default -1;
    boolean required() default true; //  是否必传
    //正则校验
    VerifyRegexEnum regex() default VerifyRegexEnum.NO;//默认不校验
}
3. 参数校验入口​​(valiadateParams方法)
private void valiadateParams(Method method, Object[] arguments) throws BusinessException {
        // 获取方法的所有参数定义
        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 (null == verifyParam) {
                continue; // 无校验注解则跳过
            }

            // 基本数据类型校验(String/Long/Integer)
            if (TyPE_STRING.equals(parameter.getType().getName())
                    || TyPE_LONG.equals(parameter.getType().getName())
                    || TyPE_INTEGER.equals(parameter.getType().getName())) {
                checkValue(value, verifyParam);
            }
            // 对象类型校验
            else {
                checkObValue(parameter, value);
            }
        }
    }

三. 对象类型递归校验​​(checkObValue方法)[这段代码没有调用这个方法,下面有解释]

    private void checkObValue(Parameter parameter, Object value) throws BusinessException {
        try {
            // 1. 获取参数的实际类型(支持泛型)
            String typeName = parameter.getParameterizedType().getTypeName();
            Class<?> classz = Class.forName(typeName); // 加载类定义

            // 2. 遍历对象的所有字段
            Field[] fields = classz.getDeclaredFields();
            for (Field field : fields) {
                // 3. 检查字段上的校验注解
                VerifyParam fieldVerifyParam = field.getAnnotation(VerifyParam.class);
                if (fieldVerifyParam == null) {
                    continue; // 无注解字段跳过
                }

                // 4. 获取字段值(突破private限制)
                field.setAccessible(true);
                Object resultValue = field.get(value);

                // 5. 递归校验字段值
                checkValue(resultValue, fieldVerifyParam); // 调用基础校验方法
            }
        } catch (BusinessException e) {
            // 透传业务校验异常
            log.error("对象参数校验业务异常", e);
            throw e;
        } catch (Exception e) {
            // 处理反射等系统异常
            log.error("对象参数校验系统异常", e);
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
    }

四. 基础类型校验​​(checkValue方法)

private void checkValue(Object value, VerifyParam verifyParam) throws BusinessException {
        /**
         * 空值检测准备
         */
         Boolean isEmpty = value==null || StringTools.isEmpty(value.toString());
         Integer length  = value==null?null: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.空值判断(StringTools)​

public static boolean verify(VerifyRegexEnum regex, String value) {
    // 调用重载方法(图片中定义的EMAIL/PASSWORD规则在此生效)
    return verify(regex.getRegex(), value); // ← 使用枚举中的正则表达式
}

private static boolean verify(String regex, String value) {
    Pattern p = Pattern.compile(regex); // ← 编译正则表达式(如EMAIL的复杂规则)
    return p.matcher(value).matches();  // ← 执行实际匹配
}

2.正则校验(VerifyUtils)​

// 图片中定义的枚举校验规则
public enum VerifyRegexEnum {
    EMAIL("^[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+$", "邮箱格式错误"),
    PASSWORD("^(?=.*\\d)(?=.*[a-zA-Z]).{8,18}$", "需包含字母和数字,8-18位");

    // 当@VerifyParam(regex=VerifyRegexEnum.EMAIL)时:
    // 实际使用的正则表达式就是EMAIL枚举实例中的regex值
}

六、流程图 

七、典型校验失败场景示例

  1. ​空值校验失败​

    • 调用:sendEmailCode(null, "123", 0)
    • 抛出:BusinessException("参数不能为空")
  2. ​邮箱格式错误​

    • 调用:sendEmailCode("invalid#email", "123", 0)
    • 匹配:EMAIL正则表达式失败
    • 抛出:BusinessException("邮箱格式错误")(消息来自枚举的desc字段)

八、设计亮点解析

  1. ​双校验层设计​

    • 前端:基础非空校验(快速反馈)
    • 后端:AOP+正则深度校验(安全兜底)
  2. ​规则集中管理​

    // 修改密码规则只需调整枚举即可全局生效
    PASSWORD("^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,20}$", "需包含大小写字母和数字")

  3. ​递归对象校验​
    支持对嵌套对象的字段级校验:

    public void update(@VerifyParam UserDTO user) {
        // 会自动校验UserDTO中所有带@VerifyParam的字段
    }

总结流程

🔀 ​​参数校验流程全链路总结​​:

HTTP请求 → @GloballInterceptor触发AOP 
→ 解析@VerifyParam注解 
→ 分发校验(基本类型→checkValue() / 对象类型→递归checkObValue()) 
→ 调用StringTools/VerifyRegexEnum工具
→ 校验失败抛BusinessException 
→ 校验通过执行业务逻

​核心箭头图​​:

Controller 
→ 🌐 AOP切面 
→ 🔍 参数扫描 
→ ✂️ 规则匹配 
→ ✅/❌ 校验结果 
→ ⚡ 业务逻辑/异常返回  

疑问:

1.为什么遍历对象所有带@VerifyParam注解的字段进行校验为什么是递归?

答:遍历对象字段校验”本身不是递归,但当字段本身又是对象时,需要再次触发相同的校验流程​​,这才是递归的本质

如果变量是普通字段(checkvalue(user对象)),会采取普通字段校验,比如:

// 简单User对象(无嵌套)
public class User {
    @VerifyParam String name; // 基本类型字段
    @VerifyParam Integer age; // 基本类型字段
}

如果变量是​​嵌套对象字段->触发递归,比如:

// 嵌套的Order对象,里面有User
public class Order {
    @VerifyParam User user; // 对象类型字段!
}

校验流程​​:

  1. checkObValue(Order对象) → 发现user字段是对象类型
  2. ​递归调用​​ checkObValue(user) → 进入User类的字段校验
  3. 如果User类中还有对象字段,继续向下递归
    🔁 ​​这才是真正的递归调用链!

2.@Pointcut切点和@GloballInterceptor是什么关系?

🔍 ​​准确关系说明​​:

@Pointcut 和 @GloballInterceptor 是​​观察者与被观察者​​的关系,具体流程如下:

  1. 你在方法上标注 @GloballInterceptor → 声明"这个方法需要被拦截

  2. @Pointcut 像扫描仪一样持续监控所有方法

  3. 通过表达式 @annotation(com.cjl.annotation.GloballInterceptor) 专门寻找带该注解的方法

  4. // 切面内部工作原理
    if (当前方法有@GloballInterceptor注解) {
        执行@Before/...增强逻辑(interceptor方法);
    }

3.这个JoinPoint point参数传进来的是什么?

@Before("requestIntercector()") 

public void interceptorDO(JoinPoint point) throws BusinessException {
    ...
}

解析一下:

@Before("requestIntercector()")
public void interceptorDO(JoinPoint point) {
    // 1. 获取目标方法实例
    Method method = ((MethodSignature) point.getSignature()).getMethod();
    // 输出:public com.cjl.vo.ResponseVO com.cjl.controller.SessionController.sendEmailCode(...)

    // 2. 获取方法参数值数组(按声明顺序)
    Object[] args = point.getArgs(); 
    // 内容:[HttpSession实例, "user@example.com", "A7b9", 1]
    
    // 3. 获取具体参数
    HttpSession session = (HttpSession) args[0]; // 第一个参数
    String email = (String) args[1];            // 第二个参数
    String checkCode = (String) args[2];        // 第三个参数
    Integer type = (Integer) args[3];           // 第四个参数

    // 4. 获取注解配置
    VerifyParam emailVerify = method.getParameters()[1].getAnnotation(VerifyParam.class);
    // 获取email参数的@VerifyParam(required=true)注解
}

4.它怎么知道我要不要拦截检验

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2376770.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

龙虎榜——20250515

上证指数缩量收阴线&#xff0c;个股跌多涨少&#xff0c;上涨波段4月9日以来已有24个交易日&#xff0c;时间周期上处于上涨末端&#xff0c;注意风险。 深证指数缩量收阴线&#xff0c;日线上涨结束的概率在增大&#xff0c;注意风险。 2025年5月15日龙虎榜行业方向分析 一…

卡洛诗,将高端西餐的冗余价值转化为普惠体验

西餐市场正经历一场结构性变革&#xff0c;一二线城市的高端西餐陷入内卷&#xff0c;而下沉市场却因品质与价格断层陷入选择困境——消费者既不愿为高价西餐的面子溢价买单&#xff0c;又难以忍受快餐式西餐的粗糙体验。这一矛盾催生了万亿级的市场真空地带&#xff0c;萨莉亚…

Flutter在键盘的上方加一个完成按钮

有些情况下&#xff0c;输入框在输入键盘弹出后&#xff0c; 需要在键盘的上方显示一个toolbar &#xff0c; 然后 toolbar 上面一个完成按钮&#xff0c;点完成按钮把键盘关闭。 如图&#xff1a; 直接上代码&#xff0c;这样写的好处是&#xff0c;把 TextField 给封装了&…

SQL注入---05--跨站注入

1 权限说明 select * from mysql.user; 这里的Y表示我前面的命令权限为root&#xff0c;n表示不支持root权限 导致结果&#xff1a; 如果为root的话&#xff0c;我就可操作这些命令并且可以进行跨数据库攻击&#xff0c;但是如果不是高权限root就无法执行这些操作 2 root权限…

【免费分享】虚拟机VM(适用于 Windows)17.6.3

—————【下 载 地 址】——————— 【​本章下载一】&#xff1a;https://drive.uc.cn/s/7c4da5cd2af64 【​本章下载二】&#xff1a;https://pan.xunlei.com/s/VOQDkRRKc5OUVTauZezaiDEHA1?pwdpybg# 【百款黑科技】&#xff1a;https://ucnygalh6wle.feishu.cn/wiki/…

2025 后端自学UNIAPP【项目实战:旅游项目】5、个人中心页面:微信登录,同意授权,获取用户信息

一、框架以及准备工作 1、前端项目文件结构展示 2、后端项目文件结构展示 3、登录微信公众平台&#xff0c;注册一个个人的程序&#xff0c;获取大appid&#xff08;前端后端都需要&#xff09;和密钥&#xff08;后端需要&#xff09; 微信公众平台微信公众平台&…

蓝桥杯算法题 -蛇形矩阵(方向向量)

&#x1f381;个人主页&#xff1a;工藤新一 &#x1f50d;系列专栏&#xff1a;C面向对象&#xff08;类和对象篇&#xff09; &#x1f31f;心中的天空之城&#xff0c;终会照亮我前方的路 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 文章目录 P…

配置VScodePython环境Python was not found;

Python was not found; run without arguments to install from the Microsoft Store, or disable this shortcut from Settings > Manage App Execution Aliases. 候试试重启电脑。 在卸载重装python后会出现难以解决的局面&#xff0c;系统变量&#xff0c;命令行&#…

ollama 重命名模型

ollama 重命名模型 ollama list# 查看列表 ollama list # 生成原模型的Modelfile文件 ollama show --modelfile qwen3:32b > Modelfile # 从Modelfile文件创建新的模型 ollama create qwen3 -f Modelfile # 删除原模型 ollama rm qwen3:32b

Qt信号槽机制与UI设计完全指南:从基础原理到实战应用

目录 前言一、信号槽1.1 传参1.2 Qt信号与槽的对应关系1.2.1一对多关系1.2.2 多对一关系 二、Designer三、Layout 布局3.1 基础用法3.2 打破布局3.3 贴合窗口3.4 伸展器&#xff08;Spacer&#xff09;3.5 嵌套布局 四、ui指针五、QWidget六、QLabel 标签使用指南总结 前言 本…

XBL6501/02/03在POE设备上的应用方案

前言&#xff1a; 在当今数字化时代&#xff0c;POE&#xff08;Power over Ethernet&#xff09;设备因其能够通过以太网线同时传输数据和电力而被广泛应用。为了满足这些设备日益增长的电源需求&#xff0c;芯伯乐推出了XBL6501/02/03系列DC-DC电源芯片&#xff0c;为POE设备…

编程题 03-树2 List Leaves【PAT】

文章目录 题目输入格式输出格式输入样例输出样例 题解解题思路完整代码 编程练习题目集目录 题目 Given a tree, you are supposed to list all the leaves in the order of top down, and left to right. 输入格式 Each input file contains one test case. For each case, …

生信小白学Rust-03

语句和表达式 举个栗子&#x1f330; fn add_with_extra(x: i32, y: i32) -> i32 {let x x 1; // 语句let y y 5; // 语句x y // 表达式 } // 语句执行操作 // 表达式会返回一个值 怎么区分呢&#xff0c;目前我的理解是只要返回了值&#xff0c;那它就是表达式 fn…

缺乏需求优先级划分时,如何合理分配资源?

当需求优先级不明确时&#xff0c;合理分配资源的关键在于建立统一评估标准、实施敏捷资源管理、提升团队协作效率、加强跨部门沟通机制。尤其是建立统一评估标准至关重要&#xff0c;它能帮助组织快速判断各项需求的重要性与紧迫性&#xff0c;从而实现资源的动态匹配与有效利…

操作系统学习笔记第3章 内存管理(灰灰题库)

1. 单选题 某页式存储管理系统中&#xff0c;主存为 128KB&#xff0c;分成 32 块&#xff0c;块号为 0、1、2、3、…、31。某作业有 5 块&#xff0c;其页号为 0、1、2、3、4&#xff0c;被分别装入主存的 3、8、4、6、9 块中。有一逻辑地址为 [3, 70]&#xff08;其中方括号中…

详细分析python 中的deque 以及和list 的用法区别

dqque :双端队列&#xff0c;可以快速的从另外一侧追加和推出对象,deque是一个双向链表&#xff0c;针对list连续的数据结构插入和删除进行优化。它提供了两端都可以操作的序列&#xff0c;这表示在序列的前后你都可以执行添加或删除操作。 通过上图可以看出&#xff0c;deque …

Stack overflow

本文来源 &#xff1a;腾讯元宝 Stack Overflow - Where Developers Learn, Share, & Build Careers 开发者学习&#xff0c;分享 通过学习、工作和经验积累等方式&#xff0c;逐步建立和发展自己的职业生涯。 Find answers to your technical questions and help othe…

和为target问题汇总

文章目录 习题377.组合总和 IV494.目标和 和为target的问题&#xff0c;可以有很多种问题的形式的考察&#xff0c;当然&#xff0c;及时的总结与回顾有利于我们熟练掌握这些知识&#xff01; 习题 377.组合总和 IV 377.组合总和 IV 思路分析&#xff1a;通过观察&#xff0…

STM32单片机内存分配详细讲解

单片机的内存无非就两种&#xff0c;内部FLASH和SRAM&#xff0c;最多再加上一个外部的FLASH拓展。在这里我以STM32F103C8T6为例子讲解FLASH和SRAM。 STM32F103C8T6具有64KB的闪存和20KB的SRAM。 一. Flash 1.1 定义 非易失性存储器&#xff0c;即使在断电后&#xff0c;其所…

Ubuntu 编译SRS和ZLMediaKit用于视频推拉流

SRS实现视频的rtmp webrtc推流 ZLMediaKit编译生成MediaServer实现rtsp推流 SRS指定某个固定网卡&#xff0c;修改程序后重新编译 打开SRS-4.0.0/trunk/src/app/srs_app_rtc_server.cpp&#xff0c;在 232 行后面添加&#xff1a; ZLMediaKit编译后文件存放在ZLMediakit/rele…