Spring Boot - 自定义注解来记录访问路径以及访问信息,并将记录存储到MySQL

1、准备阶段

application.properties;yml 可通过yaml<互转>properties

spring.datasource.url=jdbc:mysql://localhost:3306/study_annotate
spring.datasource.username=root
spring.datasource.password=123321
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update

依赖(以 jpa 为例,简化代码方便举例):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
</dependency>

2、自定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD) // 因为路径在方法上所以作用目标为 METHOD
@Retention(RetentionPolicy.RUNTIME) // 运行时:通过反射在运行时读取注解信息
public @interface AccessLog {
    String value() default "";
}

3、先来定义一个实体类

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.time.LocalDateTime;

@Entity
@Data
public class AccessLogEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String logMessage;
    private String ipAddress;
    private LocalDateTime timestamp;


    public AccessLogEntity() {
        this.timestamp = LocalDateTime.now();
    }

    public AccessLogEntity(String logMessage, String ipAddress) {
        this();
        this.logMessage = logMessage;
        this.ipAddress = ipAddress;
    }

}

4、接着dao

import com.lfsun.demolfsunstudyannotate.entity.AccessLogEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface AccessLogRepository extends JpaRepository<AccessLogEntity, Long> {
    // 这里可以定义一些自定义的查询方法,根据需要进行扩展
}


5、然后service

import com.lfsun.demolfsunstudyannotate.dao.AccessLogRepository;
import com.lfsun.demolfsunstudyannotate.entity.AccessLogEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AccessLogService {

    @Autowired
    private AccessLogRepository accessLogRepository;

    public void saveLog(String logMessage, String ipAddress) {
        // 在这里实现将日志信息保存到MySQL数据库的逻辑
        AccessLogEntity logEntity = new AccessLogEntity(logMessage, ipAddress);
        accessLogRepository.save(logEntity);
    }
}

6、该切面了

import com.lfsun.demolfsunstudyannotate.annotate.AccessLog;
import com.lfsun.demolfsunstudyannotate.service.AccessLogService;
import com.lfsun.demolfsunstudyannotate.util.IpUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Aspect
@Component
public class AccessLogAspect {

    @Autowired
    private AccessLogService accessLogService;

    @Before("@annotation(accessLog)")
    public void logAccess(JoinPoint joinPoint, AccessLog accessLog) {
        String methodName = joinPoint.getSignature().toShortString();
        String logMessage = accessLog.value().isEmpty() ? methodName : accessLog.value();
        String ipAddress = IpUtil.getClientIpAddress();

        // 在这里将日志信息记录到MySQL数据库
        accessLogService.saveLog(logMessage, ipAddress);
    }

}

7、controller

import com.lfsun.demolfsunstudyannotate.annotate.AccessLog;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/")
public class HelloController {

    @AccessLog("/hello")
    @GetMapping("/hello")
    public String hello() {
        return "hello lfsun!";
    }
}

如果观察仔细的话可以看到:点下就回到刚刚定义的切面了!
在这里插入图片描述

8、补个IpUtil ,获取ip的工具类

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Objects;

@Slf4j
public class IpUtil {

    private static final String X_FORWARDED_FOR_HEADER = "X-Forwarded-For";
    private static final String PROXY_CLIENT_IP_HEADER = "Proxy-Client-IP";
    private static final String WL_PROXY_CLIENT_IP_HEADER = "WL-Proxy-Client-IP";

    /**
     * 获取客户端真实IP地址,考虑了代理服务器的情况。
     *
     * @param request HttpServletRequest对象
     * @return 客户端真实IP地址
     */
    public static String getClientIpAddress(HttpServletRequest request) {
        String xForwardedForHeader = request.getHeader(X_FORWARDED_FOR_HEADER);
        if (xForwardedForHeader != null && !xForwardedForHeader.isEmpty()) {
            // 如果有多个IP地址,取第一个
            return xForwardedForHeader.split(",")[0].trim();
        } else if (request.getHeader(PROXY_CLIENT_IP_HEADER) != null) {
            return request.getHeader(PROXY_CLIENT_IP_HEADER);
        } else if (request.getHeader(WL_PROXY_CLIENT_IP_HEADER) != null) {
            return request.getHeader(WL_PROXY_CLIENT_IP_HEADER);
        } else {
            // 如果以上都不存在,直接获取RemoteAddr
            String remoteAddr = request.getRemoteAddr();
            log.warn("使用 remoteAddr 无法确定客户端 IP 地址: {}", remoteAddr);
            return remoteAddr;
        }
    }

    /**
     * 获取客户端真实IP地址,使用Spring的RequestContextHolder。
     *
     * @return 客户端真实IP地址
     */
    public static String getClientIpAddress() {
        try {
            HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
            return getClientIpAddress(request);
        } catch (NullPointerException e) {
            log.error("无法从 RequestContextHolder 获取 HttpServletRequest.", e);
            return "unknown";
        }
    }

}

项目启动并测试

1、从本地浏览器访问查看日志

在这里插入图片描述

2、从内网穿透的url访问查看日志

在这里插入图片描述

3、从内网穿透的url然后再开着梯子访问查看日志

在这里插入图片描述

吐槽一下 这是断网了吗 ~ ^ ~

在这里插入图片描述

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

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

相关文章

Mybatis系列之 parameterMap 弃用了

我 | 在这里 &#x1f575;️ 读书 | 长沙 ⭐软件工程 ⭐ 本科 &#x1f3e0; 工作 | 广州 ⭐ Java 全栈开发&#xff08;软件工程师&#xff09; &#x1f383; 爱好 | 研究技术、旅游、阅读、运动、喜欢流行歌曲 &#x1f3f7;️ 标签 | 男 自律狂人 目标明确 责任心强 ✈️公…

Java八股文(急速版)

Redis八股文 我看你在做项目的时候都使用到redis&#xff0c;你在最近的项目中哪些场景下使用redis呢? 缓存和分布式锁都有使用到。 问&#xff1a;说说在缓存方面使用 1.在我最写的物流项目中就使用redis作为缓存&#xff0c;当然在业务中还是比较复杂的。 2.在物流信息…

创新工具 | 教你6步用故事板设计用户体验事半功倍

问题 构思方案时团队在细节上难以共识 故事板是什么&#xff1f;故事板就像连环画一样&#xff0c;将用户使用解决方案的关键步骤顺序串联了起来&#xff0c;呈现了方案和用户之间的交互。 故事板以先后顺序展现团队票选出来的最佳解决方案&#xff0c;在过程中对于方案中未…

几个强力的nodejs库

几个强力的nodejs库 nodejs被视为许多Web开发人员的理想运行时环境。 nodejs的设计是为了在运行时中使用JavaScript编写的代码&#xff0c;它是世界上最流行的编程语言之一&#xff0c;并允许广泛的开发者社区构建服务器端应用程序。 nodejs提供了通过JavaScript库重用代码的…

Linux--网络编程

一、网络编程概述1.进程间通信&#xff1a; 1&#xff09;进程间通信的方式有**&#xff1a;管道&#xff0c;消息队列&#xff0c;共享内存&#xff0c;信号&#xff0c;信号量这么集中 2&#xff09;特点&#xff1a;依赖于linux内核&#xff0c;基本是通过内核来实现应用层…

计算机毕业设计选题推荐-家庭理财微信小程序/安卓APP-项目实战

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

SVG圆形 <circle>的示例代码

本专栏是汇集了一些HTML常常被遗忘的知识&#xff0c;这里算是温故而知新&#xff0c;往往这些零碎的知识点&#xff0c;在你开发中能起到炸惊效果。我们每个人都没有过目不忘&#xff0c;过久不忘的本事&#xff0c;就让这一点点知识慢慢渗透你的脑海。 本专栏的风格是力求简洁…

腾讯云助力港华能源上线“碳汭星云2.0”,推动能源行业绿色低碳转型

11月17日&#xff0c;港华能源与腾讯云联合打造的港华智慧能源生态平台“碳汭星云2.0”升级上线。依托双方的连接、大数据能力和行业深耕经验&#xff0c;该平台打破了园区“数据孤岛”&#xff0c;进一步提升了数据治理、应用集成和复制推广能力&#xff0c;未来有望以综合能源…

Docker发布简单springboot项目

Docker发布简单springboot项目 在IDEA工具中直接编写Dockerfile文件 FROM java:8COPY *.jar /app.jarCMD ["--server.prot 8080"]EXPOSE 8080ENTRYPOINT ["java", "-jar", "/app.jar"]将项目打包成对应的jar包&#xff0c;将Dockerf…

html主页框架,前端首页通用架构,layui主页架构框架,首页框架模板

html主页框架 前言功能说明效果使用初始化配置菜单加载主题修改回调 其他非iframe页面内容使用方式iframe页面内容使用方式 前言 这是一个基于layui、jquery实现的html主页架构 平时写的系统后台可以直接套用此框架 由本人整合编写实现&#xff0c;简单上手&#xff0c;完全免…

Android WMS——输入系统管理(十七)

一、简介 1、工作原理 输入子系统从驱动文件中读取事件后,再封装提交给 IMS,IMS 再发送给 WMS 进行处理。 Android 输入系统的工作原理概括来说,内核将原始事件写入到设备节点中,InputReader 不断地通过 EventHub 将原始事件取出来并翻译加工成 Android 输入事件,…

计算机网络(持续更新…)

文章目录 一、概述1. 计网概述⭐ 发展史⭐ 基本概念⭐ 分类⭐ 数据交换方式&#x1f970; 小练 2. 分层体系结构⭐ OSI 参考模型⭐TCP/IP 参考模型&#x1f970; 小练 二、物理层1. 物理层概述⭐ 四个特性 2. 通信基础⭐ 重点概念⭐ 极限数据传输率⭐ 信道复用技术&#x1f389…

C++:拷贝构造函数,深拷贝,浅拷贝

一.什么是拷贝构造函数&#xff1f; 同一个类的对象在内存中有完全相同的结构&#xff0c;如果作为一个整体进行复制&#xff08;拷贝&#xff09;是完全可行的。这个拷贝过程只需要拷贝数据成员&#xff0c;而函数成员是共用的&#xff08;只有一份拷贝&#xff09;。在建立对…

Java面试题07

1.线程池都有哪些状态&#xff1f; 线程池的状态有RUNNING&#xff08;运行中&#xff09;、SHUTDOWN&#xff08;关闭中&#xff0c;不接受新任务&#xff09;、 STOP&#xff08;立即关闭&#xff0c;中断正在执行任务的线程&#xff09;和TERMINATED&#xff08;终止&#x…

函数调用分析

目录 函数相关的汇编指令 JMP指令 call指令 ret指令 VS2019正向分析main函数 总结调用函数堆栈变化规律 x64dbg分析调用函数 IDA分析调用函数 函数相关的汇编指令 JMP指令 JMP 指令表示的是需要跳转到哪个内存地址&#xff0c;相当于是间接修改了 EIP 。 call指令 ca…

图像分割方法

常见的图像分割方法有以下几种&#xff1a; 1.基于阈值的分割方法 灰度阈值分割法是一种最常用的并行区域技术&#xff0c;它是图像分割中应用数量最多的一类。阈值分割方法实际上是输入图像f到输出图像g的如下变换&#xff1a; 其中&#xff0c;T为阈值&#xff1b;对于物体的…

Django 路由配置(二)

一、路由 就是根据用户请求的URL链接来判断对应的出来程序&#xff0c;并返回处理结果&#xff0c;也是就是URL和django的视图建立映射关系. 二、Django请求页面的步骤 1、首先Django确定要使用的根URLconf模块&#xff0c;通过ROOT_URLCONF来设置&#xff0c;在settings.py配置…

试用无线调试器PowerDebugger小记

试用无线调试器PowerDebugger小记 文章目录 试用无线调试器PowerDebugger小记引言准备软硬件环境PowerDebugger 无线调试器EVB-YTM32B1LE0-Q64 开发板 开始调试小结参考文献 引言 多年前调试智能车时&#xff0c;抱着电脑连着小车在跑道上一边跑一边看数据的经历&#xff0c;让…

春秋云境靶场CVE-2022-30887漏洞复现(任意文件上传漏洞)

文章目录 前言一、CVE-2022-30887描述和介绍二、CVE-2021-41402漏洞复现1、信息收集2、找可能可以进行任意php代码执行的地方3、漏洞利用找flag 总结 前言 此文章只用于学习和反思巩固渗透测试知识&#xff0c;禁止用于做非法攻击。注意靶场是可以练习的平台&#xff0c;不能随…

深入解析SSD Wear Leveling磨损均衡技术:如何让你的硬盘更长寿?

SSD的存储介质是什么&#xff0c;它就是NAND闪存。那你知道NAND闪存是怎么工作的吗&#xff1f;其实&#xff0c;它就是由很多个晶体管组成的。这些晶体管里面存储着电荷&#xff0c;代表着我们的二进制数据&#xff0c;要么是“0”&#xff0c;要么是“1”。NAND闪存原理上是一…