SpringBoot3与AOP完美结合:轻松追踪用户操作,实现精准日志记录

news2024/9/23 7:17:58

程序员必备宝典icon-default.png?t=N7T8https://tmxkj.top/#/

1.pom文件

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

       <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.78</version>
        </dependency>

2.Annotation 注解

import java.lang.annotation.*;

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

3.Entity实体类和Dao


import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.io.Serial;
import java.io.Serializable;
import java.util.Date;

@Data
@TableName(value = "sys_log",autoResultMap = true)
public class Log implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;

    @TableId(type = IdType.AUTO)
    /**
     * id
     */
    private Integer id;

    /**
     * 用户ip
     */
    private String requestIp;

    /**
     * 用户id
     */
    private String userId;

    /**
     * 用户名称
     */
    private String userName;

    /**
     * 请求地址
     */
    private String requestUrl;

    /**
     * 请求接口名称
     */
    private String requestName;

    /**
     * 请求格式
     */
    private String requestMethod;

    /**
     * 请求头信息
     */
    private String requestHeader;

    /**
     * 请求查询参数
     */
    private String requestQuery;

    /**
     * 请求体参数
     */
    private String requestParam;

    /**
     * 请求耗时(秒)
     */
    private Integer requestCost;

    /**
     * 请求状态
     */
    private String requestCode = "200";

    /**
     * 请求位置
     */
    private String requestPosition;

    /**
     * 响应状态
     */
    private String responseCode ="200";

    /**
     * 响应结果
     */
    private String responseResult;

    /**
     * 报错信息
     */
    private String reportErrors;

    /**
     * 创建时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;

    /**
     * 结束时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date endTime;

}
CREATE TABLE `tmxtestsql`.`Untitled`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `request_ip` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户ip',
  `user_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户id',
  `user_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户名称',
  `request_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求地址',
  `request_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求接口名称',
  `request_method` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求格式',
  `request_header` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '请求头信息',
  `request_query` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '请求查询参数',
  `request_param` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '请求体参数',
  `request_cost` int NULL DEFAULT NULL COMMENT '请求耗时(秒)',
  `request_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求状态',
  `request_position` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求位置',
  `response_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '响应状态',
  `response_result` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '响应结果',
  `report_errors` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '报错信息',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `end_time` datetime NULL DEFAULT NULL COMMENT '结束时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

 Dao数据层

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.portalwebsiteservice.demos.web.Entity.Log;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface LogDao extends BaseMapper<Log> {
}

 4.Util工具类(获取ip地址)

/**
 * 获取IP真实地址
 * 备注:在本地运行是获取不到真实地址,需要部署到服务上才能获取得到
 */
public class IpUtils {
        public static String getIpAddr(HttpServletRequest request) {
            String ipAddress = null;
            try {
                ipAddress = request.getHeader("x-forwarded-for");
                if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                    ipAddress = request.getHeader("Proxy-Client-IP");
                }
                if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                    ipAddress = request.getHeader("WL-Proxy-Client-IP");
                }
                if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                    ipAddress = request.getRemoteAddr();
                    if (ipAddress.equals("127.0.0.1")) {
                        // 根据网卡取本机配置的IP
                        InetAddress inet = null;
                        try {
                            inet = InetAddress.getLocalHost();
                        } catch (UnknownHostException e) {
                            e.printStackTrace();
                        }
                        ipAddress = inet.getHostAddress();
                    }
                }
                // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
                if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
                    // = 15
                    if (ipAddress.indexOf(",") > 0) {
                        ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                    }
                }
            } catch (Exception e) {
                ipAddress="";
            }
            return ipAddress;
        }
    }

5.Aspect切面类(业务流程)


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.portalwebsiteservice.demos.web.Annotation.RequestLog;
import com.portalwebsiteservice.demos.web.Dao.LogDao;
//import com.portalwebsiteservice.demos.web.Dto.JwtInfo;
import com.portalwebsiteservice.demos.web.Dto.Result;
import com.portalwebsiteservice.demos.web.Entity.Log;
//import com.portalwebsiteservice.demos.web.Service.JwtRedistService;
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.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.alibaba.fastjson.JSON.toJSONString;
import static com.portalwebsiteservice.demos.web.Util.IpUtils.getIpAddr;


@Aspect
@Component
public class LoggingAspect {

    @Resource
    private LogDao logDao;

   // @Resource
   // private JwtRedistService jwtRedistService;

    /**
     * execution是给指定区域,切入点(目前已去掉)
     * annotation是让特定类使用注解,切入点
     */
    @Pointcut("@annotation(com.portalwebsiteservice.demos.web.Annotation.RequestLog)")
    public void logPointCut() {}

    Date startDate;

    @Before("logPointCut()")
    public void beforeRequest() {
        startDate = new Date();
    }

    /**
     * 日志存入
     */
    @AfterReturning(value = "logPointCut()", returning = "result")
    public void saveLog(JoinPoint joinPoint, Result result) {
      try {
          // 获取请求头
          ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
          HttpServletRequest request = null;
          //创建实体类实例
          Log log = new Log();
          if (requestAttributes != null) {
              //获取到响应数据
              HttpServletResponse response = requestAttributes.getResponse();
              //获取请求头信息
              request = requestAttributes.getRequest();
              //从切面织入点处通过反射机制获取织入点处的方法
              MethodSignature signature = (MethodSignature) joinPoint.getSignature();
              //获取切入点所在的方法
              Method method = signature.getMethod();
              //------------------------以下方法是设置实体类参数----------------------------//
              // 获取@SystemLog(value = "用户登录")中的注解value
              RequestLog requestLogName = method.getAnnotation(RequestLog.class);
              if (requestLogName != null) {
                  String value = requestLogName.value();
                  log.setRequestName(value);
              }

              //获取用户Ip
              String clientIp = getIpAddr(request);
              log.setRequestIp(clientIp);

              //设置请求路径
              log.setRequestUrl(request.getRequestURI());
              //请求格式
              log.setRequestMethod(request.getMethod());
              //设置请求状态
              if (response != null) {
                  log.setRequestCode(String.valueOf(response.getStatus()));
              }
              //设置请求头信息
              Map<String, String> map = new HashMap<>();
              Enumeration<String> headerNames = request.getHeaderNames();
              while (headerNames.hasMoreElements()) {
                  String key = headerNames.nextElement();
                  String value = request.getHeader(key);
                  map.put(key, value);
              }
              log.setRequestHeader(toJSONString(map));
              //获取请求token
              String Authorization = request.getHeader("Authorization");
              //获取用户信息(这步骤是我的业务逻辑,你根据自己情况获取用户信息)
              if (Authorization != null) {
                 // JwtInfo jwtInfo =jwtRedistService.getUserInfo(Authorization);
                //  if (jwtInfo.getPass()){
                  //    log.setUserId(jwtInfo.getUserId());
                   //   log.setUserName(jwtInfo.getUser().getUserName());
                 // }
              }
              //设置查询参数
              log.setRequestQuery(request.getQueryString());
              //设置请求参数
              if (request.getMethod().equals("POST")) {
                  Object[] list = joinPoint.getArgs();
                  if (list != null && list.length > 0) {
                      String params = toJSONString(list[0]);
                      log.setRequestParam(params);
                  }

              }
              //设置响应结果
              log.setResponseResult(String.valueOf(result));
              //设置响应状态
              log.setResponseCode(String.valueOf(result.getCode()));
              //设置创建时间
              log.setCreateTime(startDate);
              Date nowTime = new Date();
              //设置结束时间
              log.setEndTime(nowTime);
              //设置请求时长
              long durationInMillis = nowTime.getTime() - startDate.getTime();
              long durationInSeconds = (TimeUnit.MILLISECONDS.toSeconds(durationInMillis))+1L;
              log.setRequestCost((int) durationInSeconds);
              //插入数据
              logDao.insert(log);
          }

      }catch (Exception e) {
          e.fillInStackTrace();
      }
    }


    /**
     * 定时任务清除数据
     */
    @Scheduled(cron = "0 0 2 * * ?")
    public void executeTask() {
        // 获取当前时间并减去3个月
        LocalDateTime threeMonthsAgo = LocalDateTime.now().minus(3, ChronoUnit.MONTHS);
        LambdaQueryWrapper<Log> lqw = new LambdaQueryWrapper<>();
        lqw.lt(Log::getCreateTime, threeMonthsAgo);
        List<Log> logList = logDao.selectList(lqw);
        if (logList != null && logList.size() > 0) {
            logDao.deleteBatchIds(logList);
        }
    }

备注:如果你使用了定时任务,记得在启动类添加@EnableScheduling注解

 6.使用(调用接口即可)

 运行结果:

{
  "RECORDS": [
    {
      "id": 22,
      "request_ip": "183.136.77777.78",
      "user_id": null,
      "user_name": null,
      "request_url": "/api-net/phone-info",
      "request_name": "获取手机号信息接口",
      "request_method": "GET",
      "request_header": "{\"remote-host\":\"\",\"referer\":\"http://api.aa1.cn\",\"cdn-loop\":\"cloudflare\",\"cf-ipcountry\":\"CN\",\"cf-ray\":\"8bac2457bd9093fa-LHR\",\"x-forwarded-proto\":\"https\",\"accept-language\":\"en-US,en;q=0.9\",\"x-forwarded-for\":\"183.136.132.78, 172.70.160.221\",\"x-host\":\"yubin-fuwu.top:80\",\"accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"x-real-ip\":\"172.70.160.221\",\"cf-visitor\":\"{\\\"scheme\\\":\\\"https\\\"}\",\"host\":\"yubin-fuwu.top:80\",\"connection\":\"upgrade\",\"cf-connecting-ip\":\"183.136.132.78\",\"x-scheme\":\"http\",\"cache-control\":\"max-age=0\",\"accept-encoding\":\"gzip, br\",\"user-agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36\"}",
      "request_query": "mobile=578887",
      "request_param": null,
      "request_cost": 3,
      "request_code": "200",
      "request_position": null,
      "response_code": "200",
      "response_result": "Result(code=200, msg=获取手机号信息成功, data=PhoneInfo(phoneNumber=77777, province=云南, city=文山, zipCode=663000, areaCode=0876, phoneType=电信))",
      "report_errors": null,
      "create_time": "29/8/2024 19:18:44",
      "end_time": "29/8/2024 19:18:46"
    }
  ]
}

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

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

相关文章

LlamaIndex 实现 Agent

RAG 是在数据层面为大模型提供更多、更新的外部知识&#xff0c;而 Agent &#xff08;智能体&#xff09;&#xff0c;为大模型扩展了推理业务的能力。数据是静态的&#xff0c;数据周期可能是天、小时甚至到秒&#xff0c;通过 RAG 实现时&#xff0c;需要调用对应系统的 API…

uni-app组件

一. 什么是组件,有什么好处? 在uni-app中&#xff0c;组件是构成应用的基本单位&#xff0c;它们是用来定义用户界面的一部分&#xff0c;并且通常包含了视图和逻辑。组件的设计使得开发者能够以声明式的方式构建应用界面&#xff0c;并且通过组件化的开发方式来提高代码的复…

vue-cli搭建项目过程

一.前言 传统的前端项目架构&#xff1a; 指的就是一个项目中有很多个HTML文件&#xff0c;每一个HTML文件都是相互独立的&#xff0c;如果需要在页面中导入一些外部依赖的css,js文件&#xff0c;就需要在每一个html文件中都导入就会显得特别麻烦&#xff0c;而且这些外部依赖…

详细git使用教程以及git base here命令行

0 下载 这个是官网下载特别慢 Git - Downloads (git-scm.com) 1 最基本操作与初始配置 1.1&#xff0c;linux的基本命令可用 下载安装后鼠标右键选git base here即可打开 1.2&#xff0c;git init /git clone初始化&#xff0c;创建本地仓库 出现.git隐藏文件 git clone “…

Big Model Weekly | 第34期

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 01 MiniCPM-V: A GPT-4V Level MLLM on Your Phone 近期多模态大型语言模型&#xff08;MLLMs&#xff09;的快速发展&#xff0c;从根本上改变了人工智能研究和产业的格局&#xff0c;为实现人工智能的下一个重…

Java去掉字符串中的特殊符号只保留中文数字和字母

今天在做一个导入功能发现用户导入的数据有特殊符号&#xff0c;于是想着给他去掉&#xff0c;搜了一下发现大多数方法都只保留了字母数字&#xff0c;连中文都去掉了&#xff0c;这很明显不符合我的需求 直接上代码 /*** author Sakura* date 2024/8/27 15:18*/ public clas…

Python(C++)自动微分导图

&#x1f3af;要点 反向传播矢量化计算方式前向传递和后向传递计算方式图节点拓扑排序一阶二阶前向和伴随模式计算二元分类中生成系数高斯噪声和特征二元二次方程有向无环计算图超平面搜索前向梯度下降算法快速傅里叶变换材料应力和切线算子GPU CUDA 神经网络算术微分 Pytho…

使用谷歌浏览器查看原型

需求人员给了一个原型文件包&#xff0c;用谷歌浏览器打开提示以下内容&#xff1a; 找到需求人员发的原型文件包 进入到resources-->chrome&#xff0c;找到axure-chrome-extension.crx&#xff0c;复制一份出来命名为axure-chrome-extension.tar&#xff0c;然后在该目录下…

招联金融基于 Apache Doris 数仓升级:单集群 QPS 超 10w,存储成本降低 70%

在竞争激烈的消费金融市场中&#xff0c;有效利用海量数据、提升业务运营效率是赢得市场的关键。早期招联采用典型的 Lambda 架构提供业务报表、数据运营、个性推荐、风险控制等数据服务&#xff0c;而 Lambda 过多的技术栈也引发了数据孤岛、查询效率不足、代码复用性差以及开…

AI算法平台训练站裸土检测算法训练裸土检测算法源码

在全球化进程加快与环境问题日益突出的今天&#xff0c;裸土检测成为了环境监测和土壤管理中不可或缺的一环。裸土指的是没有植被覆盖的土壤区域&#xff0c;这些区域易受侵蚀&#xff0c;并可能导致土壤流失和环境退化。为了有效应对这些问题&#xff0c;裸土检测算法应运而生…

Redis持久化与主从同步

1 淘汰策略 127.0.0.1:6379> help expireEXPIRE key secondssummary: Set a keys time to live in secondssince: 1.0.0group: generic127.0.0.1:6379> help PEXPIREPEXPIRE key millisecondssummary: Set a keys time to live in millisecondssince: 2.6.0group: gener…

【CSP:202112-1】序列查询(Java)

题目链接 202112-1 序列查询 题目描述 求解思路 模拟&#xff1a;a数组可以看作是记录 f ( x ) f(x) f(x) 函数值发生变化出的 x x x 点&#xff08;每次自增1&#xff09;。因此将每段相同数值的 f ( x ) f(x) f(x) 用乘法计算出来即可&#xff0c;最后记得要加上最后一…

Java Web —— 第九天(事务)

事务管理 & AOP 事务回顾 概念 事务 是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;这些操作 要么同时成功&#xff0c;要么同时失败 操作 开启事务(一组操作开始前&#xff0c;开启事务): start transaction / begin 提交事务(这组操作全部成功…

服务器访问端口命令

服务器访问端口命令是一组用于管理服务器端口的命令行指令。服务器端口是用于与外部设备或应用程序进行通信的逻辑通道&#xff0c;它允许数据在服务器和其他设备之间传输。以下是一些常见的服务器访问端口命令。 netstat&#xff1a;这个命令用于检查服务器上当前的网络连接和…

FPGA第 5 篇,FPGA技术优略势,FPGA学习方向,FPGA学习路线(FPGA专业知识的学习方向,FPGA现场可编程门阵列学习路线和方向)

前言 前几篇讲了一下FPGA的发展和应用&#xff0c;以及未来前景。具体详细&#xff0c;请看 FPGA发展和应用&#xff0c;以及未来前景https://blog.csdn.net/weixin_65793170/category_12665249.html 这里我们来&#xff0c;记录一下&#xff0c;FPGA专业知识的学习路线 一.…

OpenAI remove key access while using AAD authentication

题意&#xff1a;“OpenAI 在使用 AAD 认证时移除了密钥访问权限” 问题背景&#xff1a; I am calling Azure OpenAI API in my python code. To set it up, we need to provide a few parameters, one of which is openai.api_key. There are 2 options to get this value -…

力扣hot100-动态规划

文章目录 概念动态规划基本思想常见步骤常用技巧常见问题类型 动态规划题目题目&#xff1a; 爬楼梯题解 概念 动态规划 动态规划&#xff08;Dynamic Programming&#xff0c;简称DP&#xff09;是一种解决问题的算法思想&#xff0c;通常用于优化问题。它的核心思想是将一个…

K8S声明式的管理方式

一、K8S声明式的管理方式&#xff1a; 1、适合对资源的修改操作 2、声明式管理依赖于yaml文件&#xff0c;所有的内容都在yaml文件中声明 3、编辑好的yml文件还是要靠陈述式命令发布到K8S集群中 二、K8S中支持三种声明式的资源管理方式&#xff1a; 1、deployment格式&…

如何用Java SpringBoot Vue搭建创新创业学分管理系统?实战教程

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

GLM大模型 - CogVideoX:5B 开源,2B 转为 Apache 协议

8月6日&#xff0c;我们发布并开源了CogVideoX-2B模型&#xff0c;受到广大开发者的欢迎。 为了促进社区的自主使用和开放式创新&#xff0c;我们现决定将参数规模更大、性能更强的产品级模型 CogVideoX-5B 开源&#xff0c;同时 CogVideoX-2B 的开源协议调整为更加开放的Apac…