实现用户操作日志记录

news2025/7/19 16:52:19

Java记录操作日志

java自带的日志框架是java.util.logging(JUL),从JDK1.4(2002)开始捆绑在JDK中。可以使用JUL来记录操作日志。以下是使用JUL记录事务的示例:

// java.util.logging
java.util.logging.Logger logger = java.util.logging.Logger.getLogger (this.getClass ().getName ());
logger.info ("This is an info message");
logger.severe ("This is an error message"); // == ERROR

操作日志和系统日志的区别

系统日志:统日志主要是为开发排查问题提供依据,一般打印在日志文件中;系统日志的可读性要求没那么高,日志中会包含代码的信息,比如在某个类的某一行打印了一个日志。
操作日志:主要是对某个对象进行新增操作或者修改操作后记录下这个新增或者修改,操作日志要求可读性比较强,因为它主要是给用户看的,比如订单的物流信息,用户需要知道在什么时间发生了什么事情。

实现操作日志共能一般有两种方法

  1. 第一种就是很传统的做法,就是在每个模块进行插入日志的操作,这种方法虽然实现巨鹿用户操作,但是很繁琐,基本是重复的工作。
  2. 第二种就是使用spring的AOP来实现记录用户操作,也是现在最流行的写法。它的优势在于这种记录用户操作的代码独立于其他业务逻辑代码,不仅实现了解耦,而且避免了冗余代码。

操作日志表

这里我就简单记录一下基本的信息。
在这里插入图片描述

需要的一些工具类

获取Ip地址

package com.kl.util;

import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * @author Wen先森
 * @version 1.0
 * @date 2023/2/28 10:25
 */
public class IpUtils {

    /**
     * 获取Ip地址
     * @return
     */
    public static String getIpAddress() {
        HttpServletRequest request = null;

        try {
            RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
            request = ((ServletRequestAttributes)requestAttributes).getRequest();
        } catch (Exception var2) {
            return null;
        }

        return getIpAddr(request);
    }

    /**
     * 获取Ip地址
     * @param request
     * @return
     */
    public static String getIpAddr(HttpServletRequest request)
    {
        if (request == null)
        {
            return "unknown";
        }
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
        {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
        {
            ip = request.getHeader("X-Forwarded-For");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
        {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
        {
            ip = request.getHeader("X-Real-IP");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
        {
            ip = request.getRemoteAddr();
        }

        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
    }

获取浏览器和操作系统

		<!-- 解析客户端操作系统、浏览器等 -->
		<dependency>
			<groupId>eu.bitwalker</groupId>
			<artifactId>UserAgentUtils</artifactId>
			<version>1.21</version>
		</dependency>
        UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
        // 获取客户端操作系统
        String os = userAgent.getOperatingSystem().getName();
        // 获取客户端浏览器
        String browser = userAgent.getBrowser().getName();

第一种:传统方法

  1. 新增日志实体类、dao层 接口
  2. 这里我是将添加日志编写到工具类中
package com.kl.util;

import com.kl.entity.IotMSUser;
import com.kl.entity.OprLog;
import com.kl.repo.OprLogRepo;
import eu.bitwalker.useragentutils.UserAgent;
import org.springframework.util.ObjectUtils;

/**
 * @author Wen先森
 * @version 1.0
 * @date 2023/2/27 19:48
 */
public class LogUtils {
    /**
     * 添加操作日志
     * @param content
     */
    public static void insertLog (String content) {
        UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
        // 获取客户端操作系统
        String os = userAgent.getOperatingSystem().getName();
        // 获取客户端浏览器
        String browser = userAgent.getBrowser().getName();
        // 获取用户信息,可以从自己的项目中token中去取,这里就不再描写
        Integer userId=1;
        // 获取Ip地址
        String ipAddress = IpUtils.getIpAddress();
        OprLog oprLog = new OprLog();
        oprLog.setIp(ipAddress);
        oprLog.setUserAgent(browser);
        oprLog.setUserId(userId);
        oprLog.setContent(content);
        oprLog.setCreateTime(FormatUtil.formatDate());
        // 这里我才用的是JPA,大家也可以换成自己熟悉的框架去添加
        OprLogRepo oprLogRepo = SpringUtils.getBean(OprLogRepo.class);
        oprLogRepo.save(oprLog);
    }
}
  1. 在controller层的某一个方法调用该工具类记录操作日志。
    在这里插入图片描述

第二种:AOP实现记录用户操作

  1. 在pom.xml中添加AOP依赖
		<!-- aop -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
  1. 自定义操作日志注解
package com.kl.interceptor.aspectj;

import java.lang.annotation.*;

/**
 * @author Wen先森
 * @version 1.0
 * @date 2023/3/6 16:03
 * @description: 自定义操作日志注解
 */
@Target(ElementType.METHOD)//注解放置的目标位置即方法级别
@Retention(RetentionPolicy.RUNTIME)//注解在哪个阶段执行
@Documented
public @interface OprLog {
    /**
     * 操作内容
     */
    public String content() default "";
}

  1. 自定义操作日志切面类,该类是将操作日志保存到数据库
package com.kl.interceptor.aspectj;

import com.kl.entity.IotMSUser;
import com.kl.repo.OprLogRepo;
import com.kl.util.*;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.sql.Timestamp;

/**
 * @author Wen先森
 * @date 2023/3/6 16:18
 * @version 1.0
 * @description: 操作日志切面处理类
 */
@Slf4j
@Aspect
@Component
public class OperationLogAspect {

    /**
     * 设置操作日志切入点   在注解的位置切入代码
     */
    @Pointcut("@annotation(com.kl.interceptor.aspectj.OprLog)")
    public void oprLogPointCut() {
    }


    /**
     * 记录操作日志
     * @param joinPoint 方法的执行点
     * @param result  方法返回值
     * @throws Throwable
     */
    
    @AfterReturning(returning  = "result", value = "oprLogPointCut()")
    public void saveOperLog(JoinPoint joinPoint, Object result){
        try{
            // 获得注解
            OprLog controllerLog = getAnnotationLog(joinPoint);
            if (controllerLog == null)
            {
                return;
            }
            //将返回值转换成map集合
            Map<String, String> map = (Map<String, String>) result;
            // 返回值信息(根据需求决定是否记录)
            String msg = MapUtils.getString(map, "msg");
            UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
            // 获取客户端操作系统
            String os = userAgent.getOperatingSystem().getName();
            // 获取客户端浏览器
            String browser = userAgent.getBrowser().getName();
            // 获取用户信息
            IotMSUser user = TokenUtil.getUserId();
            // 用户ID,"-1" 代表没有登陆系统用户
            Integer userId=-1;
            if (!ObjectUtils.isEmpty(user)){
                userId=user.getId();
            }
            // 获取Ip地址
            String ipAddress = IpUtils.getIpAddress();
            com.kl.entity.OprLog oprLog = new com.kl.entity.OprLog();
            oprLog.setIp(ipAddress);
            oprLog.setUserAgent(browser);
            oprLog.setUserId(userId);
            oprLog.setContent(controllerLog.content());
            oprLog.setCreateTime(FormatUtil.formatDate());
            OprLogRepo oprLogRepo = SpringUtils.getBean(OprLogRepo.class);
            oprLogRepo.save(oprLog);
        }catch (Exception e){
            // 记录本地异常日志
            log.error("==前置通知异常==");
            log.error("异常信息:{}", e.getMessage());
        }
    }

    /**
     * 是否存在注解,如果存在就获取
     */
    private OprLog getAnnotationLog(JoinPoint joinPoint) throws Exception
    {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        if (method != null)
        {
            return method.getAnnotation(OprLog.class);
        }
        return null;
    }

}
  1. 在controller层的某一个方法加入@OprLog注解
    在这里插入图片描述

最终结果

在这里插入图片描述

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

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

相关文章

网分线缆测试和dc-block

今天的好苹果和坏苹果 好苹果&#xff1a;是校准件和网分都是好的&#xff0c;又给了我一次复盘的机会 网分测试线缆&#xff1a; 1.网分直接复位&#xff0c;如果网分复位是校准状态&#xff0c;且解的是精密转接头&#xff0c;BNC的&#xff0c;可以不校准&#xff0c;结果差…

【高中数学教资】教案设计通用模板

前言 本文针对的是高中数学教师资格证笔试中最后的大题——教案设计&#xff08;含设计意图&#xff0c;文末有2022下半年高中数学教资教案设计大题&#xff09;。并附上高中数学404教资考点大纲&#xff0c;还有在学习中发现的一些可以免费学习网站推荐。 一、高中数学404考…

List系列集合

一. List集合特点、特有API List的实现类的底层原理 ArrayList底层是基于数组实现的&#xff1a;根据索引定位元素快&#xff0c;增删相对慢。LinkedList底层基于双链表实现的&#xff1a;查询元素慢&#xff0c;增删首尾元素是非常快的。public class ListDemo01 {public sta…

SerDes---CDR技术

1、为什么需要CDR 时钟数据恢复主要完成两个工作&#xff0c;一个是时钟恢复&#xff0c;一个是数据重定时&#xff0c;也就是数据的恢复。时钟恢复主要是从接收到的 NRZ&#xff08;非归零码&#xff09;码中将嵌入在数据中的时钟信息提取出来。 2、CDR种类 PLL-Based CDROve…

【信号与系统笔记】第一章 绪论

1.1信号传输系统 信息传输的任务 将带有信息的信号&#xff0c;通过某种系统由发送者传送给接收者。 通信系统的组成 转换器&#xff1a;把消息转换为电信号或者把电信号还原成消息信道&#xff1a;信号传输的通道&#xff0c;广义上来说。发射机和接收机也可以是信道的一部分…

【RabbitMQ】Producer之publisher confirm、transaction - 基于AMQP 0-9-1(二)

上篇文章主要介绍Producer的mandatory参数&#xff0c;备份队列和TTL的内容&#xff0c;这篇文章讲继续介绍Producer端的开发&#xff0c;主要包括发布方确认和事务机制。 发布方确认 消息持久化机制可以保证应服务器出现异常导致消息丢失的问题&#xff0c;但是Producer将消…

线程池ThreadPoolExecutor,从0到0.6

ThreadPoolExecutor是JDK提供的在java.util.concurrent包中的一个用于创建线程池的工具类。 一、ThreadPoolExecutor的7个参数 corePoolSize&#xff1a;核心线程数&#xff0c;线程池中保留的最小的线程数量&#xff0c;即使它们是空闲的也不会被销毁&#xff0c;除非allowCor…

Modbus转profinet网关连接1200PLC在博图组态与驱动器通讯程序案例

本案例给大家介绍由兴达易控modbus转profinet网关连接1200PLC在博图软件无需编程&#xff0c;实现1200Profinet转modbus与驱动器通讯的程序案例 硬件连接&#xff1a;1200PLC一台&#xff1b;英威腾DA180系列驱动器一台&#xff1b;兴达易控modbus转profinet网关一台 下面就是…

【Git】拉取 Pull Requests 测试的两种方法

文章目录前言参考目录方法说明方法一&#xff1a;直接拉取方法二&#xff1a;使用 diff 文件2.1、保存 diff 文件2.2、新建分支并执行文件前言 最近有参与到框架帮忙进行简单的 Pull Requests&#xff08;以下简称 PR&#xff09; 测试&#xff0c;因为也是第一次接触到这种操…

代码随想录 动态规划||01背包理论 416

Day3601背包理论基础01背包有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品只能用一次&#xff0c;求解将哪些物品装入背包里物品价值总和最大。暴力的解法是指数级别的时间复杂度。进而才需要动态规划的解法来…

Java学习笔记 --- Servlet(1)

一、Servlet技术 1、Servlet基本介绍 1、Servlet 是 JavaEE 规范之一。规范就是接口 2、Servlet 就 JavaWeb 三大组件之一。三大组件分别是&#xff1a;Servlet 程序、Filter 过滤器、Listener 监听器。 3、Servlet 是运行在服务器上的一个 java 小程序&#xff0c;它可以…

院士交锋,专家论道|NLP大模型技术与应用十大挑战,剑指AI未来

2023年2月24日下午&#xff0c;第四届OpenI/O启智开发者大会NLP大模型分论坛在深圳人才研修院隆重举办。NLP大模型论坛会议现场众多NLP领域顶级专家学者与多家国产NLP大模型开发团队汇聚一堂&#xff0c;学术界与产业界破圈交流&#xff0c;激荡尖端思想、分享前沿动态&#xf…

Linux学习第二十二节-网卡IP设置

1.修改网卡IP地址 方式一&#xff1a;通过修改网卡配置文件修改 网卡配置文件位置&#xff1a; /etc/sysconfig/network-scripts/网卡名 #ifconfig 表示用于显示和设置网卡的参数 #ip addr 表示用于显示和设置网卡的参数 #systemctl restart network 表示重启网络 …

Spark Join大小表

Spark Join大小表无法广播过滤后大小表数据分布均匀大小表 : 大小表尺寸相差 3 倍以上 Join 优先考虑 BHJ小表的数据量 > 广播阈值时&#xff0c;优先考虑 SHJ 无法广播 大表 100GB、小表 10GB&#xff0c;都远超广播变量阈值 当小表的尺寸 > 8GB时&#xff0c;创建广…

剑指-Offer-30-包含min函数的栈

剑指 Offer 30.包含min函数的栈 题目描述&#xff1a; 定义栈的数据结构&#xff0c;请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中&#xff0c;调用 min、push 及 pop 的时间复杂度都是 O(1)。 示例&#xff1a; MinStack minStack new MinStack(); minSt…

Python中的错误是什么,Python中有哪些错误

7.1 错误(errors) 由于Python代码通常是人类编写的&#xff0c;那么无论代码是在解释之前还是运行之后&#xff0c;或多或少总会出现一些问题。 在Python代码解释时遇到的问题称为错误&#xff0c;通常是语法和缩进问题导致的&#xff0c;这些错误会导致代码无法通过解释器的解…

2023年绿色建筑国际会议(ICoGB 2023)

2023年绿色建筑国际会议&#xff08;ICoGB 2023&#xff09; 重要信息 会议网址&#xff1a;www.icogb.org 会议时间&#xff1a;2023年5月19-21日 召开地点&#xff1a;斯德哥尔摩 截稿时间&#xff1a;2023年4月1日 录用通知&#xff1a;投稿后2周内 收录检索&#xff…

剑指 Offer 61 扑克牌中的顺子

摘要 扑克牌中的顺子 一、集合 Set 遍历 根据题意&#xff0c;此5张牌是顺子的 充分条件 如下&#xff1a; 除大小王外&#xff0c;所有牌 无重复 &#xff1b;设此5张牌中最大的牌为max&#xff0c;最小的牌为min&#xff08;大小王除外&#xff09;&#xff0c;则需满足…

深入理解浏览器解析机制和XSS向量编码

目录 1、HTML解析 字符实体(character entities) HTML字符实体(HTML character entities) 字符引用(character references) 在HTML中有五类元素 五类元素的区别如下 深入了解下RCDATA元素 2、URL解析 3、JavaScript解析 4、解析流 1、HTML解析 从XSS的角度来说&…

es倒排索引原理

1、简介 网上看到的一篇文章&#xff0c;对Lucene的倒排索引是如何执行的&#xff0c;说的比较易懂&#xff0c;就转过来分享下。 Elasticsearch是通过Lucene的倒排索引技术实现比关系型数据库更快的过滤。特别是它对多条件的过滤支持非常好&#xff0c;比如年龄在18和30之间&a…