Aop监控所有Controller,包括void类型的response中的出参(工具类)

news2024/11/23 6:45:42

一、主要坐标

       <!--aop-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>30.1-jre</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.41</version>
        </dependency>

完整pom.xml文件(自行参考)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.xpf</groupId>
    <artifactId>springboot_test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot_test</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--aop-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>30.1-jre</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.41</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

二、aop类

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @Author xpf
 * @Date 2023/8/2 10:30
 * @Version 1.0
 */
@Slf4j
@Aspect
@Component
@Order(99999)
public class ControllerAop {

    private static final String ACTIONAUTH = "actionAuth_";
    private static final String ACTION = "mddaction_";
    private static final String SUFFIX_LSIT = "List";

    private Set<String> excludeUris = Sets.newHashSet();
    private static final PathMatcher URI_PATH_MATCHER = new AntPathMatcher();
    private static final List<String> DEFAULT_DOWNLOAD_CONTENT_TYPE = Lists.newArrayList(
            "application/vnd.ms-excel",//.xls
            "application/msexcel",//.xls
            "application/cvs",//.cvs
            MediaType.APPLICATION_OCTET_STREAM_VALUE,//.*( 二进制流,不知道下载文件类型)
            "application/x-xls",//.xls
            "application/msword",//.doc
            MediaType.TEXT_PLAIN_VALUE,//.txt
            "application/x-gzip"//.gz
    );

    @Pointcut("execution (* com..*Controller.*(..)) && !execution (* *..ExtendController.testConnect(..))")
    public void serviceApi() {
    }

    @Around("serviceApi()")
    public Object processLog(ProceedingJoinPoint jp) throws Throwable {
        Object resObj = null;
        try{
            Method method = ((MethodSignature) jp.getSignature()).getMethod();
            //获取方法名称
            String methodName = method.getName();
            //获取参数名称
            LocalVariableTableParameterNameDiscoverer paramNames = new LocalVariableTableParameterNameDiscoverer();
            String[] params = paramNames.getParameterNames(method);

            Class<?> classTarget=jp.getTarget().getClass();
            Class<?>[] par=((MethodSignature) jp.getSignature()).getParameterTypes();
            HttpServletResponse response=null;
            RequestAttributes ra;
            ServletRequestAttributes sra;
            HttpServletRequest request=null;
            String url=null;
            String _contextPath=null;
            ContentCachingResponseWrapper wrapper = null;

            ra = RequestContextHolder.getRequestAttributes();
            sra = (ServletRequestAttributes) ra;

            if (null != sra) {
                request = sra.getRequest();
                // 如果是被排除的uri,不记录log
                if (matchExclude(request.getRequestURI())) {
                    return jp.proceed();
                }

                url = request.getRequestURI();
                if (null != request.getServletContext()) {
                    _contextPath = request.getServletContext().getContextPath();
                }
                response = sra.getResponse();
            }

            if(StringUtils.isNotBlank(_contextPath)){
                url=url.replaceFirst(_contextPath, "");
            }
            long start = System.currentTimeMillis();
            String finalUrl = url;
            String ip = getRemortIP(request);
            //获取参数
            Object[] args = jp.getArgs();
            //替换response为包装类型ContentCachingResponseWrapper
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof HttpServletResponse){
                    wrapper = new ContentCachingResponseWrapper((HttpServletResponse) args[i]);
                    args[i] = wrapper;
                    break;
                }
            }
            //过滤掉request和response,以及文件类型,不能序列化
            List<Object> filteredArgs = Arrays.stream(args)
                    .filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)
                            && !(arg instanceof MultipartFile)))
                    .collect(Collectors.toList());
            JSONObject rqsJson = new JSONObject();
            rqsJson.put("rqsMethod", methodName);
            if (filteredArgs == null || filteredArgs.isEmpty()) {
                rqsJson.put("rqsParams", null);
            } else {
                //拼接请求参数
//                Map<String, Object> rqsParams = IntStream.range(0, filteredArgs.size())
//                        .boxed()
//                        .collect(Collectors.toMap(j -> params[j], j -> filteredArgs.get(j)));

                rqsJson.put("rqsParams", JSON.toJSONString(filteredArgs));
            }
            try {
                resObj = jp.proceed(args);
            } catch (Exception e) {
                log.error(methodName + "方法执行异常!", e);
//                //可以使用线程池将日志信息存入表中
//                threadPool.submit(() -> {
//                    operationLogService.insert(JSON.toJSONString(rqsJson), null,1, finalUrl, ip, e.getMessage(), System.currentTimeMillis() - start);
//                });
                log.info("ip地址为:{} \n,url为:{} \n,耗时:{}毫秒 \n,入参:{} \n, 异常信息为:{} \n",
                        ip, finalUrl, System.currentTimeMillis() - start, JSON.toJSONString(rqsJson), e.getMessage());

                throw e;
            }
            if (isDownload(response)) {
                return resObj;
            }

            String outPrams = null;
            if (resObj == null) {
                if (wrapper != null){
                    byte[] content = wrapper.getContentAsByteArray();
                    if (content.length > 0) {
                        outPrams = new String(content, wrapper.getCharacterEncoding());

                        copyResponse(wrapper);
                    }
                }

            }else {
                //排除文件类型,无法序列化为 JSON 字符串
                if (!(resObj instanceof MultipartFile)){
                    outPrams = JSON.toJSONString(resObj);
                }
            }

            String finalOutPrams = outPrams;
//            threadPool.submit(() ->
//                operationLogService.insert(JSON.toJSONString(rqsJson), finalOutPrams,0, finalUrl, ip,  null, System.currentTimeMillis() - start)
//            );
            log.info("ip地址为:{} \n,url为:{} \n,耗时:{}毫秒 \n,入参:{} \n, 出参:{} \n",
                    ip, finalUrl, System.currentTimeMillis() - start, JSON.toJSONString(rqsJson), finalOutPrams);
        } catch(Exception e){
            log.error("AopAddLogError" + e.getMessage() ,e);
            throw e;
        }
        return resObj;
    }

    private void copyResponse(final HttpServletResponse response) {
        final ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        if (wrapper != null) {
            try {
                wrapper.copyBodyToResponse();
            } catch (IOException ignored) {
            }
        }
    }

    private boolean isDownload(final HttpServletResponse response) {
        final String contentType = response.getContentType();
        if (StringUtils.isBlank(contentType)) {
            return false;
        }
        return DEFAULT_DOWNLOAD_CONTENT_TYPE.stream().anyMatch(it -> StringUtils.equalsIgnoreCase(it, contentType));
    }

    private static String getRemortIP(HttpServletRequest request) {
        String ip = getXForwardedFor(request);
        return ip.indexOf(",") > 0 ? ip.split(",")[0].trim() : ip;
    }

    private static String getXForwardedFor(HttpServletRequest request) {
        String ip = request.getHeader("x-original-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Forwarded-For");
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("X-Forward-For");
                if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                    return request.getRemoteAddr();
                }
            }
        }
        return ip;
    }


    private boolean matchExclude(final String uri) {
        if (CollectionUtils.isEmpty(excludeUris)) {
            return false;
        }
        for (final String excludeUri : excludeUris) {
            if (URI_PATH_MATCHER.match(excludeUri, uri)) {
                return true;
            }
        }
        return false;
    }
}

三、测试

1、写一个有返回值得方法  2、写一个没有返回值,即void类型,但是有response输出

response.getWriter().write(); 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/*@Controller
@ResponseBody*/
@RestController //上面两个注释的注解等于此注解
@RequestMapping("/test")
public class HelloController {

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

    @GetMapping("/hello")
    public String sayHello(@RequestParam String testParam){
        return "hello world";
    }

    /**
     * 模拟捕获返回类型为void的,response.getWriter().write();中的参数(储藏)
     * @param testParam
     * @param response
     */
    @GetMapping("/sayVoid")
    public void sayVoid(@RequestParam String testParam, HttpServletResponse response){
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json;charset=utf-8");
        try{
            response.getWriter().write(testParam);
        }catch (IOException e){
            logger.error("HelloController.sayVoid error", e);
        }
    }
}

分别调用

1、有返回值类型

查看控制台输出

 2、无返回类型void

 查看控制台输出

 

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

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

相关文章

saas堡垒机定义以及优势简单说明

工作中我们经常可以听到saas这个词语&#xff0c;但对于saas堡垒机相信还有很多人不了解。今天我们就来一起简单聊聊什么是saas堡垒机&#xff0c;saas堡垒机有哪些优势。 saas堡垒机定义 saas堡垒机顾名思义为一款SaaS化堡垒机产品&#xff0c;即一款SaaS运维审计安全系统。…

学习记录——EGE-UNet、R2AU-Net、PHNet、CFNet

EGE-UNet: an Efficient Group Enhanced UNet for skin lesion segmentation 上海交大 2023 MICCAI 基于 U-Net 进行魔改&#xff0c;用于解决医学图像&#xff08;尤其是皮肤病变&#xff09;分割中面临的问题。由于它是针对移动健康应用开发的&#xff0c;解决了当前许多模型…

硬件系统工程师宝典(35)-----SDRAM是如何“提速”的?

各位同学大家好&#xff0c;欢迎继续做客电子工程学习圈&#xff0c;今天我们继续来讲这本书&#xff0c;硬件系统工程师宝典。 上篇我们了解了FLASH可分为NOR FLASH和NAND FLASH&#xff0c;NOR FLASH一般用于代码运行及擦除或编程较少的场合&#xff0c;NAND FLASH用于擦除、…

【CSS】3D卡片效果

效果 index.html <!DOCTYPE html> <html><head><title> Document </title><link type"text/css" rel"styleSheet" href"index.css" /></head><body><div class"card"><img…

C# XML文档相关操作

C# 创建XML文档 XML文档知识点创建XML文档向XML中追加读取XML文档读取带属性的XML文档删除节点 XML文档知识点 XML 是可扩展的标记语言 XML:用来存储数据 注意点&#xff1a;XML是严格区分大小写的&#xff0c;XML标签也是成对出现的 XML文档有且只能有一个根节点&#xff1b;…

【浪费了我两个小时时间】Microsoft store无法加载页面0x80131500

绕的圈&#xff0c;踩的坑 谷歌搜索&#xff0c; newbing搜索都叫我清理缓存&#xff0c;重新安装等方法。 还被这篇文章误导了一下&#xff1a;微软应用商店错误代码0x80072EFD怎么办&#xff1f;&#xff08;已解决&#xff09; 加上重启电脑各种试不行。 最后想到要去改代…

Maven发布项目到Nexus私服

项目pom配置 在项目pom.xml中文件中的仓库配置&#xff0c;Nexus私服如何搭建在这里不介绍了可自行百度。 <distributionManagement><repository><id>releases</id><name>Nexus Release Repository</name><url>http://私服地址:34…

C++设计模式之桥接设计模式

文章目录 C桥接设计模式什么是桥接设计模式该模式有什么优缺点优点缺点 如何使用 C桥接设计模式 什么是桥接设计模式 桥接设计模式是一种结构型设计模式&#xff0c;它可以将抽象接口和实现分离开来&#xff0c;以便它们可以独立地变化和扩展。 该模式有什么优缺点 优点 灵…

定时任务之Springboot整合Quartz详解

文章目录 一、什么是Quartz二、为什么使用Quartz1、为什么要用定时任务2、为什么使用Quartz 三、常见开源定时任务的框架的异同四、Quartz的相关概念五、Quartz的使用&#xff08;此处讲解使用主要流程&#xff09;六、开源引擎框架与业务如何结合使用 一、什么是Quartz quartz…

springboot通过springdata整合es7.x

首先要明确通过springdata操作es必须要将版本号和es的版本号对应上&#xff0c;否则会报错&#xff08;倒不用完全一一对应&#xff0c;但版本号最好不要相差太多&#xff09;。springdata引入的版本号由springboot的版本号决定&#xff0c;对应关系如下&#xff1a; 这里我用…

一起来学习怎样将文档翻译成中文的同时维持原有格式

在快节奏的现代生活中&#xff0c;文档翻译软件成为我们处理多语言文本的得力工具。然而&#xff0c;当我们使用文档翻译软件时&#xff0c;有时会面临一个头疼的问题&#xff1a;即使翻译出了准确的词句&#xff0c;但格式却完全没有保留下来。这时候&#xff0c;我们怎么办呢…

常见OOM异常分析排查

常见OOM异常分析排查 Java内存溢出Java堆溢出原因解决思路总结 Java内存溢出 java堆用于存储对象实例,如果不断地创建对象,并且保证GC Root到对象之间有可达路径,垃圾回收机制就不会清理这些对象,对象数量达到最大堆的容量限制后就会产生内存溢出异常. Java堆溢出原因 无法在…

mysql月统计数据,没有的填充为0

要按时间戳字段按月份分组查询记录表&#xff0c;可以使用DATE_FORMAT函数将时间戳字段格式化为年月格式&#xff0c;然后将结果按照该字段进行分组。 SELECT a.month month,ifnull(b.count, 0) count FROM (SELECT 1 month UNION ALL SELECT 2 month UNION ALL SELECT 3 mont…

为何企业和开发团队应该重视进行兼容性测试

随着科技的不断进步和软件的广泛应用&#xff0c;保证软件在不同平台和环境下正常运行变得至关重要。本文将探讨软件兼容性测试的重要性和好处&#xff0c;并介绍为何企业和开发团队应该重视进行兼容性测试&#xff0c;以确保软件的稳定性和用户体验。 提供用户友好的体验 软件…

xcode 的app工程与ffmpeg 4.4版本的静态库联调,ffmpeg内下的断点无法暂停。

先阐述一下我的业务场景&#xff0c;我有一个iOS的app sdk项目&#xff0c;下面简称 A &#xff0c;以及运行 A 的 app 项目&#xff0c;简称 A demo 。 引用关系为 A demo 引用了 A &#xff0c;而 A 引用了 ffmpeg 的静态库&#xff08;.a文件&#xff09;。此时业务出现了 b…

线程、进程和管程

一、线程 1.1 定义 线程&#xff1a;线程是进程中的实体&#xff0c;一个进程可以拥有多个线程&#xff0c;一个线程必须有一个父进程。线程有时被称为轻量级进程&#xff0c;是程序执行流的最小单元。 线程的组成部分&#xff1a; 1. 线程ID&#xff1a;线程标识符 2. 当前…

【python】python求解矩阵的转置(详细讲解)

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

Linux服务器安装JDK20

一、下载安装包 访问官网&#xff0c;找到JDK20&#xff0c;复制下载链接 我复制的链接是&#xff1a;JDK20 二、Linux服务器操作 1.服务器根目录下创建一个新的文件夹 cd /mkdir jdkscd /jdks2.将下载好的jdk-20上传到jdks下 3.解压缩 tar -zxvf jdk-20_linux-x64_bin.tar…

ClickHouse目录结构

默认安装路径&#xff1a;/var/lib/clickhouse/ 目录结构&#xff1a; 主要介绍metadata和data metadata 其中的default、system及相应的数据库&#xff0c;.sql文件即数据库创建相关sql语句 进入default数据库&#xff08;默认数据库&#xff09;&#xff1a; 可以看到数据库…

Java电子招投标采购系统源码-适合于招标代理、政府采购、企业采购、等业务的企业 tbms

&#xfeff;功能描述 1、门户管理&#xff1a;所有用户可在门户页面查看所有的公告信息及相关的通知信息。主要板块包含&#xff1a;招标公告、非招标公告、系统通知、政策法规。 2、立项管理&#xff1a;企业用户可对需要采购的项目进行立项申请&#xff0c;并提交审批&…