讯联云库项目开发日志(二)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.它怎么知道我要不要拦截检验

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

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

相关文章

用OBD部署OceanBase社区版的避坑指南

以下是用OBD黑屏部署 OceanBase社区版时容易碰到的几个问题及解决思路&#xff0c;供大家参考。 一、 遇坑步骤&#xff1a;用yaml文件部署集群&#xff1a; obd cluster deploy obtest -c mini-single-example.yaml 报错&#xff1a; Package oceanbase-ce-4.2.1.8-108000…

无锡哲讯科技:引领芯片封装SAP系统的智能化革命

芯片封装行业的数字化转型 在全球半导体产业高速发展的今天&#xff0c;芯片封装作为产业链的关键环节&#xff0c;直接影响着芯片的性能、可靠性和成本。随着5G、人工智能、物联网等技术的普及&#xff0c;市场对芯片的需求激增&#xff0c;封装企业面临着效率提升、良率优…

从海洋生物找灵感:造个机器人RoboPteropod,它能在水下干啥?

大家好&#xff01;在如今人类对水下环境探索不断深入的时代&#xff0c;从水下考古到珊瑚礁考察&#xff0c;各种任务都离不开水下机器人的助力。但传统水下机器人尺寸较大&#xff0c;在狭窄的水下空间施展不开。今天&#xff0c;我们就来认识一款受海洋小生物启发而设计的仿…

区块链blog1__合作与信任

&#x1f342;我们的世界 &#x1f33f;不是孤立的&#xff0c;而是网络化的 如果是单独孤立的系统&#xff0c;无需共识&#xff0c;而我们的社会是网络结构&#xff0c;即结点间不是孤立的 &#x1f33f;网络化的原因 而目前并未发现这样的理想孤立系统&#xff0c;即现实中…

Linux服务之lvs+keepalived nginx+keepalived负载均衡实例解析

目录 一.LVSKeepAlived高可用负载均衡集群的部署 二.NginxKeepAlived高可用负载均衡集群的部署 一.LVSKeepAlived高可用负载均衡集群的部署 实验环境 主keepalived&#xff1a;192.168.181.10 lvs &#xff08;7-1&#xff09; 备keepalived&#xff1a;192.168.181.10…

50天50个小项目 (Vue3 + Tailwindcss V4) ✨ |搭建项目框架

&#x1f5a4; 一个专注于「Vue3 TailwindCSS」的 50 天极简开发挑战&#xff0c;探索组件边界&#xff0c;打磨技术锋芒。 &#x1f389; 欢迎来到 50 个小项目的第一天&#xff01;今天我们将从零开始搭建一个 Vue3 项目&#xff0c;并引入 Tailwind CSS v4&#xff0c;为后…

Android 中 网络图片加载库 Glide 简介

Glide 是一个功能强大且广泛使用的图片加载库,适用于 Android 应用程序。它提供了简单易用的 API,用于从网络、本地存储或资源中加载图片,并支持图片的缓存、转换、占位图、动画等功能。 一、Glide 主要特点 简单易用 提供简洁的 API,一行代码即可加载图片。 支持多种数据…

07 web 自动化之 Unittest 应用:测试报告装饰器断言

文章目录 一、常见的第三方库结合 unittest 生产 html 格式测试报告1、HtmlTestRunner2、BeatifulReport 二、装饰器 unittest.skip 强制跳过&条件跳过三、unittest的常用断言方法 一、常见的第三方库结合 unittest 生产 html 格式测试报告 1、HtmlTestRunner 官网下载 …

【Python 面向对象】

Python 的面向对象编程&#xff08;OOP&#xff09;通过类&#xff08;Class&#xff09;和对象&#xff08;Object&#xff09;实现代码结构化&#xff0c;支持封装、继承和多态三大特性。以下是系统化指南&#xff1a; 一、类与对象基础 1. 定义类 class Dog:# 类属性&…

STM32F103_LL库+寄存器学习笔记23 - PWM波形输出及软件方式调整周期与占空比

导言 脉宽调制&#xff08;PWM&#xff09;是 STM32 定时器最常用的输出模式之一&#xff0c;广泛应用于电机驱动、LED 调光、伺服控制和功率管理等场景。本篇文章将以 TIM5 为例&#xff0c;从寄存器层面深入剖析 PWM 输出的原理与实现步骤。通过本篇博客&#xff0c;你不仅能…

堆(Heap)

1. 堆&#xff08;Heap&#xff09; 1.1. Python实现堆的插入、堆顶删除和排序 class MaxHeap:def __init__(self):# 初始化空堆&#xff0c;使用列表表示self.heap []def insert(self, val):# 插入元素并执行上浮self.heap.append(val)self._sift_up(len(self.heap) - 1)de…

Spring类

BeanDefinition BeanDefinition表示Bean定义&#xff0c;BeanDefinition中存在很多属性用来描述一个Bean的特点。比如&#xff1a; class&#xff0c;表示Bean类型scope&#xff0c;表示Bean作用域&#xff0c;单例或原型等lazyInit&#xff1a;表示Bean是否是懒加载initMeth…

在vue中this.$emit有哪些作用,事件监控具体含义,以及这些子组件能封装哪些功能组件

this.$emit 的作用 this.$emit 的作用是触发一个自定义事件&#xff0c;并将数据传递给父组件。父组件可以通过 v-on&#xff08;或 &#xff09;监听这个事件&#xff0c;并在事件触发时执行相应的处理函数。 this.content 的作用 this.content 是子组件的 props&#xff0…

前端流行框架Vue3教程:16. 组件事件配合`v-model`使用

组件事件配合v-model使用 如果是用户输入&#xff0c;我们希望在获取数据的同时发送数据配合v-model 来使用&#xff0c;帮助理解组件间的通信和数据绑定。 &#x1f9e9; 第一步&#xff1a;创建子组件&#xff08;SearchComponent.vue&#xff09; 这个组件用于处理用户的搜…

《Navicat之外的新选择:实测支持国产数据库的SQLynx核心功能解析》

数据库工具生态的新变量 在数据库管理工具领域&#xff0c;Navicat长期占据开发者心智。但随着国产数据库崛起和技术信创需求&#xff0c;开发者对工具的兼容性、轻量化和本土化适配提出了更高要求。近期体验了一款名为SQLynx的国产数据库管理工具&#xff08;麦聪旗下产品&am…

AgenticSeek开源的完全本地的 Manus AI。无需 API,享受一个自主代理,它可以思考、浏览 Web 和编码,只需支付电费。

​一、软件介绍 文末提供程序和源码下载 AgenticSeek开源的完全本地的 Manus AI。无需 API&#xff0c;享受一个自主代理&#xff0c;它可以思考、浏览 Web 和编码&#xff0c;只需支付电费。这款支持语音的 AI 助手是 Manus AI 的 100% 本地替代品 &#xff0c;可自主浏览网页…

vue3.0的name属性插件——vite-plugin-vue-setup-extend

安装 这个由于是在开发环境下的一个插件 帮助我们支持name属性 所以需要是-D npm i vite-plugin-vue-setup-extend -D在pasckjson中无法注释每个插件的用处 可以在vscode中下载一个JsonComments这样可以在json中添加注释方便日后维护和查阅API 引入 在vite.config.js中 im…

Linux基础 -- 在内存中使用chroot修复eMMC

Linux基础 – 在内存中使用chroot修复eMMC 概述 本教程将介绍如何在Linux系统中&#xff0c;使用chroot在内存中构建一个临时系统&#xff0c;并在不依赖原有系统的情况下修复eMMC&#xff08;如/dev/mmcblk2&#xff09;磁盘。该方法适用于嵌入式系统修复、磁盘清理以及离线…

人工智能、深度学习、机器学习的联系与区别

定义 人工智能&#xff08;AI - Artificial Intelligence&#xff09; &#xff1a;是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。它旨在让计算机能够像人类一样思考、学习和决策&#xff0c;涉及到诸如计算机视觉、自然语言处理…

web第二次课后作业--设计一个注册登录系统

一、页面展示 登录页面 提交页面 二、代码 2.1 登录页面 <% page language"java" contentType"text/html; charsetUTF-8"pageEncoding"UTF-8"%><html> <head><meta http-equiv"Content-Type" content"…