springboot编写日志环境搭建过程

AOP记录日志

AOP记录日志的主要优点包括:

1、低侵入性:AOP记录日志不需要修改原有的业务逻辑代码,只需要新增一个切面即可。

2、统一管理:通过AOP记录日志可以将各个模块中需要记录日志的部分进行统一管理,降低了代码重复度,提高了代码可维护性和可扩展性。

3、提升效率:通过引入AOP记录日志,可以避免手动编写日志记录代码,减少了开发人员的工作量,提升了开发效率。

4、安全性:通过AOP记录日志,可以收集系统的操作日志,帮助管理员及时发现问题并进行调整,从而提高系统的安全性。

AOP记录日志的整体思想

1、基于自定义注解来确定切入点【优势:可以通过自定义注解携带一些变化的参数,比如模块名称】

2、基于环绕通知来完成日志记录

搭建之前,要明白AOP主要核心术语

下面:

切面类环境搭建

1.在common模块下创建一个独立的记录日志的模块【common-log】

在该模块下加入如下的依赖

2.自定义Log注解,如下所示:

代码如下:

import com.atguigu.spzx.common.log.enums.OperatorType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** // 自定义操作日志记录注解*/
@SuppressWarnings("all")
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {// 模块名称public String title() ;// 操作人类别public OperatorType operatorType() default OperatorType.MANAGE;// 业务类型(0其它 1新增 2修改 3删除)public int businessType() ;//是否保存请求的参数public boolean isSaveRequestData() default true;// 是否保存响应的参数public boolean isSaveResponseData() default true;
}

//操作人类别

OperatorType定义

public enum OperatorType {        // 操作人类别
    OTHER,        // 其他
    MANAGE,        // 后台用户
    MOBILE        // 手机端用户
}

3.定义一个切面类,并且在该切面类中提供一个环绕通知方法

LogAspect

/*** 这个方法作为环绕通知,环绕通知方法中两个参数,ProceedingJoinPoint和Log* 1.ProceedingJoinPoint 表示能调用我们业务方法并且可以得到相关的参数等信息* 2.Log 方法上加了public @interface Log这个注解,* 加上@Around(value = "@annotation(sysLog)"),方法执行时就会执行环绕通知,就可以完成机制* 实现当方法上加上注解之后,就会实行环绕通知*/

@Aspect
@Component
@Slf4j// 环绕通知切面类定义
public class LogAspect {            

    @Around(value = "@annotation(sysLog)")
    public Object doAroundAdvice(ProceedingJoinPoint joinPoint , Log sysLog) {
    
        Object proceed = null;
        try {

                  // 执行业务方法
            proceed = joinPoint.proceed();            
        } catch (Throwable e) {          

                // 代码执行进入到catch中,业务方法执行产生异常              
            throw new RuntimeException(e);
        }

          // 返回执行结果
        return proceed ;                              
    }
}

4.想让LogAspect这个切面类在其他的业务服务中进行使用,那么就需要该切面类纳入到Spring容器中。Spring Boot默认会扫描和启动类所在包相同包中的bean以及子包中的bean。而LogAspect切面类不满足扫描条件,因此无法直接在业务服务中进行使用。那么此时可以通过自定义注解进行实现

代码如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import(value = LogAspect.class)      

 // 通过Import注解导入日志切面类到Spring容器中    
public @interface EnableLogAspect {
    
}

5.在启动类pom.xml 引入依赖

<dependency><groupId>org.liuliu</groupId><artifactId>common-log</artifactId><version>1.0-SNAPSHOT</version>
</dependency>

6.在ManagerApplication服务的启动类上添加@EnableLogAspect注解

7.创建Log工具类,代码如下

/*** 自定义注解搞完,切面环绕方法类方法创建完成,在manager引入common包依赖,在启动类添加注解@EnableLogAspect* 然后封装一个logutil工具类备用(切面环绕方法类执行时调用工具类)*/
import com.alibaba.fastjson.JSON;
import com.atguigu.spzx.common.log.annotation.Log;
import com.atguigu.spzx.model.entity.system.SysOperLog;
import com.atguigu.spzx.utils.AuthContextUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.http.HttpMethod;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.lang.reflect.Method;
import java.util.Arrays;
public class LogUtil {//操作执行之后调用public static void afterHandlLog(Log sysLog, Object proceed,SysOperLog sysOperLog, int status ,String errorMsg) {if(sysLog.isSaveResponseData()) {sysOperLog.setJsonResult(JSON.toJSONString(proceed));}sysOperLog.setStatus(status);sysOperLog.setErrorMsg(errorMsg);}//操作执行之前调用public static void beforeHandleLog(Log sysLog,ProceedingJoinPoint joinPoint,SysOperLog sysOperLog) {// 设置操作模块名称sysOperLog.setTitle(sysLog.title());sysOperLog.setOperatorType(sysLog.operatorType().name());// 获取目标方法信息MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature() ;Method method = methodSignature.getMethod();sysOperLog.setMethod(method.getDeclaringClass().getName());// 获取请求相关参数ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();HttpServletRequest request = requestAttributes.getRequest();sysOperLog.setRequestMethod(request.getMethod());sysOperLog.setOperUrl(request.getRequestURI());sysOperLog.setOperIp(request.getRemoteAddr());// 设置请求参数if(sysLog.isSaveRequestData()) {String requestMethod = sysOperLog.getRequestMethod();if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {String params = Arrays.toString(joinPoint.getArgs());sysOperLog.setOperParam(params);}}sysOperLog.setOperName(AuthContextUtil.get().getUserName());}
}

8.在common-log模块中定义保存日志数据的service接口,然后在具体的业务服务中给出实现。分析图如下:

1)common-log 模块下创建service包

上代码:

import com.atguigu.spzx.model.entity.system.SysOperLog;/*** // 异步操作日志记录服务接口*/
public interface AsyncOperLogService {}

2)启动类项目中的serviceImpl包中实现日志记录服务接口,创建Mapper和mapper.xml

(创建过程自动脑补,继续实现项目),都创建完成,接下来去保存日志操作:

        2.1)修改切面类,调用封装工具

        import com.atguigu.spzx.common.log.annotation.Log;
import com.atguigu.spzx.common.log.service.AsyncOperLogService;
import com.atguigu.spzx.common.log.utils.LogUtil;
import com.atguigu.spzx.model.entity.system.SysOperLog;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;/*** 定义一个切面类,并且在该切面类中提供一个环绕通知方法* // 环绕通知切面类定义*/@Aspect
@Component
@Slf4jpublic class LogAspect { /**    * 封装工具类Logutil 后,日志切面类代码修改 调用工具类实现环绕方法,标红代码,首先引入        AsyncOperLogService*/@Autowiredprivate AsyncOperLogService asyncOperLogService ;
@Around(value = "@annotation(sysLog)")public Object doAroundAdvice(ProceedingJoinPoint joinPoint, Log sysLog){ // 构建前置参数SysOperLog sysOperLog = new SysOperLog() ;LogUtil.beforeHandleLog(sysLog , joinPoint , sysOperLog) ;Object proceed=null;try {// 执行业务方法proceed = joinPoint.proceed(); // 构建响应结果参数 LogUtil.afterHandlLog(sysLog , proceed, sysOperLog,0,null);} catch (Throwable e) { e.printStackTrace();LogUtil.afterHandlLog(sysLog , proceed, sysOperLog,1, e.getMessage());// 代码执行进入到catch中,业务方法执行产生异常throw new RuntimeException();}
// 保存日志数据asyncOperLogService.saveSysOperLog(sysOperLog);// 返回执行结果return proceed;}
}

        2.2)执行上述切面类保存日志方法:

// 保存日志数据 asyncOperLogService.saveSysOperLog(sysOperLog);

//service接口

import com.atguigu.spzx.model.entity.system.SysOperLog;/*** // 异步操作日志记录服务接口*/
public interface AsyncOperLogService {保存日志数据public abstract void saveSysOperLog(SysOperLog sysOperLog) ;
}

//service实现类

import com.atguigu.spzx.common.log.service.AsyncOperLogService;
import com.atguigu.spzx.manager.mapper.SysOperLogMapper;
import com.atguigu.spzx.model.entity.system.SysOperLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class AsyncOperLogServiceImpl implements AsyncOperLogService {@Autowiredprivate SysOperLogMapper sysOperLogMapper;@Overridepublic void saveSysOperLog(SysOperLog sysOperLog) { // 异步执行保存日志操作sysOperLogMapper.insert(sysOperLog);}
}

//mapper接口

import com.atguigu.spzx.model.entity.system.SysOperLog;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface SysOperLogMapper {//保存日志操作void insert(SysOperLog sysOperLog);
}

//mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.spzx.manager.mapper.SysOperLogMapper"><sql id="columns">id,title,method,request_method,operator_type,oper_name,oper_url,oper_ip,oper_param,json_result,status,error_msg,create_time,update_time,is_deleted</sql><insert id="insert">insert into sys_oper_log (id,title,method,request_method,operator_type,oper_name,oper_url,oper_ip,oper_param,json_result,status,error_msg) values (#{id},#{title},#{method},#{requestMethod},#{operatorType},#{operName},#{operUrl},#{operIp},#{operParam},#{jsonResult},#{status},#{errorMsg})</insert>
</mapper>

        2.3)一切准备就绪,在需要添加操作日志的接口方法上添加@Log注解进行测试。(添加红色表示框内容),启动器启动

//查询列表
@Log(title = "品牌管理",businessType = 0,operatorType = OperatorType.OTHER)
@GetMapping("/{page}/{limit}")
public Result<PageInfo<Brand>> list(@PathVariable(value = "page") Integer page, @PathVariable(value = "limit") Integer limit){PageInfo<Brand> brandList=brandService.findByPage(page, limit);return Result.build(brandList, ResultCodeEnum.SUCCESS);
}

结果:

创作不易,谢谢大家

补充:附上日志数据表结构,方便理解

CREATE TABLE `sys_oper_log` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志主键',
  `title` varchar(50) DEFAULT '' COMMENT '模块标题',
  `business_type` varchar(20) DEFAULT '0' COMMENT '业务类型(0其它 1新增 2修改 3删除)',
  `method` varchar(100) DEFAULT '' COMMENT '方法名称',
  `request_method` varchar(10) DEFAULT '' COMMENT '请求方式',
  `operator_type` varchar(20) DEFAULT '0' COMMENT '操作类别(0其它 1后台用户 2手机端用户)',
  `oper_name` varchar(50) DEFAULT '' COMMENT '操作人员',
  `dept_name` varchar(50) DEFAULT '' COMMENT '部门名称',
  `oper_url` varchar(255) DEFAULT '' COMMENT '请求URL',
  `oper_ip` varchar(128) DEFAULT '' COMMENT '主机地址',
  `oper_param` varchar(2000) DEFAULT '' COMMENT '请求参数',
  `json_result` varchar(2000) DEFAULT '' COMMENT '返回参数',
  `status` int DEFAULT '0' COMMENT '操作状态(0正常 1异常)',
  `error_msg` varchar(2000) DEFAULT '' COMMENT '错误消息',
  `oper_time` datetime DEFAULT NULL COMMENT '操作时间',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `update_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '删除标记(0:不可用 1:可用)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=67 DEFAULT CHARSET=utf8mb3 COMMENT='操作日志记录';

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

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

相关文章

神经网络的工程基础(二)——随机梯度下降法|文末送书

相关说明 这篇文章的大部分内容参考自我的新书《解构大语言模型&#xff1a;从线性回归到通用人工智能》&#xff0c;欢迎有兴趣的读者多多支持。 本文涉及到的代码链接如下&#xff1a;regression2chatgpt/ch06_optimizer/stochastic_gradient_descent.ipynb 本文将讨论利用…

WinApp自动化测试之辅助工具介绍

前篇文章中&#xff0c;我们简单介绍了部分WinApp自动化测试脚本常规操作&#xff0c;今天我们来讲剩余的部分。 文件批量上传 文件批量上传和文件单个上传原理是相同的&#xff0c;单个上传直接传入文件路径即可&#xff0c;批量上传需要进入批量上传的文件所在目录&#xf…

uniapp创建支付密码实现(初始密码,第二次密码)

示例&#xff1a; 插件地址&#xff1a;自定义数字/身份证/密码输入框&#xff0c;键盘密码框可分离使 - DCloud 插件市场 1.下载插件并导入HBuilderX&#xff0c;找到文件夹&#xff0c;copy number-keyboard.vue一份为number-keyboard2.vue&#xff08;number-keyboard.vue是…

C++ STL map容器erase操作避坑

map容器的erase方法有三种重载形式&#xff1a; //1.删除迭代器所指向的元素 //返回值是指向下一个节点的迭代器 iterator erase(iterator it); //2.区间删除 iterator erase(iterator first, iterator last); //3.根据键值删除 //返回值为删除的元素个数 size_type erase(con…

民国漫画杂志《时代漫画》第37期.PDF

时代漫画37.PDF: https://url03.ctfile.com/f/1779803-1248636302-c017ee?p9586 (访问密码: 9586) 《时代漫画》的杂志在1934年诞生了&#xff0c;截止1937年6月战争来临被迫停刊共发行了39期。 ps: 资源来源网络!

C++基础编程100题-002 OpenJudge-1.1-04 输出保留3位小数的浮点数

更多资源请关注纽扣编程微信公众号 002 OpenJudge-1.1-04 输出保留3位小数的浮点数 http://noi.openjudge.cn/ch0101/04/ 描述 读入一个单精度浮点数&#xff0c;保留3位小数输出这个浮点数。 输入 只有一行&#xff0c;一个单精度浮点数。 输出 也只有一行&#xff0c;…

07.爬虫---使用session发送请求

07.使用session发送请求 1.目标网站2.代码实现 1.目标网站 我们以这个网站作为目标网站 http://www.360doc.com/ 注册用户 注册后从登录界面获取到这些信息 2.代码实现 import requestssession requests.Session() url http://www.360doc.com/ajax/login/login.ashx u…

深入剖析Java线程池的核心概念与源码解析:从Executors、Executor、execute逐一揭秘

文章目录 文章导图前言Executors、Executor、execute对比剖析Executors生成的线程池&#xff1f;线程池中的 execute 方法execute 方法的作用execute的工作原理拒绝策略 源码分析工作原理基本知识线程的状态线程池的状态线程池状态和线程状态总结线程池的状态信息和线程数量信息…

RedisSearch与Elasticsearch:技术对比与选择指南

码到三十五 &#xff1a; 个人主页 数据时代&#xff0c;全文搜索已经成为许多应用程序中不可或缺的一部分。RedisSearch和Elasticsearch是两个流行的搜索解决方案&#xff0c;它们各自具有独特的特点和优势。本文简单探讨一些RedisSearch和Elasticsearch之间的技术差异。 目录…

9款实用而不为人知的小众软件推荐!

AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频https://aitools.jurilu.com/ 在电脑软件的浩瀚海洋中&#xff0c;除了那些广为人知的流行软件外&#xff0c;还有许多简单、干净、功能强大且注重实用功能的小众软件等待我们…

[NISACTF 2022]sign_crypto(LATEX)

题目&#xff1a; 我们看出这是LATEX编码&#xff0c;破解之后&#xff1a; 看出每个“\”之后的第一个字母连起来即使&#xff1a;nss....&#xff0c;在大写即可得到flag。

Sui Nami Bags对NFT使用案例进行创新

在四月的Sui Basecamp活动中&#xff0c;与会者体验了一系列Sui技术&#xff0c;这些技术以Nami Bags的形式呈现&#xff0c;这些数字礼包里满是来自Sui生态的NFT和优惠券。通过Enoki&#xff08;Mysten Labs的新客户参与平台&#xff09;提供支持&#xff0c;即使没有加密钱包…

OpenCV学习 基础图像操作(十七):泛洪与分水岭算法

原理 泛洪填充算法和分水岭算法是图像处理中的两种重要算法&#xff0c;主要用于区域分割&#xff0c;但它们的原理和应用场景有所不同&#xff0c;但是他们的基础思想都是基于区域迭代实现的区域之间的划分。 泛洪算法 泛洪填充算法&#xff08;Flood Fill&#xff09;是一…

修改element-ui el-radio颜色

修改element-ui el-radio颜色 需求效果图代码实现 小结 需求 撤销扣分是绿色&#xff0c;驳回是红色 效果图 代码实现 dom <el-table-columnlabel"操作"width"200px"><template v-slot"scope"><el-radio-group v-model"s…

Vue插槽与作用域插槽

title: Vue插槽与作用域插槽 date: 2024/6/1 下午9:07:52 updated: 2024/6/1 下午9:07:52 categories: 前端开发 tags:VueSlotScopeSlot组件通信Vue2/3插槽作用域API动态插槽插槽优化 第1章&#xff1a;插槽的概念与原理 插槽的定义 在Vue.js中&#xff0c;插槽&#xff08;…

c++(七)

c&#xff08;七&#xff09; 内联函数内联函数的特点为什么要有内联函数内联函数是如何工作的呢 类型转换异常处理智能指针单例模式懒汉模式饿汉模式 VS中数据库的相关配置 内联函数 修饰类的成员函数&#xff0c;关键字&#xff1a;inline inline 返回值类型 函数名(参数列…

vue-el-steps 使用2[代码示例]

效果图 代码 element代码 <template> <div class"app-container"> <el-form :model"queryForm" size"small" :inline"true"> <el-form-item label"内容状态"> <el-button-group> <el-bu…

as keyof GlobalStore

解释 as keyof GlobalStore 在 TypeScript 中&#xff0c;as keyof GlobalStore 是一种类型断言语法。它告诉 TypeScript&#xff0c;返回的值是一个特定类型的值&#xff0c;这里是 GlobalStore 类型的键。这在编译时有助于确保类型安全。 关键点&#xff1a; 类型断言&…

构建智慧银行保险系统的先进技术架构

随着科技的不断发展&#xff0c;智慧银行保险系统正日益受到关注。在这个数字化时代&#xff0c;构建一个先进的技术架构对于智慧银行保险系统至关重要。本文将探讨如何构建智慧银行保险系统的先进技术架构&#xff0c;以提升服务效率、降低风险并满足客户需求。 ### 1. 智慧银…

qwen-moe

一、定义 qwen-moe 代码讲解&#xff0c; 代码qwen-moe与Mixtral-moe 一样&#xff0c; 专家模块qwen-moe 开源教程Mixture of Experts (MoE) 模型在Transformer结构中如何实现&#xff0c;Gate的实现一般采用什么函数&#xff1f; Sparse MoE的优势有哪些&#xff1f;MoE是如…