后台管理系统的通用权限解决方案(七)SpringBoot整合SpringEvent实现操作日志记录(基于注解和切面实现)

1 Spring Event框架

除了记录程序运行日志,在实际项目中一般还会记录操作日志,包括操作类型、操作时间、操作员、管理员IP、操作原因等等(一般叫审计)。

操作日志一般保存在数据库,方便管理员查询。通常的做法在每个请求方法中构建审计对象,并写入数据库,但这比较繁琐和冗余。更简便的做法是使用Spring Event框架进行统一处理。

Spring Event是Spring的事件通知机制,可以将相互耦合的代码解耦。Spring Event是监听者模式的一个具体实现。

监听者模式包含了监听者Listener、事件Event、事件发布者EventPublish,过程就是事件发布者EventPublish发布一个事件,被监听者Listener捕获到,然后执行事件Event相应的方法。

2 Spring Event案例

  • 1)创建maven工程spring-event-demo,并配置其pom.xml文件如下。由于Spring Event的相关API在spring-context包中,所以只需引入Spring相关依赖,而无需额外配置。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.2.RELEASE</version><relativePath/></parent><groupId>com.hsgx</groupId><artifactId>spring-event-demo</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies>
</project>
  • 2)创建审计信息类Audit、审计事件类AuditEvent、审计监听器类LogListener
package com.hsgx.event.pojo;import lombok.Data;import java.time.LocalDateTime;/*** 审计信息*/
@Data
public class Audit {private String type; //操作类型private LocalDateTime time; //操作时间private String userName; //操作员private String requestIp; //操作员IPprivate String description; //操作原因
}
package com.hsgx.event.pojo;import org.springframework.context.ApplicationEvent;/*** 定义审计事件*/
public class AuditEvent extends ApplicationEvent {public AuditEvent(Audit audit) {super(audit);}
}
package com.hsgx.event.listener;import com.hsgx.event.pojo.Audit;
import com.hsgx.event.pojo.AuditEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;/*** 审计监听器*/
@Component
public class AuditListener {// 异步监听AuditEvent事件@Async@EventListener(AuditEvent.class)public void saveAudit(AuditEvent auditEvent) {Audit audit = (Audit) auditEvent.getSource();long id = Thread.currentThread().getId();System.out.println("监听到审计事件:" + audit + " 线程id:" + id);// 将日志信息保存到数据库...}
}
  • 3)创建UserController用于发布事件
package com.hsgx.event.controller;import com.hsgx.event.pojo.Audit;
import com.hsgx.event.pojo.AuditEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.time.LocalDateTime;/*** 发布事件*/
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate ApplicationContext applicationContext;@GetMapping("/get")public String getUser(){// 构造操作日志信息Audit audit = new Audit();audit.setType("获取用户信息");audit.setTime(LocalDateTime.now());audit.setUserName("admin");audit.setRequestIp("127.0.0.1");audit.setDescription("获取用户信息");// 构造事件对象ApplicationEvent event = new AuditEvent(audit);// 发布事件applicationContext.publishEvent(event);long id = Thread.currentThread().getId();return "发布事件成功,线程id:" + id;}
}
  • 5)创建启动类SpringEventApp,使用@EnableAsync注解启用异步处理
package com.hsgx.event;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;@SpringBootApplication
@EnableAsync //启用异步处理
public class SpringEventApp {public static void main(String[] args) {SpringApplication.run(SpringEventApp.class,args);}
}
  • 6)启动项目后访问/user/get请求,触发发布事件,在监听器类AuditListener中监听到事件并进行相关操作

  • 7)在UserController中,需要注入ApplicationContext对象并调用publishEvent()方法手动发布事件,有点繁琐。我们可以通过创建一个审计注解@Audit,并通过切面拦截该注解的方式来完成。先引入AOP的依赖、hutool工具依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.1.0</version>
</dependency>
  • 8)创建审计注解@Audit
package com.hsgx.event.annotation;import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Audit {/*** 描述*/String value();/*** 类型*/String type() default "";
}
  • 9)创建切面类AuditAspect,做以下事情:
  • 在切面类AuditAspect中定义切点,拦截Controller中添加@Audit注解的方法
  • 在切面类AuditAspect中定义前置通知,在前置通知方法doBefore()中收集操作相关信息封装为Audit对象并保存到ThreadLocal中
  • 在切面类AuditAspect中定义成功返回通知,在成功返回通知方法doAfterReturning中通过ThreadLocal获取Audit对象并继续设置其他的成功操作信息,随后发布事件
  • 在切面类AuditAspect中定义异常返回通知,在异常返回通知方法doAfterThrowable中通过ThreadLocal获取Audit对象并继续设置其他的异常操作信息,随后发布事件
package com.hsgx.event.aspect;import cn.hutool.core.convert.Convert;
import cn.hutool.extra.servlet.ServletUtil;
import com.hsgx.event.pojo.Audit;
import com.hsgx.event.pojo.AuditEvent;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Objects;@Slf4j
@Aspect
public class AuditAspect {@Autowiredprivate ApplicationContext applicationContext;/*** 用于保存线程中的审计对象*/private static final ThreadLocal<Audit> THREAD_LOCAL = new ThreadLocal<>();/*** 定义Controller切入点拦截规则,拦截 @Audit 注解的方法*/@Pointcut("@annotation(com.hsgx.event.annotation.Audit)")public void auditAspect() {}/*** 从ThreadLocal中获取审计对象,没有则创建一个*/private Audit getAudit() {Audit audit = THREAD_LOCAL.get();if (audit == null) {return new Audit();}return audit;}/*** 前置通知,收集操作相关信息封装为Audit对象并保存到ThreadLocal中*/@Before(value = "auditAspect()")public void doBefore(JoinPoint joinPoint) throws Throwable {HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();Audit audit = getAudit();audit.setTime(LocalDateTime.now());audit.setRequestIp(ServletUtil.getClientIP(request));// 操作员一般通过读取当前登录的管理员信息获取audit.setUserName("zhangsan");// 获取 @Audit 注解的信息com.hsgx.event.annotation.Audit ann = joinPoint.getTarget().getClass().getAnnotation(com.hsgx.event.annotation.Audit.class);if (ann != null) {audit.setDescription(ann.value());audit.setType(ann.type());}// 保存到线程容器THREAD_LOCAL.set(audit);}/*** 成功返回通知*/@AfterReturning(returning = "ret", pointcut = "auditAspect()")public void doAfterReturning(Object ret) {// 根据返回对象 ret 再做一些操作Audit audit = getAudit();audit.setDescription(audit.getDescription() + " 成功 ");// 发布事件applicationContext.publishEvent(new AuditEvent(audit));THREAD_LOCAL.remove();}/*** 异常返回通知*/@AfterThrowing(throwing = "e", pointcut = "auditAspect()")public void doAfterThrowable(Throwable e) {// 根据异常返回对象 e 再做一些操作Audit audit = getAudit();audit.setDescription(audit.getDescription() + " 失败 " + e.getMessage());// 发布事件applicationContext.publishEvent(new AuditEvent(audit));THREAD_LOCAL.remove();}
}
  • 10)在UserController中使用@Audit注解
// com.hsgx.event.controller.UserController@com.hsgx.event.annotation.Audit(type = "saveUser", value = "新增用户")
@PostMapping("/save")
public String saveUser(){return "新增用户成功";
}
  • 11)重启服务并调用/user/save请求

本节完,更多内容查阅:后台管理系统的通用权限解决方案

延伸阅读:后台管理系统的通用权限解决方案(六)SpringBoot整合Logback实现日志记录

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

视频设备一体化监控运维方案

随着平安城市、雪亮工程等项目建设的号召&#xff0c;视频监控系统的建设如火如荼地开展。无论在公共场所、企业单位、住宅小区、矿山工地还是交通枢纽&#xff0c;视频监控系统已成为保障安全、维护秩序和提升管理效率的重要工具。但由于对视频监控系统中的前端设备&#xff0…

二十八、Python基础语法(面向对象-下)

一、self 从函数的语法上来看, self 是形参 , 是一个普通的参数,那么在调用的时候,就需要传递实参值。从调用上看, 我们没有给 self 这个形参传递实参值, 但是 Python 解释器会自动的将调用这个方法的对象&#xff0c;作为实参值传递给 self。 class Dog:def eat(self):print…

【Leecode】Leecode刷题之路第37天之解数独

题目出处 37-解数独-题目出处 题目描述 个人解法 思路&#xff1a; todo代码示例&#xff1a;&#xff08;Java&#xff09; todo复杂度分析 todo官方解法 37-解数独-官方解法 方法1&#xff1a;回溯 思路&#xff1a; 代码示例&#xff1a;&#xff08;Java&#xff09; p…

【golang/navmesh】使用recast navigation进行寻路

目录 说在前面安装使用可视化 说在前面 go version&#xff1a;1.20.2 linux/amd64操作系统&#xff1a;wsl2detour-go版本&#xff1a;v0.2.0github&#xff1a;这里&#xff0c;求star! 安装 使用go mod安装即可go get github.com/o0olele/detour-go使用 使用场景模型构建n…

qt QFormLayout详解

QFormLayout 是 Qt 框架中用于创建表单布局的一个类&#xff0c;适合于将标签和输入控件整齐地排列在一起。它可以帮助开发者轻松构建用户输入界面&#xff0c;尤其是在处理表单时。 QFormLayout以两列的形式展示其子项&#xff0c;常用于创建“标签-字段”对的布局。其中&…

电脑小白必看|电脑安装常用软件简单小技巧

前言 最近同事换了新电脑&#xff0c;问我怎么下载常用软件&#xff1f; 我反问了一下&#xff1a;什么常用软件呢&#xff1f; 她说&#xff1a;微信、QQ、钉钉、酷狗、wps这种类型的软件。 哦豁&#xff0c;那其实很简单&#xff0c;但很多人还是没学会。小白之前分享过一…

RocketMQ 消息消费失败的处理机制

在分布式消息系统中&#xff0c;处理消费失败的消息是非常关键的一环。 RocketMQ 提供了一套完整的消息消费失败处理机制&#xff0c;下面我将简要介绍一下其处理逻辑。 截图代码版本&#xff1a;4.9.8 步骤1 当消息消费失败时&#xff0c;RocketMQ会发送一个code为36的请求到…

数据结构算法学习方法经验总结

DSA:Data Structures, Algorithms, and Problem-Solving Techniques 三大核心支柱 一次学习一个主题&#xff0c;按照如下顺序学习 如何开始学习新的主题 学习资源 https://www.youtube.com/playlist?listPLDN4rrl48XKpZkf03iYFl-O29szjTrs_O (Algorithms) https://ww…

Linux 操作系统的诞生与发展历程

目录 背景与起源 诞生过程 特点与影响 背景与起源 历史背景&#xff1a; 1980年代末至1990年代初&#xff0c;计算机操作系统市场主要由商业软件主导&#xff0c;如DOS、Windows以及Unix的各种版本。然而&#xff0c;这些系统往往价格昂贵&#xff0c;且源代码不开放&#…

第三届北京国际水利科技博览会将于25年3月在国家会议中心召开

由中国农业节水和农村供水技术协会、北京水利学会、振威国际会展集团等单位联合主办的第三届北京国际水利科技博览会暨供水技术与设备展&#xff08;北京水利展&#xff09;将于2025年3月31日至4月2日在北京•国家会议中心举办&#xff01; 博览会以“新制造、新服务、新业态”…

贪心算法习题其二【力扣】【算法学习day.19】

前言 ###我做这类文档一个重要的目的还是给正在学习的大家提供方向&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非常非常高滴&am…

Linux中NFS配置

文章目录 一、NFS介绍1.1、NFS的工作流程1.2、NFS主要涉及的软件包1.3、NFS的主要配置文件 二、安装NFS2.1、更新yum2.2、安装NFS服务2.3、配置NFS服务器2.4、启动NFS服务2.5、配置防火墙&#xff08;如果启用了防火墙&#xff0c;需要允许NFS相关的端口通过&#xff09;2.6、生…

Docker | 将本地项目发布到阿里云的实现流程

发布到阿里云 本地镜像发布到阿里云流程具体流程1. docker commit 生成新镜像文件2. 查看镜像3. 阿里云开发者平台选择控制台&#xff0c;进入容器镜像服务&#xff0c;选择个人实例创建命名空间仓库名称进入管理界面获得脚本推送到阿里云 补充&#xff1a; docker tag 命令基本…

Qt指定程序编译生成文件的位置

shadow build: [基础]Qt Creator 的 Shadow build(影子构建)-CSDN博客 影子构建&#xff1a;将源码路径和构建路径分开&#xff08;生成的makefile文件和其他产物都不放到源码路径&#xff09;&#xff0c;以此来保证源码路径的清洁。 实验1&#xff1a; 我创建了两个项目:…

嵌入式常用功能之通讯协议1--串口

嵌入式常用功能之通讯协议1--串口&#xff08;本文&#xff09; 嵌入式常用功能之通讯协议1--IIC 嵌入式常用功能之通讯协议1--SPI&#xff08;待定&#xff09; ...... 一、串口协议简介 1&#xff0c;简介 UART(异步串行通信)&#xff1a;时钟基准不是同一个&#xff08…

「Mac畅玩鸿蒙与硬件10」鸿蒙开发环境配置篇10 - 项目实战:计数器应用

本篇将通过一个简单的计数器应用,带你体验鸿蒙开发环境的实际操作流程。本项目主要练习组件的使用、事件响应和状态管理,帮助开发者熟悉基本的应用构建流程。 关键词 计数器应用组件操作事件响应状态管理HarmonyOS 应用开发一、创建计数器项目 1.1 在 DevEco Studio 中新建项…

Python | Leetcode Python题解之第513题找树左下角的值

题目&#xff1a; 题解&#xff1a; class Solution:def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:q deque([root])while q:node q.popleft()if node.right:q.append(node.right)if node.left:q.append(node.left)ans node.valreturn ans

操作系统实验记录

实验零:虚拟机安装 一、安装vmware虚拟机 与vmware匹配搜索结果 - 考拉软件 (rjctx.com),下载17.5.1版本即可下载后对照教程安装 二、下载iso虚拟驱动 搜索清华大学镜像网站,点击再搜ubuntu,下载这个4.1GB的iso文件安装后打开vmware虚拟机 三、配置vmware虚拟机 右键管…

适配器模式:类适配器与对象适配器

适配器模式是一种结构性设计模式&#xff0c;旨在将一个接口转换成客户端所期望的另一种接口。它通常用于解决由于接口不兼容而导致的类之间的通信问题。适配器模式主要有两种实现方式&#xff1a;类适配器和对象适配器。下面&#xff0c;我们将详细探讨这两种方式的优缺点及适…

Python毕业设计选题:基于Python的无人超市管理系统-flask+vue

开发语言&#xff1a;Python框架&#xff1a;flaskPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 系统首页 超市商品详情 购物车 我的订单 管理员登录界面 管理员功能界面 用户界面 员…