Springboot自定义注解实现操作日志管理

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
日志服务 SLS,月写入数据量 50GB 1个月
简介: Springboot自定义注解实现操作日志管理

操作日志的记录

为什么要有日志?

因为我们不光要记录代码的运行,如(logback log4j),而且还应该记录用户的行为,这叫做业务运行日志

例如:记录 zhangsan 在项目中 调用了哪个方法, 什么时间调用的 。访问的ip地址, 访问了哪些数据,做了什么操作,以此当程序出现问题的时候更利于我们进行错误的排查!

业务运行日志的作用

  1. 记录用户的行为 用于后续的分析
  2. 记录用户的所有的操作

业务运行日志最常用的使用场景:记录管理员所有的行为操作, 可以用于业务分析,事故恢复

日志实现的思路

1.我们需要记录哪些数据 存入数据库

这里列出一个我所用的表结构,如下所示:

字段 含义
log_id 主键
log_date 时间
log_content 操作内容 例如:查询全部菜单信息 添加用户数据
log_name_id 用户的id
log_ip 用户的ip地址
log_type 操作类型

2.在项目中什么位置记录

日志记录是一个数据库的添加操作 是一段代码

通常,我们在Controller方法进行后置增强

如下图所示,我们在需要记录操作的controller上使用aop配置一个切入点,以此来记录用户所进行的操作

3.如何实现记录功能

实现方式:AOP

4.Aop日志记录 具体代码实现

aop的使用流程,这里使用注解式aop来实现

具体步骤:

设置切入点

  1. 可以切在方法上
  2. 可以切在注解上
@Transactional 事务注解 注解加在类上 aop 切在注解上

写增强 日志记录增强

  1. 获取日志的相关信息
    用户的id ip地址, 时间, 操作的描述, 类型等信息
  2. 将日志对象 添加到数据库

增强方法中获取session

因为我们是通过aop来获取用户的请求的,所以就需要通过当前的请求拿到session,进而去获取用户的信息。

但是,操作的描述如何获取呢?

比如 执行的方法不同  描述是不一样的
login             管理员登录
selectAllMenu  查询了所有的菜单

解决方案:使用自定义注解:

  1. 在 目标 方法上添加自定义注解 (@Log) 如下
  2. 在增强中获取注解(@Log)的value 和 type

代码实现

自定义日志注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 元注解:加在自定义注解上的注解
 * @Target 定义注解可以添加的位置 METHOD 方法上 type 类上
 * @Retention RUNTIME 运行时  不管编译 还是 运行 这个注解都可以用
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAnnotation {
    /**
     * 写法类似于接口的方法 后面可以通过default 关键字给默认值
     * 用法类似于属性
     * @return
     */
    String value() default "";
    String type() default "";
}

这里要注意什么是元注解,和 注解属性的定义方式

2. 在目标方法上使用注解

3. 在增强方法中获取注解的value 和 type

        /**
         * 操作的描述
         *
         * 执行的方法不同  描述是不一样的
         * login         管理员登录
         * selectAllGuru 查询了所有的上师
         *
         * 获取注解的值
         */
//        1.通过连接点获取方法签名 被切入方法的所有信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//        2.获取被切入方法对象
        Method method = signature.getMethod();
//        3.获取方法上的注解
        LogAnnotation annotation = method.getAnnotation(LogAnnotation.class);
//        4.获取注解的值
        String value = annotation.value();

完整的aop的代码实现

package com.tourism.hu.config;
/**
 * @author 马超伟
 * @PROJECT_NAME: fzll
 * @Description:
 * @date 15:29
 * @Copyright: All rights Reserved, Designed By Huerdai  
 * Copyright:    Copyright(C) 2019-2020
 * Company       Huerdai Henan LTD.
 */
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tourism.hu.entity.CustomerInfo;
import com.tourism.hu.entity.CustomerLoginLog;
import com.tourism.hu.service.ICustomerInfoService;
import com.tourism.hu.service.ICustomerLoginLogService;
import com.tourism.hu.util.IpAddressUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
/*** @Aspect 标记当前类为功能增强类 切面类 *
 *  @Configuration 标记当前类为配置类 这个注解包含了@Component的功能
 */
@Aspect
@Configuration
public class LogAop {
    private  Logger logger = LoggerFactory.getLogger(getClass());
    @Resource
    private ICustomerInfoService iCustomerInfoService;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private ICustomerLoginLogService iCustomerLoginLogService;
    /**
     * JoinPoint 连接点 就是切入点 通过这个对象可以获取切入点的相关所有信息 例如:被切入的方法和注解
     *
     * @param joinPoint ** 切入点的设置 切注解 @annotation *
     */
    @After("@annotation(com.tourism.hu.config.Log)")
    public void logAfter(JoinPoint joinPoint) {
      //new 一个日志的实体,用来保存日志信息
        CustomerLoginLog loginLog = new CustomerLoginLog();
        // 1.获取日志相关的信息  用户的id session  ip  时间  操作的描述  类型  ctrl+H
        /**
         * 获取用户id
         * 为什么不能装配session?因为服务器有多个session
         * 通过 ServletRequestAttributes 可以获取当前请求
         * 当前请求可以获取当前会话的session
         */
         //获取用户的请求
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        //得到session
        HttpSession session = request.getSession();
        String sessionid = session.getId();
        //通过sessionid去获取用户信息
        Object obj = redisTemplate.opsForValue().get(sessionid);
        String customerId = "";
        if(obj!=null) {
            customerId=obj.toString();
        }
        //拿到用户对象
        CustomerInfo customerInfo = iCustomerInfoService.getOne(new QueryWrapper<CustomerInfo>().eq("id", customerId));
        if (customerInfo!=null){
          //将用户的id 存入到日志实体中
            loginLog.setCustomerId(customerInfo.getCustomerId());
        }
         loginLog.setLoginTime(LocalDateTime.now());
        /**
         * 获取用户的ip
         * 通过工具类 ip
         */
        loginLog.setLoginIp(IpAddressUtil.getIp());
        /**
         * 操作的描述
         * 执行的方法不同  描述是不一样的
         * login         管理员登录
         * 获取注解的值
         */
//        1.通过连接点获取方法签名 被切入方法的所有信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//        2.获取被切入方法对象
        Method method = signature.getMethod();
//        3.获取方法上的注解
        Log annotation = method.getAnnotation(Log.class);
//        4.获取注解的值
        String value = annotation.value();
        loginLog.setLogContent(value);
        // 获取注解的类型
        String type = annotation.type();
        if (type!=null){
            loginLog.setLoginType(type);
        }
//        2.将日志对象 添加到数据库
        System.out.println(loginLog);
        logger.debug("loginLog===="+loginLog);
        boolean save = iCustomerLoginLogService.save(loginLog);
        logger.debug("保存日志------"+save);
    }
}

所用到的工具类

获取ip地址的工具类IpAddressUtil

  public static String getIp() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String ip = "null";
        try {
            ip = request.getHeader("x-forwarded-for");
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_CLIENT_IP");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
        } catch (Exception e) {
            logger.error("IPUtils ERROR ", e);
        }
        //使用代理,则获取第一个IP地址
        if(StringUtils.isNotEmpty(ip) && ip.length() > 15) {
          if(ip.indexOf(",") > 0) {
              ip = ip.substring(0, ip.indexOf(","));
          }
      }
        return ip;
    }
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
16天前
|
Java Spring
在使用Spring的`@Value`注解注入属性值时,有一些特殊字符需要注意
【10月更文挑战第9天】在使用Spring的`@Value`注解注入属性值时,需注意一些特殊字符的正确处理方法,包括空格、引号、反斜杠、新行、制表符、逗号、大括号、$、百分号及其他特殊字符。通过适当包裹或转义,确保这些字符能被正确解析和注入。
|
5天前
|
XML JSON Java
SpringBoot必须掌握的常用注解!
SpringBoot必须掌握的常用注解!
23 4
SpringBoot必须掌握的常用注解!
|
7天前
|
存储 缓存 Java
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
40 2
|
7天前
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
25 1
|
21天前
|
架构师 Java 开发者
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
|
2天前
|
存储 安全 Java
springboot当中ConfigurationProperties注解作用跟数据库存入有啥区别
`@ConfigurationProperties`注解和数据库存储配置信息各有优劣,适用于不同的应用场景。`@ConfigurationProperties`提供了类型安全和模块化的配置管理方式,适合静态和简单配置。而数据库存储配置信息提供了动态更新和集中管理的能力,适合需要频繁变化和集中管理的配置需求。在实际项目中,可以根据具体需求选择合适的配置管理方式,或者结合使用这两种方式,实现灵活高效的配置管理。
6 0
|
14天前
|
存储 Java 数据管理
强大!用 @Audited 注解增强 Spring Boot 应用,打造健壮的数据审计功能
本文深入介绍了如何在Spring Boot应用中使用`@Audited`注解和`spring-data-envers`实现数据审计功能,涵盖从添加依赖、配置实体类到查询审计数据的具体步骤,助力开发人员构建更加透明、合规的应用系统。
|
2天前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
64 30
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
|
28天前
|
XML JSON Java
Logback 与 log4j2 性能对比:谁才是日志框架的性能王者?
【10月更文挑战第5天】在Java开发中,日志框架是不可或缺的工具,它们帮助我们记录系统运行时的信息、警告和错误,对于开发人员来说至关重要。在众多日志框架中,Logback和log4j2以其卓越的性能和丰富的功能脱颖而出,成为开发者们的首选。本文将深入探讨Logback与log4j2在性能方面的对比,通过详细的分析和实例,帮助大家理解两者之间的性能差异,以便在实际项目中做出更明智的选择。
176 3
|
29天前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1607 14