SpringBoot+AOP实现用户操作日志的记录

2023-11-12

前言:
任何一个项目都会有一个用户操作日志(也叫行为日志)的模块,它主要用来记录某个用户做了某个操作,当出现操作失败时,通过日志就可以快速的查找是哪个用户在哪个模块出现了错误,以便于开发人员快速定位问题所在。

实现这一功能一般有两种方法:

  • 第一种就是很传统的做法,就是在每个模块进行插入日志的操作(不推荐),这种做法虽然实现了记录用户的操作,但很繁琐而且基本上是重复的工作。
  • 第二种就是使用Spring的AOP来实现记录用户操作,也是推荐的现如今都使用的一种做法。它的优势在于这种记录用户操作的代码独立于其他业务逻辑代码,不仅实现了解耦,而且避免了冗余代码。

具体实现步骤

  1. 在pom.xml中添加AOP依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>`
  1. 设计操作日志记录表
    在这里插入图片描述

  2. 新增日志实体类、dao层 接口

  3. 自定义操作日志记录的注解

package com.example.springcloud.aop;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author lyz
 * @title: OperationLog
 * @projectName springcloud
 * @date 2020/9/23
 * @description: 自定义操作日志注解
 */
@Target(ElementType.METHOD)//注解放置的目标位置即方法级别
@Retention(RetentionPolicy.RUNTIME)//注解在哪个阶段执行
@Documented
public @interface OperationLogAnnotation {
    String operModul() default ""; // 操作模块

    String operType() default "";  // 操作类型

    String operDesc() default "";  // 操作说明
}

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

import com.example.springcloud.dao.OperationLogDao;
import com.example.springcloud.domain.OperationLog;
import com.example.springcloud.utils.IPUtil;
import org.aspectj.lang.JoinPoint;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
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;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

/**
 * @author lyz
 * @title: OperationAspect
 * @projectName springcloud
 * @date 2020/9/23
 * @description: 操作日志切面处理类
 */
@Aspect
@Component
public class OperationLogAspect {
    @Autowired
    OperationLogDao logDao;
    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * 设置操作日志切入点   在注解的位置切入代码
     */
    @Pointcut("@annotation(com.example.springcloud.aop.OperationLogAnnotation)")
    public void operLogPoinCut() {
    }

    /**
     * 记录操作日志
     * @param joinPoint 方法的执行点
     * @param result  方法返回值
     * @throws Throwable
     */
    @AfterReturning(returning = "result", value = "operLogPoinCut()")
    public void saveOperLog(JoinPoint joinPoint, Object result) throws Throwable {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        try {
            //将返回值转换成map集合
            Map<String, String> map = (Map<String, String>) result;
            OperationLog operationLog = new OperationLog();
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            //获取切入点所在的方法
            Method method = signature.getMethod();
            //获取操作
            OperationLogAnnotation annotation = method.getAnnotation(OperationLogAnnotation.class);
            if (annotation != null) {
                operationLog.setModel(annotation.operModul());
                operationLog.setType(annotation.operType());
                operationLog.setDescription(annotation.operDesc());
            }
            //操作时间
            operationLog.setOperationTime(Timestamp.valueOf(sdf.format(new Date())));
            //操作用户
            operationLog.setUserCode(request.getHeader("userCode"));
            //操作IP
            operationLog.setIp(IPUtil.getIpAdrress(request));
            //返回值信息
            operationLog.setResult(map.get("message"));
            //保存日志
            logDao.save(operationLog);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

  1. 在controller层的某一个方法加入@OperationLogAnnotation 注解
package com.example.springcloud.controller;

import com.example.springcloud.aop.OperationLogAnnotation;
import com.example.springcloud.domain.User;
import com.example.springcloud.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * @author lyz
 * @title: UserController
 * @projectName springcloud
 * @date 2020/9/12
 * @description:
 */
@Api(tags = "用户表")
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    UserService userService;

    @OperationLogAnnotation(operModul = "用户模块-用户列表",operType = "查询",operDesc = "查询所有用户")
    @ApiOperation(value = "查询所有用户",notes = "这是用来查询所有用户列表")
    @GetMapping("/users")
    public Object findAll(@RequestParam(required = false)String userName,
                          @RequestParam(required = false)Integer sex,
                          @RequestParam(required = false, defaultValue = "10") int limit,
                          @RequestParam(required = false, defaultValue = "0") int offset,
                          @RequestParam(required = false, defaultValue = "createTime") String sortBy,
                          @RequestParam(required = false, defaultValue = "DESC") String sortFlag, HttpServletRequest request){

        offset=offset/limit;
        request.setAttribute("userCode","admin");
        return userService.findAll(userName,sex,sortBy,sortFlag,offset,limit);
    }


    @OperationLogAnnotation(operModul = "用户模块-新增用户",operType = "新增",operDesc = "新增用户")
    @PostMapping("/addUser")
    @ApiOperation(value = "新增用户",notes = "通过这个方法可以添加新用户")
    public Object createUser(@RequestBody Map<String, String>map){
        return userService.save(map);
    }

}

  1. 通过swagger或者postmain进行测试,最后在数据库中查看操作日志记录
    在这里插入图片描述
    总结:用户操作日志是AOP最常见的一种业务场景,这里使用了后置通知,当然也可以也可以使用异常通知记录异常信息,方法如上。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot+AOP实现用户操作日志的记录 的相关文章

随机推荐

  • 利用Python消费RocketMQ消息队列数据

    语言 python3 6 环境 centos 7 1 安装 rocketmq python 地址见 https pypi org project rocketmq pip install rocketmq 2 安装rocketmq clie
  • 【定量分析、量化金融与统计学】R语言:多元线性回归实例

    今天来做一个R语言的多元线性回归的实例 题目是这样的 练习 度假村排名 旅游胜地 专门介绍高级度假和住宿的杂志 Spas 在 读者选择 评选的世界20家独立海滨精品酒店中榜上有名 所显示的数据是这些酒店根据Resorts温泉年度读者选择调查
  • Springboot整合RestTemplate发送http请求

    据技术选型总结常见的三种方式发送http请求 本文介绍Springboot整合RestTemplate发送http请求方式 其他两种如下链接 java原生发送http请求 程序三两行的博客 CSDN博客 HttpClient和OkHttp发
  • java语言简介

    什么是Java语言 一种面向对象的语言 编写程序的开始就是编写类的开始 class 用于定义类 一种平台无关的语言 必须程序运行的解释环境 真正的运行步骤为 javac编译 java解释执行 一种健壮的语言 吸收了C C 语言的优点 但是去
  • Idea安装配置Maven【简述】

    Maven 是一个管理和构建Java项目的工具 它主要的生命周期 编译 测试 打包 发布 Maven项目可以在不同IDE使用 比如 Idea 和 eclipse 他们自身的项目是不能互通的 然而使用Maven构建的项目可以在这两个不同平台使
  • C++ Primer 学习笔记 第四章 表达式

    表达式由运算对象组成 对表达式求值得到一个结果 字面值和变量是最简单的表达式 其结果就是字面值和变量的值 把一个运算符和一个或多个运算对象组合起来可以生成较复杂的表达式 C 定义了一元运算符 二元运算符和三元运算符 作用于一个运算对象的运算
  • oracle语句中截取字符,Oracle中字符串截取最全方法总结

    substr 函数 截取字符串 语法 SUBSTR string start length string 表示源字符串 即要截取的字符串 start 开始位置 从1开始查找 如果start是负数 则从string字符串末尾开始算起 leng
  • 剑指offer:中等部分

    剑指offer 中等部分 001 求1 2 n 求 1 2 n 要求不能使用乘除法 for while if else switch case等关键字及条件判断语句 A B C 示例 1 输入 n 3 输出 6 示例 2 输入 n 9 输出
  • 晶振与软件的关系(深度理解)

    晶振是电路中非常常见的一个元件 常常被人们称为芯片的心脏 确实如此 没有了晶振 可以说一般情况下芯片就无法工作 可编程器件通常来说就是能够通过编程后来执行的芯片 例如各种单片机等等 通常来说我们称软件即是芯片的灵魂 那么如此 作为一个实体的
  • 篮球比赛JAVA代码_Java编程实现NBA赛事接口调用实例代码

    第一步 找别人提供的接口 比如在这里我选择的是聚合数据提供的接口 第二步 要申请相应的AppKey方可使用 此参数会作为接口的参数调用 第三步 调用别人提供的接口方法 代码如下 package juheapi nba Created by
  • MySQL between and 过滤的边界问题

    当数据库字段中存储的是yyyy MM dd格式 即date类型 用between and查询参数yyyy MM dd格式时 包含头尾 相当于x gt y x lt z 当是yyyy MM dd HH mm ss格式 即datetime类型
  • MySQL的biglog文件操作

    一 查看 1 SQL 1 查看所有binlog文件 mysql gt show binary logs 2 查看binlog内容 mysql gt show binlog events in mysql bin 000001 2 mysql
  • 反向代理神器 Nginx Proxy Manager 群晖Docker部署

    群晖Docker部署 本文将使用 NginxProxyManager 中文版 介绍NginxProxyManager基于群晖Docke的部署方法 并且所有操作均在群晖网页端完成 不需要命令行操作 非常适合新手 GitHub xiaoxinp
  • CCMoveBy和CCMoveTo有什么区别?

    CCMoveBy和CCMoveTo有什么区别 cocos2d里面的CCMoveBy 和CCMoveTo有什么区别 含义不同的地方在那块 那位高人给解释一下 谢谢 insul 2010 09 14 18 52 by是相对于当前位置 to是到该
  • AI for Scinece Cup 邀请函:一等奖5万

    点击阅读原文 也可进入比赛报名页面
  • Three.js快速入门

    Three js快速入门 1 threejs文件包下载和目录简介 下载地址 网盘链接 https pan baidu com s 1 Ix8TiOScypNcQe3BIl5vA pwd rrks 提取码 rrks threejs文件资源目录
  • iTween基础之Value(数值过度)

    一 基础介绍 二 基础属性 原文地址 http blog csdn net dingkun520wy article details 50550527 一 基础介绍 Value有一个函数 ValueTo 返回一个 from 和 to 之间的
  • element table 合计 第一行 固定列

    element table 合计 第一行 在这位大哥这里学来的 但同时我这边的情况是 固定高度 第一列固定 参数多 因此 这个方法不能够完全满足 因此加入以下代码 代码作用 在每次获取到表格数据时进行操作 因为固定列后 第一列滚动到最底部都
  • np.random.randn()、np.random.rand()、np.random.randint()的区别和用法

    1 np random randn 函数 通过本函数可以返回一个或一组服从标准正态分布的随机样本值 语法 np random randn d0 d1 d2 dn 1 当函数括号内没有参数时 则返回一个浮点数 2 当函数括号内有一个参数时 则
  • SpringBoot+AOP实现用户操作日志的记录

    前言 任何一个项目都会有一个用户操作日志 也叫行为日志 的模块 它主要用来记录某个用户做了某个操作 当出现操作失败时 通过日志就可以快速的查找是哪个用户在哪个模块出现了错误 以便于开发人员快速定位问题所在 实现这一功能一般有两种方法 第一种