查看原文
其他

Spring Boot AOP记录用户操作日志

MrBird SpringForAll社区 2021-05-26
点击上方☝SpringForAll社区 轻松关注!
及时获取有趣有料的技术文章

来源:

https://mrbird.cc/Spring-Boot-AOP%20log.html

在Spring框架中,使用AOP配合自定义注解可以方便的实现用户操作的监控。首先搭建一个基本的Spring Boot Web环境开启Spring Boot,然后引入必要依赖:

1<dependency>
2    <groupId>org.springframework.boot</groupId>
3    <artifactId>spring-boot-starter-jdbc</artifactId>
4</dependency>
5
6<!-- aop依赖 -->
7<dependency>
8    <groupId>org.springframework.boot</groupId>
9    <artifactId>spring-boot-starter-aop</artifactId>
10</dependency>
11
12<!-- oracle驱动 -->
13<dependency>
14   <groupId>com.oracle</groupId>
15   <artifactId>ojdbc6</artifactId>
16   <version>6.0</version>
17</dependency>
18
19<!-- druid数据源驱动 -->
20<dependency>
21   <groupId>com.alibaba</groupId>
22   <artifactId>druid-spring-boot-starter</artifactId>
23   <version>1.1.6</version>
24</dependency>

自定义注解

定义一个方法级别的@Log注解,用于标注需要监控的方法:

1@Target(ElementType.METHOD)
2@Retention(RetentionPolicy.RUNTIME)
3public @interface Log {
4    String value() default "";
5}

创建库表和实体

在数据库中创建一张sys_log表,用于保存用户的操作日志,数据库采用oracle 11g:

1CREATE TABLE "SCOTT"."SYS_LOG" (
2   "ID" NUMBER(20NOT NULL ,
3   "USERNAME" VARCHAR2(50 BYTENULL ,
4   "OPERATION" VARCHAR2(50 BYTENULL ,
5   "TIME" NUMBER(11NULL ,
6   "METHOD" VARCHAR2(200 BYTENULL ,
7   "PARAMS" VARCHAR2(500 BYTENULL ,
8   "IP" VARCHAR2(64 BYTENULL ,
9   "CREATE_TIME" DATE NULL 
10);
11
12COMMENT ON COLUMN "SCOTT"."SYS_LOG"."USERNAME" IS '用户名';
13COMMENT ON COLUMN "SCOTT"."SYS_LOG"."OPERATION" IS '用户操作';
14COMMENT ON COLUMN "SCOTT"."SYS_LOG"."TIME" IS '响应时间';
15COMMENT ON COLUMN "SCOTT"."SYS_LOG"."METHOD" IS '请求方法';
16COMMENT ON COLUMN "SCOTT"."SYS_LOG"."PARAMS" IS '请求参数';
17COMMENT ON COLUMN "SCOTT"."SYS_LOG"."IP" IS 'IP地址';
18COMMENT ON COLUMN "SCOTT"."SYS_LOG"."CREATE_TIME" IS '创建时间';
19
20CREATE SEQUENCE seq_sys_log START WITH 1 INCREMENT BY 1;

库表对应的实体:

1public class SysLog implements Serializable{
2
3    private static final long serialVersionUID = -6309732882044872298L;
4
5    private Integer id;
6    private String username;
7    private String operation;
8    private Integer time;
9    private String method;
10    private String params;
11    private String ip;
12    private Date createTime;
13    // get,set略
14}

保存日志的方法

为了方便,这里直接使用Spring JdbcTemplate来操作数据库。定义一个SysLogDao接口,包含一个保存操作日志的抽象方法:

1public interface SysLogDao {
2    void saveSysLog(SysLog syslog);
3}

其实现方法:

1@Repository
2public class SysLogDaoImp implements SysLogDao {
3
4    @Autowired
5    private JdbcTemplate jdbcTemplate;
6
7    @Override
8    public void saveSysLog(SysLog syslog) {
9        StringBuffer sql = new StringBuffer("insert into sys_log ");
10        sql.append("(id,username,operation,time,method,params,ip,create_time) ");
11        sql.append("values(seq_sys_log.nextval,:username,:operation,:time,:method,");
12        sql.append(":params,:ip,:createTime)");
13
14        NamedParameterJdbcTemplate npjt = new NamedParameterJdbcTemplate(this.jdbcTemplate.getDataSource());
15        npjt.update(sql.toString(), new BeanPropertySqlParameterSource(syslog));
16    }
17}

切面和切点

定义一个LogAspect类,使用@Aspect标注让其成为一个切面,切点为使用@Log注解标注的方法,使用@Around环绕通知:

1@Aspect
2@Component
3public class LogAspect {
4
5    @Autowired
6    private SysLogDao sysLogDao;
7
8    @Pointcut("@annotation(com.springboot.annotation.Log)")
9    public void pointcut() { }
10
11    @Around("pointcut()")
12    public Object around(ProceedingJoinPoint point) {
13        Object result = null;
14        long beginTime = System.currentTimeMillis();
15        try {
16            // 执行方法
17            result = point.proceed();
18        } catch (Throwable e) {
19            e.printStackTrace();
20        }
21        // 执行时长(毫秒)
22        long time = System.currentTimeMillis() - beginTime;
23        // 保存日志
24        saveLog(point, time);
25        return result;
26    }
27
28    private void saveLog(ProceedingJoinPoint joinPoint, long time) {
29        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
30        Method method = signature.getMethod();
31        SysLog sysLog = new SysLog();
32        Log logAnnotation = method.getAnnotation(Log.class);
33        if (logAnnotation != null) {
34            // 注解上的描述
35            sysLog.setOperation(logAnnotation.value());
36        }
37        // 请求的方法名
38        String className = joinPoint.getTarget().getClass().getName();
39        String methodName = signature.getName();
40        sysLog.setMethod(className + "." + methodName + "()");
41        // 请求的方法参数值
42        Object[] args = joinPoint.getArgs();
43        // 请求的方法参数名称
44        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
45        String[] paramNames = u.getParameterNames(method);
46        if (args != null && paramNames != null) {
47            String params = "";
48            for (int i = 0; i < args.length; i++) {
49                params += "  " + paramNames[i] + ": " + args[i];
50            }
51            sysLog.setParams(params);
52        }
53        // 获取request
54        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
55        // 设置IP地址
56        sysLog.setIp(IPUtils.getIpAddr(request));
57        // 模拟一个用户名
58        sysLog.setUsername("mrbird");
59        sysLog.setTime((int) time);
60        sysLog.setCreateTime(new Date());
61        // 保存系统日志
62        sysLogDao.saveSysLog(sysLog);
63    }
64}

测试

TestController:

1@RestController
2public class TestController {
3
4    @Log("执行方法一")
5    @GetMapping("/one")
6    public void methodOne(String name) { }
7
8    @Log("执行方法二")
9    @GetMapping("/two")
10    public void methodTwo() throws InterruptedException {
11        Thread.sleep(2000);
12    }
13
14    @Log("执行方法三")
15    @GetMapping("/three")
16    public void methodThree(String name, String age) { }
17}

最终项目目录如下图所示:

QQ截图20171208113619.png

启动项目,分别访问:

  • http://localhost:8080/web/one?name=KangKang

  • http://localhost:8080/web/two

  • http://localhost:8080/web/three?name=Mike&age=25

查询数据库:

1SQLselect * from sys_log order by id;
2
3        ID USERNAME   OPERATION        TIME METHOD                         PARAMS                         IP         CREATE_TIME
4---------- ---------- ---------- ---------- ------------------------------ ------------------------------ ---------- --------------
5        11 mrbird     执行方法一          6 com.springboot.controller.Test  nameKangKang                127.0.0.1  08-12-17
6                                            Controller.methodOne()
7
8        12 mrbird     执行方法二       2000 com.springboot.controller.Test                                127.0.0.1  08-12-17
9                                            Controller.methodTwo()
10
11        13 mrbird     执行方法三          0 com.springboot.controller.Test  nameMike age: 25            127.0.0.1  08-12-17
12                                            Controller.methodThree()

source code:
https://github.com/wuyouzhuguli/Spring-Boot-Demos/tree/master/07.Spring-Boot-AOP-Log :



●  【图文讲解】你一定能看懂的HTTPS原理剖析!

●  基础面试,为什么面试官总喜欢问String?

●  Spring Boot Admin 2.2.0发布,支持最新Spring Boot/Cloud之外,新增中文展示!

●  你应该知道的 @ConfigurationProperties 注解的使用姿势,这一篇就够了

●  学并发编程,透彻理解这三个核心是关键

●  读取Excel还用POI?试试这款开源工具

●  什么是读写锁?微服务注册中心是如何进行读写锁优化的?

●  使用 Spring Framework 时常犯的十大错误

●  Lombok 使用详解,简化Java编程

●  用 Spring 的 BeanUtils 前,建议先了解这几个坑!

●  别在 Java 代码里乱打日志了,这才是打印日志的正确姿势!

●  Mybatis:颠覆你心中对事务的理解!

●  面试官:你说 HashMap 线程不安全,它为啥不安全呢?

●  ElasticSearch 亿级数据检索案例实战!

●  Java 8 Stream Api 中的 peek 操作

● 【12张手绘图】我搞懂了微服务架构!

● 凌晨两点,IT人的朋友圈!

● Java 8 Stream Api 中的新操作

● SonarQube - 中文插件安装

● SonarQube 搭建好了,5分钟Docker搭建Maven私服

● 无套路,3分钟带你轻松上手SonarQube - 代码质量检测平台







如有收获,请帮忙转发,您的鼓励是作者最大的动力,谢谢!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存