【Springboot】基于AOP机制的前置通知以及Cookies记录用户操作日志

news2024/11/28 14:07:27

文章目录

  • 前言
    • 1. 添加依赖
    • 2. 创建自定义注解@LogAnnotation
    • 3. 创建日志记录类型
    • 3. 编写切面逻辑
    • 4. 完善切面层,获取详细的请求信息
      • 4.1 获取自定义注解上的属性值
      • 4.2 通过Cookies获取用户信息
      • 4.3 获取执行时间
      • 4.4 日志实体类以及对应数据库类型
    • 5.最后实现的结果


前言

在一个项目中,想要能够记录用户敏感操作的功能。例如用户登录操作,删除某个模块的内容,系统能够将系统日志自动加入到数据库中。日志内容主要包括了,操作用户的id,用户的姓名、用户ip来源、操作内容是什么,执行了什么URL,执行耗时等等。

其实日志记录,简单的实现实际上还是在用户执行敏感操作的时候,新增一个日志Controller,像其他业务一样实现数据库的增删改查。但是这种笨方法将会大大增加代码量。同时还要改动原项目代码,在执行写入数据库的控制类后还要写入数据库的日志类,如果不仔细审查会容易出错。

但是有没有好一点的方法呢?我们可以使用面向切面编程(Aspect Oriented Programming,AOP)解决这类问题。

先不扯这些原理机制,我们要实现的目标就是,用一种东西或者某种机制,在系统执行某个控制类(Controller)方法的之前(预先通知)或者之后(事后通知),它能够将执行的这个方法参数,用户信息,ip,执行了什么操作等等都记录下来,打印到系统后台或者写到数据库,具有高级权限的人员,如管理员可以从后台看到这些执行操作的信息。

怎么实现呢?如果我们使用了AOP机制,那么在需要日志记录的方法上添加注释即可。
例如给登录控制类上添加日志记录和打印,我们只需在LoginController添加一个自定义注释@LogAnnotation,并注明该注解下的两个属性的message,operation值即可:其他代码完全不用动。

    @LogAnnotation(message = "用户登录", operation = LogType.LOGIN)
    //上面的注释就已经完成了AOP机制
    @PostMapping("/login")
     public ApiResult login(@RequestBody Login login) {
     ....
     你的登录业务代码
     ....
     }

这实际上正是AOP的特性之一,在不改变源代码的前提下,给系统增加某些共有功能,例如日志记录,性能统计,安全控制,事务处理,异常处理等系统级维护层次,这样开发的好处就是,共有模块的代码和你的业务代码分离,降低代码耦合度。

本次项目就是采用AOP机制,实现日志记录,由于项目中没有使用任何安全框架,所以日志记录获取用户的登录信息(登录名,用户姓名等)采用了“获取cookies值”方法实现。

1. 添加依赖

        <!--spring切面aop依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- json解析依赖 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>

2. 创建自定义注解@LogAnnotation

由于是创建自定义注解,所以新建class的时候选择的是Annotation型。

package com.feng.generation_design.annotation;

import java.lang.annotation.*;
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented //生成文档
public @interface LogAnnotation {
    String message();  // 日志内容
    String operation();  // 日志类型
}

3. 创建日志记录类型

该类型很容易理解,如果你想要在用户执行某个添加功能时,启动日志记录,那么就在对应的“添加”控制类上,设定LogType的operation属性为 “ADD”;

package com.feng.generation_design.entity; 
public class LogType {
    //添加型日志
    public static final String ADD = "ADD";
    //删除型日志
    public static final String DELETE = "DELETE";
   //更新类型的日志记录
    public static final String UPDATE = "UPDATE";
    //查询类型的日志记录
    public static final String QUERY = "QUERY";
    //登录型日志
    public static final String LOGIN = "LOGIN";
   //退出登录型的日志记录
    public static final String LOGOUT = "LOGOUT";

}

接下去就是创建具体的切面逻辑。

3. 编写切面逻辑

新建一个java.class,名为SystemLogAspect ,它的具体结构如下:

public class SystemLogAspect {

    private static Logger logger = LoggerFactory.getLogger(SystemLogAspect.class);

    //定义切点@PointCut
    //在注解位置切入代码,也就是你的自定义注解所在的位置
    @Pointcut("@annotation(com.xxx.LogAnnotation)")

    public void logPoinCut() {
    }

    //前置通知
    //在执行方法之前打印获取的参数内容
    @Before("logPoinCut()")
    public void before(JoinPoint joinPoint) throws UnsupportedEncodingException {
	//将日志实现服务注入到该类中
 	//   @Autowired
   // OperateLogServiceimpl operateLogService;
       //在这里编写的日志记录代码。
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
        logger.info("URL: {}", request.getRequestURL().toString());
        logger.info("HTTP请求类型: {}", request.getMethod());
        logger.info("执行方法: {}", joinPoint);
        logger.info("传递参数: {}", Arrays.toString(joinPoint.getArgs()));
        logger.info("IP地址:   {}: " + request.getRemoteAddr());
    }

现在可以重启项目,然后可以在某个控制类上加上注解,

   @LogAnnotation(message = "用户登录", operation = LogType.LOGIN)

在前台触发加上注解的控制类,看系统后台是否能够正确打印日志信息。成功启动项目,触发日志:

2023-06-07 16:05:05.604  INFO 2892 --- [nio-8080-exec-4] c.f.g.aspect.SystemLogAspect             : URL: http://localhost:8080/institute/DeleteCurriculumById/10010
2023-06-07 16:05:05.604  INFO 2892 --- [nio-8080-exec-4] c.f.g.aspect.SystemLogAspect             : HTTP请求类型: DELETE
2023-06-07 16:05:05.604  INFO 2892 --- [nio-8080-exec-4] c.f.g.aspect.SystemLogAspect             : 执行方法: execution(ApiResult com.feng.generation_design.controller.CurriculumController.DeleteCurriculumById(Integer))
2023-06-07 16:05:05.604  INFO 2892 --- [nio-8080-exec-4] c.f.g.aspect.SystemLogAspect             : 传递参数: [10010]

现在就是将你想记录的日志内容,填充到你的before方法中。

4. 完善切面层,获取详细的请求信息

这部分就是在上面的结构基础上,完善我们要记录的日志信息。下面所有的代码都是写到

首先,我们之前提到过,我们自定义的注解,其实有两个参数,分别是operationmethod属性,我们给不同的控制类设置了不同的属性值,我们怎么在上面的SystemLogAspect 中获取呢?

4.1 获取自定义注解上的属性值

获取method属性

 //获取切入点属性
        //从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取切入点所在的方法
        Method method = signature.getMethod();
        //获取操作
        LogAnnotation myLog = method.getAnnotation(LogAnnotation.class);
        if (myLog != null) {
            String value = myLog.message();
            System.out.println("获取到的method属性:" + operatingLog.getMessage());
        }

4.2 通过Cookies获取用户信息

如果在前端使用了cookies保存用户的一些登录信息。如用户名,id,等信息,那么只需借助声明
HttpServletRequest request = attributes.getRequest();辅助获得用户数据。

首先,不妨先打印出你的Cookies里面到底有什么。(Cookie采用的都是key-value存储数据的)

    Cookie[] cookies = request.getCookies();
        if (cookies != null && cookies.length > 0) {
            for (Cookie cookie : cookies) {
                System.out.println(cookie.getName() + ": " + cookie.getValue());
            }

里面的中文信息会有乱码,
在这里插入图片描述

对有中文的cookie属性设置如下:


   request.setCharacterEncoding("UTF-8");
   //上面这个写到最前面
  
  Cookie[] cookies = request.getCookies();
        if (cookies != null && cookies.length > 0) {
            for (Cookie cookie : cookies) {
                System.out.println(cookie.getName() + ": " + cookie.getValue());
                //对有中文乱码进行编码设置。
                if (cookie.getName().equals("cname")) {
                    operatingLog.setUserName(URLDecoder.decode(cookie.getValue(), "utf-8"));
                }
				//对有中文乱码进行编码设置。
                if (cookie.getName().equals("cid")) {
                    operatingLog.setUserId(Integer.parseInt(URLDecoder.decode(cookie.getValue(), "utf-8")));
                }
            }

4.3 获取执行时间

记录用户执行当前操作的时间,数据库记录时间实际上是varchar类型,实体类也是String类型,所以,在后台直接获得指定格式的时间,转成字符串.。

  //获取执行时间
        Date day = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss ");
//        System.out.println("格式化输出:" + sdf.format(day));

既然能够获取到用户信息,以及执行的控制类信息,那就创建一个日志实体类,将上面收集的信息打包,创建Service层,ServiceImpl类、Mapper层,把上面的数据写入数据库。这部分的内和增删改查业务已经一样了。所以不再赘述。

4.4 日志实体类以及对应数据库类型

@Data
public class OperatingLog implements Serializable {
    private Integer logId;    //消息id
    private Integer userId;     //操作用户Id
    private String message;        //操作内容
    private String url;         //操作地址
    private String ip;      //请求Ip
    private String date;    //日志发生时间
    private Long totalTime;  //总耗时
    private String userName;		//操作用户名
    private String type;       //请求类型
    private String params;      //传递参数值
}

对应的数据库如下:
在这里插入图片描述

        operateLogService.add(operatingLog);

5.最后实现的结果

在这里插入图片描述

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

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

相关文章

Spring Authorization Server扩展实现OAuth2.0的密码模式

写在前面 Spring官方已经停止对Spring Security Oauth2的维护&#xff0c;项目和相关文档也被移除 Spring Authorization Server是官方新推出的OAuth2.1和OpenID Connect1.0的实现 两个主要的版本&#xff0c;0.4.x&#xff1a;jdk8。1.x: jdk17 这里用的版本是0.4.1 OAuth2…

A Comprehensive Survey of Neural Architecture Search: Challenges and Solutions

这是NAS综述系列的第二篇文章&#xff0c;针对《A Comprehensive Survey of Neural Architecture Search:Challenges and Solutions》的翻译。 神经架构搜索综述&#xff1a;挑战与解决方案 摘要1 引言1.1 动机1.2 我们的贡献和相关综述1.3 文章组织 2 早期NAS的特征3 优化策略…

SpringBoot之Spring Data JPA入门学习2

我们继续使用上一章的环境。SpringBoot之Spring Data JPA入门学习 一、自动生成数据 我们修改一下实体 增加了几个注解&#xff1a; CreationTimestamp 自动生成创建时间。 UpdateTimestamp 自动生成更新时间。 使用这两个注解我们还需要在类上加上两个注解DynamicInsert和…

【unity造轮子】排序排行榜的制作

List类中有一个【Sort方法】 可以非常快速的对【整数类】 或者【小数类】元素进行升序 public class TestCompare MonoBehaviour {public List<int>numbers;private void Start(){numbersnew List<int>(){20,10,30,70,60,40,50,90,80,100}:}private void Update()…

stable diffusion图片资源分享和模型推荐,好用的模型有哪些呢?

前言 这篇文章主要是分享我的图片和推荐一些好用的模型&#xff0c;模型不在多在于精&#xff0c;基于几个好的大模型适当下载一下LORA模型&#xff0c;就能画出非常好的图片&#xff0c;多话不说 图片分享 简单展示 详情请看&#xff1a;https://space.bilibili.com/109890…

Amazon Web Services (AWS)上的 OpenText 信息管理(IM) 解决方案

Amazon Web Services (AWS)上的 OpenText 信息管理(IM) 解决方案 OpenText 行业领先的信息管理(IM) 解决方案作为完全托管的服务提供&#xff0c;以 Amazon 公有云环境的安全性、可扩展性和性能为后盾&#xff0c;实现业务数字化转型并推动创新。 价值 降低运营成本30%以上&…

ldap服务安装,客户端安装,ldap用户登录验证测试

安装服务端 # 安装ldap服务 docker run -p 389:389 -p 636:636 \ --name openldap \-v /home/manager/testldap:/testldap \ --env LDAP_ORGANISATION"admin" \ --env LDAP_DOMAIN"hadoop.apache.org" \ --env LDAP_ADMIN_PASSWORD"Dmpxxx" \ -…

上周发布的Notes/Domino 12.0.2FP1以及REST API 1.0.4

大家好&#xff0c;才是真的好。 一段时间没见&#xff0c;有没有分外想念&#xff1f; 其实&#xff0c;我们每周都至少更新一篇&#xff0c;虽然今天是周日&#xff0c;可也是工作日啊&#xff0c;因此本周也算赶上发了一篇。 废话不多说&#xff0c;先上图&#xff1a; …

PMP证书含金量也太高了吧!在一线城市可享受多项福利~

近年来&#xff0c;企业与企业、城市与城市间的人才争夺变得更加激烈&#xff0c;各大城市为了泛集聚和培养重点领域紧缺专业人才均针对持有国际职业资格认证的人才出台了相关优惠政策&#xff0c;目前我了解到的已有以下5个城市针对PMP项目管理及相关行业和认证出台了鼓励政策…

2023-06-07:Redis 持久化方式有哪些?以及有什么区别?

2023-06-07&#xff1a;Redis 持久化方式有哪些&#xff1f;以及有什么区别&#xff1f; 答案2023-06-07&#xff1a; Redis提供了两种持久化机制&#xff1a;RDB和AOF。 RDB RDB持久化是将Redis当前进程中的数据生成快照并保存到硬盘的过程。快照指的是Redis在某一时刻的内…

2.5 TCP网络协议

一、TCP协议网络开发API 1、传输控制块&#xff08;TCB&#xff09; 传输控制块&#xff08;TCB&#xff09;是TCP协议的核心数据结构之一&#xff0c;它用于维护TCP连接状态和处理TCP数据传输。每个TCP连接都有一个对应的TCB&#xff0c;其中包含了该连接的相关信息&#xf…

地震勘探基础(十四)之地震反演

地震反演 反射波地震勘探主要是利用的是地下岩石的弹性差异特征&#xff0c;而弹性差异主要体现在波阻抗差异上。地震波垂直入射情况下&#xff0c;反射系数的公式告诉我们反射界面上下的波阻抗差异越大&#xff0c;反射系数越大&#xff0c;反射振幅也就相对较强。 利用反射波…

使用Hugo+Github从0开始免费搭建个人博客

环境搭建 一.安装git 以win11为例 1.注册git账号&#xff1a;https://github.com/并记住用户名和密码 2.下载地址&#xff1a;Git - Downloads (git-scm.com)鼠标右键显示Git Bash Here就表示安装Git成功了。 3.设置本地git用户 git config --global user.name "Your…

ROS 2 Humble 标定纠正畸变全景鱼眼展开网络摄像头

简介 本文使用 Ubuntu 系统,用 Python 开发 ROS 2 ,用 camera_calibration 功能包标定相机,用 OpenCV 读取视频帧和转换 ROS 2 图像话题,用 MediaMTX 搭建流媒体服务器, 用 FFmpeg 将视频帧输出为视频流。 最终效果: 环境准备 虚拟机 VMware Workstation 安装 Ubunt…

L9110S电机驱动模块demo

0.资料 项目工程文件夹 分文件原理 1.认识L9110S 1、概述&#xff1a; 一个L9110S驱动可以控制一个电机&#xff0c;图中左右两个黑色芯片就是L9110S驱动。当然如果会硬件也可以直接把它们设计到单片机开发板上。 一个电机由两个针脚控制&#xff0c;我们用杜邦线让L9110S…

MidJourney如何画出专业摄影师拍出的照片效果,附提示词

文 / 高扬&#xff08;微信公众号&#xff1a;量子论&#xff09; 最近沉迷于MidJourney作画&#xff0c;与ChatGPT相比&#xff0c;研究AI绘画&#xff0c;可以扩大自己的想像空间。 孩子的想像力更为丰富&#xff0c;如果家有宝宝&#xff0c;可以把孩子们的想法用AI绘画呈现…

面向对象的几大特性总结(适合秋招和小白学习的一篇文章)

前言&#xff1a; 本篇文章主要讲解面向对象的几大特性相关知识。该专栏比较适合刚入坑Java的小白以及准备秋招的大佬阅读。 如果文章有什么需要改进的地方欢迎大佬提出&#xff0c;对大佬有帮助希望可以支持下哦~ 小威在此先感谢各位小伙伴儿了&#x1f601; 以下正文开始 …

Doo Prime 德璞资本:怎么买黄金期货?黄金期货交易特点有哪些?

黄金期货是一种衍生品,是指在期货交易所上交易的黄金合约。作为一种高风险高收益的投资工具,有哪些黄金期货交易特点&#xff1f;本文围绕这一内容展开说明。 黄金期货交易特点一、需要开立账户 黄金期货是期货&#xff0c;如同股票投资要到证券公司开户一样&#xff0c;黄金期…

JVM | Java内存区域

JVM | Java内存区域 1、运行时数据区域1.1、程序计数器(线程私有)1.2、虚拟机栈(线程私有)1.3、本地方法栈(线程私有)1.4、堆(线程共享)1.5、方法区(元空间)(线程共享)1.6、直接内存(线程共享)2、HotSpot 虚拟机对象分配、布局和访问2.1、对象创建流程2.2、对象的…

【高危】Apache Inlong 存在JDBC反序列化漏洞

漏洞描述 Apache InLong 是可用于构建基于流式的数据分析、建模等一站式的海量数据集成框架。 在Apache Inlong受影响版本&#xff0c;由于未对接收的jdbcUrl参数过滤空格字符&#xff0c;导致可以利用空格绕过jdbcUrl中autoDeserialize参数过滤限制&#xff0c;通过认证的攻…