从零开始搭建Springboot项目脚手架4:保存操作日志

news2025/3/16 19:38:39

目的:通过AOP切面,统一记录接口的访问日志

1、加maven依赖

2、 增加日志类RequestLog

3、 配置AOP切面,把请求前的request、返回的response一起记录

package com.template.common.config;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.template.common.domain.model.RequestLog;
import eu.bitwalker.useragentutils.UserAgent;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 使用AOP切面记录请求日志
 */
@Aspect
@Component
@Slf4j
public class RequestLogger {
  /**
   * 切入点
   */
  @Pointcut("execution(public * com.template.api.controller.*.*Controller.*(..))")
  public void controllerPointcut() {
    // 本方法不会被执行,只是作为一个标记,与被注解的服务方法进行关联
    // 切入点为controller的public方法,也就是各个请求路径
  }

  /**
   * 环绕操作
   *
   * @param joinPoint 切入点
   * @return 原方法返回值
   * @throws Throwable 异常信息
   */
  @Around("controllerPointcut()")
  public Object controllerLogging(ProceedingJoinPoint joinPoint) throws Throwable {
    // 开始打印请求日志
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();

    // 计算各个日志参数
    long startTime = System.currentTimeMillis();
    Thread currentThread = Thread.currentThread();
    String userHost = extractUserHost(request);
    String userAgentString = request.getHeader("User-Agent");
    UserAgent userAgent = UserAgent.parseUserAgentString(userAgentString);
    Map<String, Object> requestParams = extractRequestParams(joinPoint);
    Object responseResult = joinPoint.proceed();
    Signature controllerMethod = joinPoint.getSignature();
    String classMethod = controllerMethod.getDeclaringTypeName() + "." + controllerMethod.getName();

    final RequestLog requestLog = RequestLog
      .builder()
      .userHost(userHost)
      .userOs(userAgent.getOperatingSystem().getName())
      .userBrowser(userAgent.getBrowser().getName())
      .userAgent(userAgentString)
      .requestUrl(request.getRequestURL().toString())
      .requestMethod(request.getMethod())
      .requestParams(requestParams)
      .responseResult(responseResult)
      .classMethod(classMethod)
      .threadId(Long.toString(currentThread.getId()))
      .threadName(currentThread.getName())
      .costMillisecond(System.currentTimeMillis() - startTime)
      .build();

    // 打印日志
    log.info("Request Log Info : {}", JSONUtil.toJsonStr(requestLog));

    return responseResult;
  }

  /**
   * 获取用户Host地址
   *
   * @param request 请求对象
   * @return 用户Host
   */
  private static String extractUserHost(HttpServletRequest request) {
    String xRealIp = request.getHeader("X-Real-IP");
    if (StrUtil.isNotEmpty(xRealIp) && !"unknown".equalsIgnoreCase(xRealIp)) {
      return xRealIp;
    }

    String xForwardedFor = request.getHeader("x-forwarded-for");
    if (StrUtil.isNotEmpty(xForwardedFor) && !"unknown".equalsIgnoreCase(xForwardedFor)) {
      return xForwardedFor;
    }

    String proxyClientIp = request.getHeader("Proxy-Client-IP");
    if (StrUtil.isNotEmpty(proxyClientIp) && !"unknown".equalsIgnoreCase(proxyClientIp)) {
      return proxyClientIp;
    }

    String wlProxyClientIp = request.getHeader("WL-Proxy-Client-IP");
    if (StrUtil.isNotEmpty(wlProxyClientIp) && !"unknown".equalsIgnoreCase(wlProxyClientIp)) {
      return wlProxyClientIp;
    }

    String httpClientIp = request.getHeader("HTTP_CLIENT_IP");
    if (StrUtil.isNotEmpty(httpClientIp) && !"unknown".equalsIgnoreCase(httpClientIp)) {
      return httpClientIp;
    }

    String httpxForwardedFor = request.getHeader("HTTP_X_FORWARDED_FOR");
    if (StrUtil.isNotEmpty(httpxForwardedFor) && !"unknown".equalsIgnoreCase(httpxForwardedFor)) {
      return httpxForwardedFor;
    }

    String remoteAddr = request.getRemoteAddr();
    if (!"127.0.0.1".equals(remoteAddr) && !"0:0:0:0:0:0:0:1".equals(remoteAddr)) {
      return remoteAddr;
    }

    //根据网卡取本机IP地址
    try {
      return InetAddress.getLocalHost().getHostAddress();
    } catch (UnknownHostException e) {
      log.error("getIpAddress exception:", e);
    }

    return xRealIp;
  }

  /**
   * 获取请求参数
   *
   * @param joinPoint 切入点
   * @return 请求参数
   */
  private Map<String, Object> extractRequestParams(ProceedingJoinPoint joinPoint) {
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    final String[] parameterNames = methodSignature.getParameterNames();
    final Object[] parameterValues = joinPoint.getArgs();

    if (ArrayUtil.isEmpty(parameterNames) || ArrayUtil.isEmpty(parameterValues)) {
      return Collections.emptyMap();
    }

    if (parameterNames.length != parameterValues.length) {
      log.warn("{}方法参数名和参数值数量不一致", methodSignature.getName());
      return Collections.emptyMap();
    }

    Map<String, Object> requestParams = new HashMap<>();
    for (int i = 0; i < parameterNames.length; i++) {
      requestParams.put(parameterNames[i], parameterValues[i]);
    }

    return requestParams;
  }

}

4、查看效果

 

 也可以把日志写入到数据库里面,自由发挥。

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

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

相关文章

Go语言的内存泄漏如何检测和避免?

文章目录 Go语言内存泄漏的检测与避免一、内存泄漏的检测1. 使用性能分析工具2. 使用内存泄漏检测工具3. 代码审查与测试 二、内存泄漏的避免1. 使用defer关键字2. 使用垃圾回收机制3. 避免循环引用4. 使用缓冲池 Go语言内存泄漏的检测与避免 在Go语言开发中&#xff0c;内存泄…

SpringCloud+Python 混合微服务,如何打造AI分布式业务应用的技术底层?

尼恩&#xff1a;LLM大模型学习圣经PDF的起源 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;经常性的指导小伙伴们改造简历。 经过尼恩的改造之后&#xff0c;很多小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试机会&#x…

力扣刷题---961. 在长度 2N 的数组中找出重复 N 次的元素【简单】

题目描述&#x1f357; 给你一个整数数组 nums &#xff0c;该数组具有以下属性&#xff1a; nums.length 2 * n. nums 包含 n 1 个 不同的 元素 nums 中恰有一个元素重复 n 次 找出并返回重复了 n 次的那个元素。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3,3] 输…

智能猫眼锁核心解决方案以及芯片简介SSD222

书接上回&#xff0c;前篇文章我们诠释了IP 网络摄像系统的定义以及组成部分的功能&#xff0c;也大概的讲了一下所针对的市场以及举例介绍了一款相关芯片&#xff0c;详情可点击下面卡片浏览高集成IP摄像SOC处理方案简介https://blog.csdn.net/Chipsupply/article/details/139…

XILINX FPGA DDR 学习笔记(一)

DDR 内存的本质是数据的存储器&#xff0c;首先回到数据的存储上&#xff0c;数据在最底层的表现是地址。为了给每个数据进行存放并且在需要的时候读取这个数据&#xff0c;需要对数据在哪这个抽象的概念进行表述&#xff0c;我们科技树发展过程中把数据在哪用地址表示。一个数…

【C++】<知识点> 标准模板库STL(上)

文章目录 一、STL---string类 1. 常用构造函数 2. 常用操作 3. 字符串流处理 二、STL---容器 1. STL及基本概念 2. 顺序容器简介 3. 关联容器简介 4. 容器适配器简介 5. 常用成员函数 三、STL---迭代器 1. 普通迭代器 2. 双向、随机访问迭代器 3. 不同容器的迭代器…

项目思考-编辑器

1、文本生成编辑器 2、图片合成编辑器&#xff08;未完待续&#xff09; 3、文字和图像版本的技术要点&#xff0c;区别&#xff08;未完待续&#xff09; 4、编辑器的人员配置考虑&#xff0c;技术难点分析&#xff08;未完待续&#xff09; 1、文本生成编辑器

AI爆文写作:如何找对标账号的文章?告诉你一个秘密:找低粉爆款的抄!这样风险最小!

一、注册新号来训练推荐爆款的素材 首先第一点:强烈推荐注册一个专用个人微信号,通过阅读,点赞和在看等动作,训练算法为我们推荐爆款素材。 二、为什么要对标低分爆款? 2.1 什么是低粉爆款? 就是粉丝量很少,但却有很高阅读量,甚至10万+阅读的文章。 对标账号的文章…

从零开始学逆向,js逆向启蒙:有道翻译

语言&#xff1a;js、python 工具&#xff1a;pycharm、chrome浏览器F12调试、chatgpt&#xff08;补充js第三方库&#xff0c;转python&#xff09;、node.js(js运行)&#xff08;必须&#xff09; 目标&#xff1a;学习掌握基本js逆向知识。 对象&#xff1a; 有道翻译 &a…

Nginx - 安全基线配置与操作指南

文章目录 概述中间件安全基线配置手册1. 概述1.1 目的1.2 适用范围 2. Nginx基线配置2.1 版本说明2.2 安装目录2.3 用户创建2.4 二进制文件权限2.5 关闭服务器标记2.6 设置 timeout2.7 设置 NGINX 缓冲区2.8 日志配置2.9 日志切割2.10 限制访问 IP2.11 限制仅允许域名访问2.12 …

移动硬盘难题:不显示容量与无法访问的解决策略

在使用移动硬盘的过程中&#xff0c;有时会遇到一些棘手的问题&#xff0c;比如移动硬盘不显示容量且无法访问。这种情况让人十分头疼&#xff0c;因为它不仅影响了数据的正常使用&#xff0c;还可能导致重要数据的丢失。接下来&#xff0c;我们就来详细探讨一下这个问题及其解…

java 子类继承父类

为什么需要继承 我现在要有两个类一个 一个是小学生&#xff0c;一个是大学生 代码 小学生 package b; public class encapsulatio{public String name;public int age;public double score;public void setscore (double score) {this.scorescore;}public void testing() {S…

AI预测福彩3D采取888=3策略+和值012路一缩定乾坤测试5月25日预测第1弹

上一套算法采用了88723的容差策略&#xff0c;关于容差策略相信大家都比较清楚&#xff1a;容差可以最大限度的保证初始大底中包含中奖号码&#xff0c;然后再通过设置一些杀号条件进行缩水。比如&#xff0c;我对我的各种模型算法近30期的预测结果进行了统计&#xff0c;如果采…

行车安全:UWB模块的智能化在车辆安全系统中的作用

随着交通车辆数量的不断增加和道路交通拥堵的加剧&#xff0c;车辆安全问题日益引起人们的关注。在这种背景下&#xff0c;超宽带&#xff08;UWB&#xff09;技术作为一种新兴的定位技术&#xff0c;正逐渐应用于车辆安全系统中&#xff0c;为提高车辆行车安全性提供了新的解决…

ClickHouse实战处理(一):MergeTree表引擎

MergeTree作为家族系列最基础的表引擎&#xff0c;主要有以下特点&#xff1a; 存储的数据按照主键排序&#xff1a;创建稀疏索引加快数据查询速度。支持数据分区&#xff0c;可以通过PARTITION BY语句指定分区字段。支持数据副本。支持数据采样。 一、MergeTree分类和建表参…

02. Flink 快速上手

02. Flink 快速上手 1、创建项目导入依赖 pom文件&#xff1a; <properties><flink.version>1.17.0</flink.version> </properties><dependency><groupId>org.apache.flink</groupId><artifactId>flink-streaming-java<…

算法打卡 Day10(栈与队列)-用栈实现队列 + 用队列实现栈

今天开始进入栈与队列啦&#xff01; 文章目录 栈与队列理论基础栈 Leetcode 232-用栈实现队列题目描述解题思路 Leetcode 225-用队列实现栈题目描述解题思路 首先我们来学习一下栈与队列的基础知识~ 栈与队列理论基础 栈与队列的区别是&#xff1a;栈是先进后出&#xff0c…

初识java——javaSE (6)接口的实现——比较器与深拷贝,浅拷贝

文章目录 前言一 比较器1.1 关于两个对象的比较1.2 Comparable接口&#xff1a;1.3 Arrays.sort方法的实现1.4 比较器的实现Comparator接口 二 深拷贝与浅拷贝2.1 浅拷贝&#xff1a;Cloneable接口&#xff1a;clone方法&#xff1a;实现拷贝&#xff1a;浅拷贝&#xff1a; 2.…

2024年5月22日 (周三) 叶子游戏新闻

《奇星协力》Steam抢先体验开启 求生城市建造Leikir Studio工作室开发的一款求生城市建造新游《奇星协力》Steam抢先体验开启&#xff0c;限时九折优惠&#xff0c;本作支持中文&#xff0c;感兴趣的玩家可以关注下了。 《原神》预告4.7版本前瞻特别节目 5月24日播出5月22日&am…

Opencompass模型评测教程

模型评测 模型评测非常关键&#xff0c;目前主流的方法主要可以概括为主观评测和客观评测&#xff0c;主观评测又可以分为两种形式&#xff1a;人工判断或者和模型竞技场。客观评测一般采用评测数据集的形式进行模型评测。本教程使用Opencompass工具进行对Internlm2-7b模型进行…