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

目的:通过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、查看效果

 

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C/C++】Makefile文件的介绍与基本用法

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

烟囱ERP系统

一、烟囱系统定义 “烟囱式”系统&#xff0c;来自维基百科的解释是&#xff1a;一种不能与其他系统进行有效协调工作的信息系统&#xff0c;又称为孤岛系统。 二、烟囱系统的案例 比如&#xff1a;就像以下一样&#xff0c;各个系统之间是独立的&#xff0c;所有对接是通过…

揭秘Markdown:轻松掌握基础语法,让你的写作更高效、优雅!

文章目录 前言1.标题1.1 使用 和 - 标记一级和二级标题1.2 使用 # 号标记 2.段落格式2.1 字体2.2 分割线2.3 删除线2.4 下划线2.5 脚注 3.列表3.1 无序列表3.2 有序列表3.3 列表嵌套 4.区块4.1 区块中使用列表4.2 列表中使用区块 5.代码代码区块 6.链接7.图片8.表格9.高级技巧…

访问元组元素

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 在Python中&#xff0c;如果想将元组的内容输出也比较简单&#xff0c;可以直接使用print()函数即可。例如&#xff0c;要想打印上面元组中的untitle…

嵌入式进阶——震动马达

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 原理图控制分析功能设计 原理图 控制分析 S8050 NPN三极管特性 NPN型三极管的工作原理是基于PN结和PNP型晶体管的工作原理。 当外…

【大数据面试题】32 Flink 怎么重复读 Kafka?

一步一个脚印&#xff0c;一天一道面试题 首先&#xff0c;为什么要读过的 Kafka 数据重写读一次呢&#xff1f;什么场景下要怎么做呢&#xff1f; 答&#xff1a;当任务失败&#xff0c;从检查点Checkpoint 开始重启时&#xff0c;检查点的数据是之前的了&#xff0c;就需要…

扩散模型学习1

DDPM 总体训练原理 https://www.bilibili.com/video/BV1nB4y1h7CN/?spm_id_from333.337.search-card.all.click&vd_sourcef745c116402814185ab0e8636c993d8f 讲得很好&#xff1a;每次都是输入t和noise-x的图像&#xff0c;预测noise之后得到和加入的noise比较&#xff1b…

【Spring】深入理解 Spring 上下文(Context)层次结构

前言 在使用 Spring 框架进行应用程序开发时&#xff0c;Spring 上下文&#xff08;Context&#xff09;是一个非常重要的概念。Spring 上下文提供了一个环境&#xff0c;用于管理应用程序中的对象&#xff08;通常称为 Bean&#xff09;及其之间的依赖关系。在复杂的应用程序…

Virtual Box安装Ubuntu及设置

Virtual Box安装Ubuntu及设置 本文包含以下内容&#xff1a; 使用Virtual Box安装Ubuntu Desktop。设置虚拟机中的Ubuntu&#xff0c;使之可访问互联网并可通过SSH访问。 Ubuntu Desktop下载 从官网下载&#xff0c;地址为&#xff1a;Download Ubuntu Desktop | Ubuntu U…

HTML.

HTML:超文本标记语言&#xff08;Hyper Text Markup Language&#xff09; 超文本&#xff1a;不同于普通文本&#xff0c;可以定义图片&#xff0c;音频&#xff0c;视频等内容 标记语言&#xff1a;由标签构成的语言 HTML标签都是预定义好的HTML代码直接在浏览器中运行&#…

B站大数据分享视频创作300天100+原创内容4000+粉

以今年五一作为一个里程碑参考点&#xff0c;给明年一个可以比较的数据。 我正经发力创作是2023.06.17 (前面几个视频是试水)&#xff0c;300天不到一年时间 创作了100原创数据相关视频&#xff0c;创作频率应该很高了&#xff0c;收获了下面几个数字&#xff0c;审视自身&…

Cadence OrCAD学习笔记(3)capture使用技巧_1

本期介绍capture的一些使用技巧。资料来源于小破站up主硬小二 1、导出像Visio规格的图纸 2、全局修改元件属性 然后保存、关闭即可。 3、导出BOM 4、导出网表 5、元件自动编号 6、capture软件和allegro关联 7、新建原理图symbol 以上为添加封装库的路径 如果要创建多部分的sy…

Pandas 多层索引中的索引和切片操作你学会了吗

1. Series的索引操作 对于Series来说&#xff0c;直接中括号[]与使用 .loc() 完全一样 显式索引 # 导包import numpy as npimport pandas as pd data np.random.randint(0,100,size6) index [ ["1班","1班","1班","2班","…

MySQL事务篇1:事物的四大特性(ACID)、三类数据读取问题与隔离级别

一、什么是事务&#xff1f; MySQL的事务&#xff08;Transaction&#xff09;是一组由数据库管理系统&#xff08;DBMS&#xff09;执行的一个或多个SQL语句的集合&#xff0c;这些SQL语句作为一个单独的工作单元执行。事务的主要目的是确保数据库的一致性和完整性&#xff0c…

Qt 界面上字体自适应控件大小 - 随控件缩放

Qt 界面上字体自适应控件大小 - 随控件缩放 引言一、设计思路二、进阶版大致思路三、参考链接 引言 Qt控件自适应字体大小可以用adjustSize()函数&#xff0c;但字体自适应控件大小并没有现成的函数可调. - 本文实现了按钮上的字体随按钮大小变化而变化 (如上图所示) - 其他控件…

webgl入门-绘制三角形

绘制三角形 前言 三角形是一个最简单、最稳定的面&#xff0c;webgl 中的三维模型都是由三角面组成的。咱们这一篇就说一下三角形的绘制方法。 课堂目标 理解多点绘图原理。可以绘制三角形&#xff0c;并将其组合成多边形。 知识点 缓冲区对象点、线、面图形 第一章 web…

Denoising Diffusion Probabilistic Models 全过程概述 + 论文总结

标题&#xff1a;Denoising&#xff08;&#x1f31f;去噪&#xff09;Diffusion Probabilistic Models&#xff08;扩散概率模型&#xff09; 论文&#xff08;NeurIPS会议 CCF A 类&#xff09;&#xff1a;Denoising Diffusion Probabilistic Models 源码&#xff1a;hojona…

通过unsplash引入图片素材

如果您还没听说过——当您需要无版权费的照片用于项目时&#xff0c;无论是否用于商业目的&#xff0c;Unsplash 都是您的不二之选。我自己也经常用它来获取大型背景图像。 虽然他们为开发者提供了出色的 API&#xff0c;但他们还为您提供了通过 URL 直接访问随机图片的选项。…

《python编程从入门到实践》day39加更

# 昨日知识点回顾 添加主题、条目 # 今日知识点学习 19.1.3 编辑条目 1.URL模式edit——entry # learning_logs/urls.py ---snip---# 用于编辑条目的页面path(edit_entry/<int:entry_id>/, views.edit_entry, nameedit_entry), ] 2.视图函数edit_entry() # views.py fr…

《计算机网络微课堂》1-2:因特网概述

1-2&#xff1a;因特网概述 网络、互连网&#xff08;互联网&#xff09;和因特网因特网发展的三个阶段因特网的标准化工作因特网的组成 ‍ 网络、互连网&#xff08;互联网&#xff09;和因特网 我们首先介绍网络、互联网&#xff08;互连网&#xff09;因特网的基本概念&a…