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

news2025/1/20 1:43:10

一、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); // 抛出业务异常
        }
    }
}

总结

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

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

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

相关文章

vscode编译c/c++找不到jni.h文件

解决办法: 一、下载JDK 访问Oracle官网的Java下载页面&#xff1a;Java Downloads | Oracle 选择适合您操作系统的JDK版本&#xff1a; 对于Windows&#xff0c;选择“Windows x64”或“Windows x86”&#xff08;取决于您的系统是64位还是32位&#xff09;。对于Linux&#…

STM32 HAL库开发——入门篇(3):OLED、LCD

源自正点原子视频教程&#xff1a; 【正点原子】手把手教你学STM32 HAL库开发全集【真人出镜】STM32入门教学视频教程 单片机 嵌入式_哔哩哔哩_bilibili 一、OLED 二、内存保护&#xff08;MPU&#xff09;实验 2.1 内存保护单元 三、LCD 3.1 显示屏分类 3.2 LCD简介 3.3 LCD…

基于Seatunnel最新2.3.5版本分布式集群安装部署指南(小白版)

基于Seatunnel2.3.5版本分布式集群安装部署 1.环境准备2.JDK安装3.Maven安装4.Seatunnel在master节点安装部署配置4.1.下载Seatunnel安装包4.2.解压下载好的tar.gz包4.3.下载连接器4.4.配置Seatunnel的系统环境变量4.5.配置 SeaTunnel Engine服务 JVM参数4.6.配置文件中集群相关…

阅读笔记——《AFLNET: A Greybox Fuzzer for Network Protocols》

【参考文献】Pham V T, Bhme M, Roychoudhury A. Aflnet: a greybox fuzzer for network protocols[C]//2020 IEEE 13th International Conference on Software Testing, Validation and Verification (ICST). IEEE, 2020: 460-465.【注】本文仅为作者个人学习笔记&#xff0c;…

socket通信(C语言+Python)

在socket文件夹下创建server.c和client.c。 服务端代码&#xff08;server.c&#xff09;&#xff1a; #include <stdio.h> #include <Winsock2.h> void main() {WORD wVersionRequested;WSADATA wsaData;int err;wVersionRequested MAKEWORD( 1, 1 );err WSAS…

单片机原理及技术(三)—— AT89S51单片机(二)(C51编程)

一、AT89S51单片机的并行I/O端口 1.1 P0口 AT89S51的P0口是一个通用的I/O口&#xff0c;可以用于输入和输出。每个引脚都可以通过软件控制为输入或输出模式。 1.1.1 P0口的工作原理 P0口的工作原理是通过对P0寄存器的读写操作来控制P0口的引脚。 输出模式&#xff1a;当P0口…

从大到小吗?-分支c++

题目描述 给出 4 个整数&#xff0c;a , b , c , d 。 判断这四个数字是否满足从大到小。 输入 输入 4 个整数&#xff0c;a , b , c , d 。 输出 输出 Yes 或者 No 。 样例输入 4 3 2 1 样例输出 Yes 提示 分析&#xff1a; 这道题十分的简单&#xff0c;只需判断…

技术管理之巅—如何从零打造高质效互联网技术团队阅读体验

技术管理之巅—如何从零打造高质效互联网技术团队 《技术管理之巅&#xff1a;如何从零打造高质效互联网技术团队》是黄哲铿所著的一本书&#xff0c;致力于帮助技术管理者从零开始打造高效的互联网技术团队。该书分为多个章节&#xff0c;分别探讨了从团队文化建设到技术架构…

【Redis】 Redis 集成到 Spring Boot上面

文章目录 &#x1f343;前言&#x1f384;Spring Boot连接redis客户端&#x1f6a9;项目的创建&#x1f6a9;配置端⼝转发&#x1f6a9;配置 redis 服务地址&#x1f6a9;更改 Redis 配置文件&#x1f6a9;使用 StringRedisTemplate 类操作 &#x1f38d;Spring Boot操作Redis客…

SAP HCM HR_PAD_HIRE_EMPLOYEE 自定义信息类型字段保存问题

导读 INTRODUCTION SAP HCM入职程序&#xff1a;SAP HCM入职程序有两个一个是HR_PAD_HIRE_EMPLOYEE一个是HR_MAINTAIN_MASTERDATA&#xff0c;前面的函数是SAP为新框架开发的&#xff0c;后面函数是旧的逻辑&#xff0c;这两个函数的在于底层的结构不一致&#xff0c;对于自定…

应用matplotlib.animation.FuncAnimation绘制摆线

上次尝试了用matplotlib.animation.ArtistAnimation绘制摆线&#xff0c;实际上也可以用matplotlib.animation.FuncAnimation实现同样的功能。 导入相关文件 引用的库包括numpy&#xff0c;matplotlib&#xff0c;代码如下&#xff1a; import numpy as np import matplotli…

【启程Golang之旅】让文件操作变得简单

欢迎来到Golang的世界&#xff01;在当今快节奏的软件开发领域&#xff0c;选择一种高效、简洁的编程语言至关重要。而在这方面&#xff0c;Golang&#xff08;又称Go&#xff09;无疑是一个备受瞩目的选择。在本文中&#xff0c;带领您探索Golang的世界&#xff0c;一步步地了…

Spring Boot集成pmd插件快速入门Demo

1.什么是pmd插件&#xff1f; PMD 插件允许您在项目的源代码上自动运行PMD代码分析工具&#xff0c;并生成带有其结果的站点报告。它还支持与 PMD 一起分发的单独的复制/粘贴检测器工具&#xff08;或 CPD&#xff09;。 此版本的 Maven PMD 插件使用 PMD 6.42.0 并且需要 Jav…

新增FTP功能、支持添加Redis远程数据库,专业版新增网站监控和黑金主题,1Panel开源面板v1.10.10版本发布

2024年6月7日&#xff0c;现代化、开源的Linux服务器运维管理面板1Panel发布v1.10.10版本。 在这一版本中&#xff0c;1Panel新增了多项实用功能。社区版方面&#xff0c;新增了FTP功能、支持添加Redis远程数据库、支持设置压缩密码&#xff0c;并新增了清理镜像构建缓存的功能…

从 Android 恢复已删除的备份录

本文介绍了几种在 Android 上恢复丢失和删除的短信的方法。这些方法都不能保证一定成功&#xff0c;但您可能能够恢复一些短信或其中存储的文件。 首先要尝试什么 首先&#xff0c;尝试保留数据。如果你刚刚删除了信息&#xff0c;请立即将手机置于飞行模式&#xff0c;方法是…

若依原生框架集成mybatisplus

1、进入父级依赖 将这个阿里数据库连接池druid注释掉&#xff0c;然后将pagehelper排除jsqlparser分页&#xff0c;使用mybatisplus分页查询防止mybatisplus与pagehelper版本不匹配&#xff0c;不然会报错 2、进入disease-framework模块&#xff1a; config的下面DruidConf…

【Python报错】已解决TypeError: can only concatenate str (not “int“) to str

解决Python报错&#xff1a;TypeError: can only concatenate str (not “int”) to str 在Python中&#xff0c;字符串连接是常见的操作&#xff0c;但如果你尝试将整数&#xff08;int&#xff09;与字符串&#xff08;str&#xff09;直接连接&#xff0c;会遇到TypeError: …

(函数)判断一句话中最长的单词(C语言)

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>//声明函数&#xff1b; int aiphabetic(char); int longest(char[]);int main() {//初始化变量值&#xff1b;int i;char line[100] { 0 };//获取用户输入字符…

【吊打面试官系列】CHAR 和 VARCHAR 的区别?

大家好&#xff0c;我是锋哥。今天分享关于 【CHAR 和 VARCHAR 的区别&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; CHAR 和 VARCHAR 的区别&#xff1f; 1、CHAR 和 VARCHAR 类型在存储和检索方面有所不同 1000道 互联网大厂Java工程师 精选面试题-Java资源…

什么是Docker ?

在软件开发的星辰大海中&#xff0c;有一个神奇的技术&#xff0c;它能够将应用程序及其依赖环境封装在一个轻量级的、可移植的容器中。这项技术就是Docker。它不仅简化了应用的部署流程&#xff0c;还让开发和运维之间的界限变得模糊&#xff0c;使得跨平台部署变得前所未有的…