我使用Spring AOP实现了用户操作日志功能

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我使用Spring AOP实现了用户操作日志功能相关的知识,希望对你有一定的参考价值。

参考技术A 系统需要对用户的操作进行记录,方便未来溯源

首先想到的就是在每个方法中,去实现记录的逻辑,但是这样做肯定是不现实的,首先工作量大,其次违背了软件工程设计原则(开闭原则)

这种需求显然是对代码进行增强,首先想到的是使用 SpringBoot 提供的 AOP 结合注解的方式来实现

如果是使用 spring 框架的,引入 spring-aop 即可

需要 lombok 插件(@getter && @setter 注解)

因为我使用的是 Mybatis-plus,所以 logService.save(log); 是 mybatis-plus 原生的 save operation

这步其实就是把 log 插入到数据库

6、使用

只需要在方法上加上 @ILog 注解,并设置它的 value 即可(value 就是描述当前 method 作用)

数据库:

如果要对现有代码进行功能扩展,使用 AOP + 注解不妨为一种优雅的方式

对 AOP 不熟悉的小伙伴,可以深入了解一下,毕竟是 spring 最重要的特性之一。

Spring Boot AOP记录用户操作日志

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

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
?
<!-- aop依赖 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
?
<!-- oracle驱动 -->
<dependency>
  <groupId>com.oracle</groupId>
  <artifactId>ojdbc6</artifactId>
  <version>6.0</version>
</dependency>
?
<!-- druid数据源驱动 -->
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid-spring-boot-starter</artifactId>
  <version>1.1.6</version>
</dependency>

 

 

自定义注解

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

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

 

创建库表和实体

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

CREATE TABLE "SCOTT"."SYS_LOG" (
  "ID" NUMBER(20) NOT NULL ,
  "USERNAME" VARCHAR2(50 BYTE) NULL ,
  "OPERATION" VARCHAR2(50 BYTE) NULL ,
  "TIME" NUMBER(11) NULL ,
  "METHOD" VARCHAR2(200 BYTE) NULL ,
  "PARAMS" VARCHAR2(500 BYTE) NULL ,
  "IP" VARCHAR2(64 BYTE) NULL ,
  "CREATE_TIME" DATE NULL
);
?
COMMENT ON COLUMN "SCOTT"."SYS_LOG"."USERNAME" IS ‘用户名‘;
COMMENT ON COLUMN "SCOTT"."SYS_LOG"."OPERATION" IS ‘用户操作‘;
COMMENT ON COLUMN "SCOTT"."SYS_LOG"."TIME" IS ‘响应时间‘;
COMMENT ON COLUMN "SCOTT"."SYS_LOG"."METHOD" IS ‘请求方法‘;
COMMENT ON COLUMN "SCOTT"."SYS_LOG"."PARAMS" IS ‘请求参数‘;
COMMENT ON COLUMN "SCOTT"."SYS_LOG"."IP" IS ‘IP地址‘;
COMMENT ON COLUMN "SCOTT"."SYS_LOG"."CREATE_TIME" IS ‘创建时间‘;
?
CREATE SEQUENCE seq_sys_log START WITH 1 INCREMENT BY 1;

 

库表对应的实体:

public class SysLog implements Serializable{
?
  private static final long serialVersionUID = -6309732882044872298L;
   
  private Integer id;
  private String username;
  private String operation;
  private Integer time;
  private String method;
  private String params;
  private String ip;
  private Date createTime;
  // get,set略
}

 

保存日志的方法

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

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

 

其实现方法:

@Repository
public class SysLogDaoImp implements SysLogDao {
?
  @Autowired
  private JdbcTemplate jdbcTemplate;
   
  @Override
  public void saveSysLog(SysLog syslog) {
      StringBuffer sql = new StringBuffer("insert into sys_log ");
      sql.append("(id,username,operation,time,method,params,ip,create_time) ");
      sql.append("values(seq_sys_log.nextval,:username,:operation,:time,:method,");
      sql.append(":params,:ip,:createTime)");
       
      NamedParameterJdbcTemplate npjt = new NamedParameterJdbcTemplate(this.jdbcTemplate.getDataSource());
      npjt.update(sql.toString(), new BeanPropertySqlParameterSource(syslog));
  }
}

 

切面和切点

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

@Aspect
@Component
public class LogAspect {
?
  @Autowired
  private SysLogDao sysLogDao;
   
  @Pointcut("@annotation(com.springboot.annotation.Log)")
  public void pointcut() { }
?
  @Around("pointcut()")
  public Object around(ProceedingJoinPoint point) {
      Object result = null;
      long beginTime = System.currentTimeMillis();
      try {
          // 执行方法
          result = point.proceed();
      } catch (Throwable e) {
          e.printStackTrace();
      }
      // 执行时长(毫秒)
      long time = System.currentTimeMillis() - beginTime;
      // 保存日志
      saveLog(point, time);
      return result;
  }
?
private void saveLog(ProceedingJoinPoint joinPoint, long time) {
      MethodSignature signature = (MethodSignature) joinPoint.getSignature();
      Method method = signature.getMethod();
      SysLog sysLog = new SysLog();
      Log logAnnotation = method.getAnnotation(Log.class);
      if (logAnnotation != null) {
          // 注解上的描述
          sysLog.setOperation(logAnnotation.value());
      }
      // 请求的方法名
      String className = joinPoint.getTarget().getClass().getName();
      String methodName = signature.getName();
      sysLog.setMethod(className + "." + methodName + "()");
      // 请求的方法参数值
      Object[] args = joinPoint.getArgs();
      // 请求的方法参数名称
      LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
      String[] paramNames = u.getParameterNames(method);
      if (args != null && paramNames != null) {
          String params = "";
          for (int i = 0; i < args.length; i++) {
              params += " " + paramNames[i] + ": " + args[i];
          }
          sysLog.setParams(params);
      }
      // 获取request
      HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
      // 设置IP地址
      sysLog.setIp(IPUtils.getIpAddr(request));
      // 模拟一个用户名
      sysLog.setUsername("mrbird");
      sysLog.setTime((int) time);
      sysLog.setCreateTime(new Date());
      // 保存系统日志
      sysLogDao.saveSysLog(sysLog);
  }
}

 

测试

TestController:

@RestController
public class TestController {
?
  @Log("执行方法一")
  @GetMapping("/one")
  public void methodOne(String name) { }
   
  @Log("执行方法二")
  @GetMapping("/two")
  public void methodTwo() throws InterruptedException {
      Thread.sleep(2000);
  }
   
  @Log("执行方法三")
  @GetMapping("/three")
  public void methodThree(String name, String age) { }
}

 

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

技术图片

启动项目,分别访问:

以上是关于我使用Spring AOP实现了用户操作日志功能的主要内容,如果未能解决你的问题,请参考以下文章

SSH 下做一个spring AOP的 操作日志记录功能

springboot使用spring的aop功能实现操作日志功能

springboot-使用AOP日志拦截实现

Spring AOP 实现写事件日志功能

17Spring实战:利用AOP实现日志监控

Spring_AOP 记录系统关键操作日志用法