当前位置: 首页 > article >正文

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

目录

利用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.它怎么知道我要不要拦截检验

相关文章:

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

目录 利用AOP实现参数拦截: 一、​​HTTP请求进入Controller​&#xff08;发送邮件验证码&#xff09; 二、AOP切面触发 1. 切面拦截&#xff08;GlobalOperactionAspect.class&#xff09; method.getAnnotation()​​ null interceptor 判断​​ 2.参数校验注解 3. 参…...

龙虎榜——20250515

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

知识图谱重构电商搜索:下一代AI搜索引擎的底层逻辑

1. 搜索引擎的进化论 从雅虎目录式搜索到Google的PageRank算法&#xff0c;搜索引擎经历了三次技术跃迁。而AI搜索引擎正在掀起第四次革命&#xff1a;在电商场景中&#xff0c;传统的「关键词匹配」已无法满足个性化购物需求&#xff0c;MOE搜索等新一代架构开始融合知识图谱…...

python-修改图片背景色

在Python中&#xff0c;可以使用图像处理库&#xff08;如OpenCV或Pillow&#xff09;来修改图片的背景色。通常&#xff0c;修改背景色的流程包括以下步骤&#xff1a; 1、对图片进行分割&#xff0c;识别前景和背景。 2、对背景区域进行颜色替换。 下面是两种实现方法&#x…...

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

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

【ROS2】ROS节点启动崩溃:rclcpp::exceptions::RCLInvalidArgument

1、问题描述 启动ROS节点时,直接崩溃,打印信息如下: terminate called after throwing an instance of rclcpp::exceptions::RCLInvalidArgumentwhat(): failed to create guard condition: context argument is null, at ./src/rcl/guard_condition.c:65 [ros2run]: Abo…...

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权限…...

Vue 学习随笔系列二十三 -- el-date-picker 组件

el-date-picker 组件 文章目录 el-date-picker 组件el-date-picker 只有某些日期可选 el-date-picker 只有某些日期可选 <template><div><el-form ref"form" size"mini":model"form" :rules"rules"label-width"8…...

【免费分享】虚拟机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/…...

Oracle中的select1条、几条、指定范围的语句

在Oracle中&#xff0c;可以使用不同的方法来选择一条记录、多条记录或指定范围内的记录。以下是具体的实现方式&#xff1a; 1. 查询单条记录 使用ROWNUM伪列限制结果为1条&#xff1a; SELECT * FROM your_table WHERE ROWNUM 1;特点&#xff1a;Oracle会在结果集生成时分…...

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 标签使用指南总结 前言 本…...

Anaconda环境中conda与pip命令的区别

文章目录 conda与pip的基本区别在Anaconda环境中的实际差异安装包环境管理依赖解决示例最佳实践建议 常见问题解答 conda与pip的基本区别 包来源与生态系统 conda&#xff1a;从Anaconda默认仓库或conda-forge等渠道获取包 不仅管理Python包&#xff0c;还能管理非Python依赖&…...

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;其所…...

RedHat7 如何更换yum镜像源

RedHat7如何更换yum镜像源&#xff1f; # 删除系统自带 yum rpm -qa|grep -e yum -e python-urlgrabber |xargs rpm -e --nodeps# 下载yum与wget的rpm软件包 curl -O http://mirrors.aliyun.com/centos/7/os/x86_64/Packages/yum-3.4.3-168.el7.centos.noarch.rpm curl -O ht…...

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…...

Intellij报错:the file size(3.47M) exceeds configured limit (2.56MB)

今天在部署一个教学平台的时候&#xff0c;当执行数据库脚本出现了以上问题。 自己把解决的方案分享给大家&#xff1a; 于IntelliJ IDEA或PyCharm&#xff0c;可以通过编辑idea.properties文件来增加文件大小限制。 打开idea.properties文件&#xff0c;通常位于IDE的安装目录…...