Springboot使用Aop保存接口请求日志到mysql(及解决Interceptor拦截器中引用mapper和service为null)

news2024/11/16 21:54:01

一、Springboot使用Aop保存接口请求日志到mysql

1、添加aop依赖

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

2、新建接口保存数据库的实体类RequestLog.java

package com.example.springboot.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;

/**
 * <p>
 * 请求日志
 * </p>
 *
 * @author Sca_jie
 * @since 2023-09-28
 */
@Getter
@Setter
@TableName("request_log")
public class RequestLog implements Serializable {

    private static final long serialVersionUID = 1L;

    // 主键-自增
    @TableId(value = "number", type = IdType.AUTO)
    private Integer number;

    // 用户账号
    private String id;

    // 携带token
    private String token;

    // 接口路径
    private String url;
    
    // 请求类型
    private String method;

    // 携带参数
    private String params;

    // ip地址
    private String ip;

    // 结果
    private String result;

    // 接口发起时间
    private LocalDateTime startDate;

    // 接口结束时间
    private LocalDateTime endDate;

    // 响应耗时
    private String responseTime;
}

3、新建一个注解RequestLogAnnotation.java

package com.example.springboot.annotation;

import java.lang.annotation.*;

/**
 * 请求记录日志注解
 */
@Target({ElementType.TYPE, ElementType.METHOD}) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented
public @interface RequestLogAnnotation {
    String value() default "";
}

4、(核心)新建aop面切类RequestLogAspect.java拦截请求并保存日志

package com.example.springboot.common;

import cn.hutool.core.net.NetUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.example.springboot.annotation.RequestLogAnnotation;
import com.example.springboot.entity.RequestLog;
import com.example.springboot.mapper.RequestLogMapper;
import com.example.springboot.utils.CookieUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * 日志记录
 *
 */
@Aspect
@Component
public class RequestLogAspect {

    @Autowired(required = false)
    RequestLogMapper requestLogMapper;

    @Pointcut("@annotation(com.example.springboot.annotation.RequestLogAnnotation)")
    public void logPointCut() {

    }

    // 请求的开始处理时间(不同类型)
    Long startTime = null;
    LocalDateTime startDate;

    @Before("logPointCut()")
    public void beforeRequest() {
        startTime = System.currentTimeMillis();
        startDate = LocalDateTime.now();
    }

    @AfterReturning(value = "logPointCut()", returning = "result")
    public void saveLog(JoinPoint joinPoint, Object result) {

        // 获取请求头
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        HttpServletResponse response = requestAttributes.getResponse();

        //从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        //获取切入点所在的方法
        Method method = signature.getMethod();

        // 初始化日志表的实体类
        RequestLog requestLog = new RequestLog();

        //获取操作
        RequestLogAnnotation requestLogAnnotation = method.getAnnotation(RequestLogAnnotation.class);

//        // 获取@SystemLogAnnotation(value = "用户登录")中的注解value
//        if (systemLogAnnotation != null) {
//            String value = systemLogAnnotation.value();
//            requestLog.setSName(value);
//        }

        // 获取cookies
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            // 获取token
            for(Cookie cookie : cookies){
                if(cookie.getName().equals("token")){
                    requestLog.setToken(cookie.getValue());
                }
            }

            // 获取id
            String id = CookieUtil.getid(cookies);
            if (id != "" | id != null) {
                requestLog.setId(id);
            }
        }

        // 区分get和post获取参数
        String params = "{}";
        if (request.getMethod().equals("GET")) {
            params = JSONObject.toJSONString(request.getParameterMap());
        } else if (request.getMethod().equals("POST")) {
            params = JSONUtil.toJsonStr(joinPoint.getArgs());
        }

        // 获取用户真实ip地址
        String ip;
        if (request.getHeader("x-forwarded-for") == null) {
            ip = request.getRemoteAddr();
        } else {
            ip = request.getHeader("x-forwarded-for");
        }

        if (ip.equals("0:0:0:0:0:0:0:1")) {
            ip = "127.0.0.1";
        }

        // 用户Ip
        requestLog.setIp(ip);
        // 接口请求类型
        requestLog.setMethod(request.getMethod());
        // 请求参数(区分get和post)
        requestLog.setParams(params);
        // 请求接口路径
        requestLog.setUrl(request.getRequestURI().toString());
        // 返回结果
        requestLog.setResult(JSONObject.toJSONString(result));
        // 请求开始时间
        requestLog.setStartDate(startDate);
        // 请求结束时间
        requestLog.setEndDate(LocalDateTime.now());
        // 请求共计时间(ms)
        requestLog.setResponseTime(String.valueOf(System.currentTimeMillis() - startTime));

        // 保存日志到mysql
        requestLogMapper.insert(requestLog);
    }
}

5、在对应接口添加注解@RequestLogAnnotation

    @RequestLogAnnotation(value = "获取上传记录")
    @GetMapping("/getlist")
    public Result getlist (@RequestParam(required = false) String id) {
        if (id == null) {
            return Result.success(404, "参数缺失");
        } else {
            List<UploadLog> page = uploadLogService.getlist(id);
            return Result.success(200, page.toString());
        }

    }

效果如下

 二、解决Interceptor拦截器中引用mapper和service为null

背景

当我们项目中同时使用Interceptor拦截器和aop日志拦截时,被Interceptor拦截器所拦截的请求不会通过aop日志保存到数据库(防止恶意爬虫)。

但是项目如果需要记录这些被拦截的非法请求的话,目前暂时的解决方法是在Interceptor拦截器所拦截非法的请求之前再使用前面的RequestLogMapper再重新进行保存一次(只针对非法请求,因为合法请求会通过Aop日志拦截)。

但这时候又出现了新的问题,在Interceptor创建时mapper和service还没来得及注入,会导致mapper和service引用为null,就需要在创建前先行赋值,如下:

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    /**
     * 白名单
     */
    private static String[] WhiteList = {"/user/login", "/user/register"};

    /**
     * 解决在Token拦截器中无法使用mapper和service的情况(无Bean)
     * @return
     */
    @Bean
    public TokenInterceptor myTokenInterceptor () {
        return new TokenInterceptor();
    }

    /**
     * http请求拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        // 除excludePathPatterns内包含的接口,其他接口都要经过拦截,执行LogInterceptor()
        registry.addInterceptor(myTokenInterceptor())
                .excludePathPatterns(WhiteList);
    }
}

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

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

相关文章

VsCode 常见的配置、常用好用插件

1、自动保存&#xff1a;不用装插件&#xff0c;在VsCode中设置一下就行 2、设置ctr滚轮改变字体大小 3、设置选项卡多行展示 这样打开了很多个文件&#xff0c;就不会导致有的打开的文件被隐藏 4、实时刷新网页的插件&#xff1a;LiveServer 5、open in browser 支持快捷键…

FM100/FM101协议系列-快速充电接口芯片

产品描述&#xff1a; FM100/FM101是一款支持Quick Charge 2.0&#xff08;QC 2.0&#xff09;快速充电协议的充电接口控制器 IC&#xff0c;可自动识别快速充电设备类型&#xff0c;并通过QC2.0协议与设备握手&#xff0c;使之获得设备允许的安全最高充电电压&#xff0c;在保…

为什么程序员必须坚持写技术博客?

当你申请一份工作的时候&#xff0c;你的简历通常大概只有两页的篇幅。当你接受面试的时候&#xff0c;你通常会跟面试官聊上一两个小时。以如此简短的简历和如此短暂的面试来评估一名软件开发人员的技能非常困难&#xff0c;所以雇主以此判定某个人是否适合某个工作岗位也颇具…

VB.NET vs. VB6.0:现代化编程语言 VS 经典老旧语言

目录 ​.NET背景&#xff1a; 特点: VB6.0背景&#xff1a; 特点: 两者之间的不同: 总结: 升华: .NET背景&#xff1a; VB.NET一种简单&#xff0c;现代&#xff0c;面向对象计算机编程语言&#xff0c;有微软开发&#xff0c;VB.NET是一种基于.NET Framework的面向对象…

基于Dockerfile搭建LNMP

目录 一、基础环境准备 1、环境前期准备 二、部署nginx&#xff08;容器IP 为 172.18.0.10&#xff09; 1、配置Dockerfile文件 2、配置nginx.conf文件 3、构建镜像、启动镜像 三、部署mysql 1、配置Dockerfile文件 2、配置my.conf文件 3、构建镜像、启动镜像 5、验…

Linux 基本指令(上)

文章内容&#xff1a; 1. ls 指令 语法&#xff1a; ls [选项][目录或文件] 功能&#xff1a;对于目录&#xff0c;该命令列出该目录下的所有子目录与文件。对于文件&#xff0c;将列出文件名以及其他信息。 单个ls显示当前目录下的文件和目录 常用选项&#…

5MW风电永磁直驱发电机-1200V直流并网Simulink仿真模型

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【Redis学习笔记二】三种特殊数据类型、事务的基本操作、锁、持久化、发布订阅、主从复制、哨兵模式

文章目录 三种特殊数据类型geospatial 地理位置Hyperloglog 基数统计Bitmaps 事务基本操作悲观锁乐观锁 持久化RDB&#xff08;Redis Database&#xff09;AOF&#xff08;Append Only File&#xff09;拓展 Redis发布订阅命令原理缺点应用 redis主从复制概念作用为什么使用集群…

JVM222

文章目录 JVM222运行时数据区的内部结构线程程序计数器&#xff08;PC寄存器&#xff09;虚拟机栈 JVM222 运行时数据区的内部结构 概述 本节主要讲的是运行时数据区&#xff0c;也就是下图这部分&#xff0c;它是在类加载器加载完成后的阶段&#xff0c;如下图&#xff1a; …

MySql学习笔记:MySql性能优化

本文是自己的学习笔记&#xff0c;主要参考以下资料 - 大话设计模式&#xff0c;程杰著&#xff0c;清华大学出版社出版 - 马士兵教育 1、MySql调优金字塔2、MySql调优2.1、查询性能2.1.1、慢查询2.1.1.1、总结 1、MySql调优金字塔 Mysql 调优时设计三个层面&#xff0c;分别是…

华为云云耀云服务器L实例评测|云耀云服务器L实例部署JumpServer开源堡垒机

华为云云耀云服务器L实例评测&#xff5c;云耀云服务器L实例部署JumpServer开源堡垒机 一、前言二、JumpServer 介绍2.1 JumpServer 简介2.2 JumpServer特点2.3 JumpServer支持的资产类型 三、本次实践介绍3.1 本次实践简介3.2 本次环境规划3.3 操作系统及配置要求3.4 数据库环…

雷达波束高度估计、折射分类、大气波导现象概念

一、雷达波束高度估计 雷达波束在地球大气层中的传播并非直线,而是受到大气层的影响呈现出一种弯曲的形态,这种现象称为大气折射。这是由于地球大气的密度并非均匀,从地面到高空,大气的密度逐渐减小,因此电磁波在穿过大气层时,会因大气密度的变化而改变传播方向,形成弯曲…

二蛋赠书四期:《Go编程进阶实战:开发命令行应用、HTTP应用和gRPC应用》

前言 大家好&#xff01;我是二蛋&#xff0c;一个热爱技术、乐于分享的工程师。在过去的几年里&#xff0c;我一直通过各种渠道与大家分享技术知识和经验。我深知&#xff0c;每一位技术人员都对自己的技能提升和职业发展有着热切的期待。因此&#xff0c;我非常感激大家一直…

光伏发电预测(GRU模型,Python代码)

运行效果&#xff1a;光伏发电预测&#xff08;GRU模型&#xff0c;Python代码&#xff09;_哔哩哔哩_bilibili 所有库的版本&#xff1a; 1.数据集&#xff08;连续10年不间断采集三个光伏电站的发电量及天气情况&#xff0c;每隔半个小时采集一次信息&#xff0c;因此&…

RT-Thread 内存管理(学习一)

内存管理 在计算系统中&#xff0c;通常存储空间可以分为两种&#xff1a;内部存储空间和外部存储空间。 内部存储空间通常访问速度比较快&#xff0c;能够按照变量地址随机访问&#xff0c;也就是我们通常所说的RAM&#xff08;随机存储器&#xff09;&#xff0c;可以把它理…

设备上架与调试步骤项目篇

1.设备又哪些常见的调试方法&#xff1f; 2.设备开箱 -> 使用的步骤是什么&#xff1f; 3.开局新设备都要设置哪些功能&#xff1f; -- 工程师&#xff1a;架构设计 项目实施 故障排查 -- 调试设备&#xff1a; -- 1.WEB界面 - 界面调试 - 内容比较少的 主要项目 …

一文从0到1手把手教学UI自动化测试之数据驱动!

在UI的自动化测试中&#xff0c;我们需要把测试使用到的数据分离到文件中&#xff0c;如果单纯的写在我们的测试模块里面&#xff0c;不是一个好的设计&#xff0c;所以不管是什么类型的自动化测试&#xff0c;都是需要把数据分离出来的。当然分离到具体的文件里面&#xff0c;…

基于YOLOv5的工地安全帽、口罩检测系统

目录 1&#xff0c;YOLOv5模型原理介绍 1.1 输入侧 1.1.1 数据增强 1.1.2 自适应锚框计算 1.1.3 自适应图片缩放 1.2 Backbone 1.3 Neck 1.4 输出端 2 &#xff0c; 基于YOLOv5的工地安全帽、口罩检测系统实现流程 2.1 整体项目 2.2 代码展示 2.3 效果展示 1&a…

VulnHub Mercury

//nmap命令大全 -sT TCP (全)连接扫描&#xff0c;准确但留下大量日志记录-sS TCP SYN (半)扫描&#xff0c;速度较快&#xff0c;不会留下日志-sP扫描存活主机-pn 不检测主机存活,不进行ping-po 扫描之前不进行ping-O 查看目标主机系统版本-sV 探测服务版本-A 全面扫描 一、修…

C++day01(QT简介、C++)

今日任务&#xff1a; 代码&#xff1a; #include <iostream>using namespace std;int main() {/** 输入字符串统计大写、小写、数字、空格以及其他字符的个数**/string s;cout << "请输入一个字符串" << endl;//cin >> s;getline(cin,s);i…