开发系统时往往需要考虑记录用户访问系统查询了那些数据、进行了什么操作,尤其是访问重要的数据和执行重要的操作的时候将数记录下来尤显的有意义。有了这些用户行为数据,事后可以以用户为条件对用户在系统的访问和操作进行统计。

同时因为系统对登录用户在系统的行为有较为详细的记录,客观上也增加了系统的安全性。

记录那些数据

根据多年的经验,系统一般自动记录用户以下内容基本可以满足需要:

谁,在什么时候,在哪里,做了什么,结果如何

使用什么方式

使用Spring AOP切面记录用户在系统中行为再合适不过了。使用Spring AOP切面横切需要需要记录的用户访问的方法,在切面中记录:谁,在什么时候,在哪里,做了什么,结果如何。 如下图所示

使用Spring AOP实现记录用户行为的切面

首先回顾下AOP的术语

Advice(通知):

定义了切面完成的工作以及何时执行这个工作。

Spring切面定义了5种类型的通知:

  • 前置通知(Before):在目标方法调用之前调用。

  • 后置通知(After):在目标方法完成之后调用。

  • 返回通知(After-returning) :在目标方法成功执行之后调用。

  • 异常通知(After-throwing):在目标方法抛出异常之后条用。

  • 环绕通知(Around):包裹了目标方法,在目标方法调用之前和之后执行自定义的行为。

Pointcut(切点): Advice(通知)定义了 何时、做什么的问题,那么切点定义了哪里做的问题。切点Pointcut的定义会匹配通知所要织入的一个或多个连接点JoinPoint。稍后我们用注解的方式描述切点Pointcut。

Join Point(连接点):应用通知的时机,在SpringAOP中指的的就是方法。

切面(Aspect) :就是通知和切点的结合。它们共同定义了在何时何处做点什么。

示意图:

编码实现

标注了AuditAction注解的方法都将被AuditAspectAuditAspect拦截,并在通知中将用户的行为记录下来。

用注解的方式定义切点

被此注解标注的方法都将被拦截织入通知。

package com.sdsxblp.common.log.aspect;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Documented
@Retention(RUNTIME)
@Target(METHOD)
/**
* @Description: 标记需要记录操作日志的方法
* @author Bruce Zou(jblzdg@163.com)
*/
public @interface AuditAction {
  /**
    * 对资源(比如用户管理功能等)进行操作
    *
    * @return
    */
  String resource() default "";

  /**
    * 资源的描述
    *
    * @return
    */
  String resourceDes() default "";

  /**
    * 操作的类型
    *
    * @return
    */
  String actionType() default "";
}
定义切面
package com.sdsxblp.common.log.aspect;

import com.sdsxblp.common.log.entity.ActionLog;
import com.sdsxblp.common.log.util.AuditLogHolder;
import com.sdsxblp.common.log.service.ActionLogService;
import com.sdsxblp.common.log.api.IAuditActionDataService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Date;

/**
* @author Bruce Zou(jblzdg@163.com)
* @Description:记录操作日志的切面
*/
@Aspect
@Component
@Slf4j
public class AuditAspect {

  @Autowired
  //将用户行为数据保存到db中
  private ActionLogService actionLogService;

//使用环绕通知。
//被com.sdsxblp.common.log.aspect.AuditAction标注的方法都将被拦截
  @Around(value = "@annotation(com.sdsxblp.common.log.aspect.AuditAction)")
  public Object executeAudit(ProceedingJoinPoint joinPoint) throws Throwable {
      log.info("**开始记录操作日志:");
      //执行目标方法
      Object returnValue = joinPoint.proceed();
     
      ActionLog actionLog = new ActionLog();
      actionLog.setActionDate(new Date());
      setValueFromAnnotation(joinPoint, actionLog);
      //为了提高性能使用异步方法保存行为数据
      actionLogService.asynSaveLog(actionLog);
      log.info("**操作日志记录结束。");
      // 返回目标方法的结果
      return returnValue;
  }


  /**
    * 从代理的Target方法中获取资源、资源描述和操作类型。
    *
    * @param joinPoint
    * @param actionLog
    * @throws NoSuchMethodException
    */
  private void setValueFromAnnotation(ProceedingJoinPoint joinPoint, ActionLog actionLog) throws NoSuchMethodException {
      //获取接口方法签名
      MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
      String methodName = methodSignature.getName();
      //获取实现类方法
      Method method = joinPoint.getTarget().getClass().getMethod(methodName, methodSignature.getParameterTypes());
      //获取实现类注解
      AuditAction auditAction = method.getAnnotation(AuditAction.class);
      if (auditAction != null) {
          String resource = auditAction.resource();
          if (StringUtils.isNotBlank(resource)) {
              actionLog.setResource(resource);
          }
          String resourceDes = auditAction.resourceDes();
          if (StringUtils.isNotBlank(resourceDes)) {
              actionLog.setResourceDes(resourceDes);
          }
          String actionType = auditAction.actionType();
          if (StringUtils.isNotBlank(actionType)) {
              actionLog.setActionType(actionType);
          }
      }

  }
}
备注 ActionLog主要代码如下
public class ActionLog  {
  private String userName;
  private Date actionDate;
  private String fomIp;
  private String macAddress;
  private String resource;
  private String resourceDes;

  private String actionType;
  private String beforeValue;
  private String afterValue;
  ...
}

利用Spring AOP切面对用户访问进行监控的更多相关文章

  1. Spring AOP 切面编程记录日志和接口执行时间

    最近客户现在提出系统访问非常慢,需要优化提升访问速度,在排查了nginx.tomcat内存和服务器负载之后,判断是数据库查询速度慢,进一步排查发现是因为部分视图和表查询特别慢导致了整个系统的响应时间特 ...

  2. 使用Spring AOP切面解决数据库读写分离

    http://blog.jobbole.com/103496/ 为了减轻数据库的压力,一般会使用数据库主从(master/slave)的方式,但是这种方式会给应用程序带来一定的麻烦,比如说,应用程序如 ...

  3. 化繁就简,如何利用Spring AOP快速实现系统日志

    1.引言 有关Spring AOP的概念就不细讲了,网上这样的文章一大堆,要讲我也不会比别人讲得更好,所以就不啰嗦了. 为什么要用Spring AOP呢?少写代码.专注自身业务逻辑实现(关注本身的业务 ...

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

    我使用Spring AOP实现了用户操作日志功能 今天答辩完了,复盘了一下系统,发现还是有一些东西值得拿出来和大家分享一下. 需求分析 系统需要对用户的操作进行记录,方便未来溯源 首先想到的就是在每个 ...

  5. Spring AOP切面的时候参数的传递

    Spring AOP切面的时候参数的传递 Xml: <?xml version="1.0" encoding="UTF-8"?> <beans ...

  6. spring AOP(切面) 表达式介绍

    在 spring AOP(切面) 例子基础上对表达式进行介绍 1.添加接口删除方法 2.接口实现类 UserDaoServer 添加实现接口删除方法 3.测试类调用delUser方法 4. 输出结果截 ...

  7. 利用spring AOP 和注解实现方法中查cache-我们到底能走多远系列(46)

    主题:这份代码是开发中常见的代码,查询数据库某个主表的数据,为了提高性能,做一次缓存,每次调用时先拿缓存数据,有则直接返回,没有才向数据库查数据,降低数据库压力. public Merchant lo ...

  8. 利用Spring AOP自定义注解解决日志和签名校验

    转载:http://www.cnblogs.com/shipengzhi/articles/2716004.html 一.需解决的问题 部分API有签名参数(signature),Passport首先 ...

  9. 利用Spring AOP和自定义注解实现日志功能

    Spring AOP的主要功能相信大家都知道,日志记录.权限校验等等. 用法就是定义一个切入点(Pointcut),定义一个通知(Advice),然后设置通知在该切入点上执行的方式(前置.后置.环绕等 ...

随机推荐

  1. 【CF1591】【数组数组】【逆序对】#759(div2)D. Yet Another Sorting Problem

    题目:Problem - D - Codeforces 题解 此题是给数组排序的题,操作是选取任意三个数,然后交换他们,确保他们的位置会发生改变. 可以交换无限次,最终可以形成一个不下降序列就输出&q ...

  2. windows下python3.7安装gmpy2、Crypto 库及rsa

    基于python3.7在windows下安装gmpy2 先检查一下是否安装了wheel文件包,在cmd中输入wheel,查看一下,如果没有安装,则输入安装:pip install wheel 如果遇到 ...

  3. 30个类手写Spring核心原理之动态数据源切换(8)

    本文节选自<Spring 5核心原理> 阅读本文之前,请先阅读以下内容: 30个类手写Spring核心原理之自定义ORM(上)(6) 30个类手写Spring核心原理之自定义ORM(下)( ...

  4. CF1132B Discounts 题解

    Content 有一个长度为 \(n\) 的数组 \(a_1,a_2,a_3,...,a_n\).有 \(q\) 次询问,每次询问给定一个数 \(x\).对于每次询问,求出数组中去掉一个第 \(x\) ...

  5. AT266 迷子のCDケース 题解

    Content 有 \(n+1\) 个碟,编号为 \(0\sim n\),一开始 \(0\) 号碟在播放机上,其他的碟依次放进了 \(n\) 个盒子里面.现在有 \(m\) 次操作,每次操作找到当前在 ...

  6. 原生XMLHTTPResponse,jQuery-Ajax 上传文件;iframe上传图片&预览;图片验证码小案例

    原生AJAX Ajax主要就是使用 [XmlHttpRequest]对象来完成请求的操作,该对象在主流浏览器中均存在(除早起的IE),Ajax首次出现IE5.5中存在(ActiveX控件) 1.Xml ...

  7. Shell 丢弃错误和输出信息

    shell中使用>/dev/null 2>&1 丢弃信息   在一些Shell脚本中,特别是Crontab的脚本中,经常会看到 >/dev/null 2>&1这 ...

  8. GBK 和 UTF8

    首先来看一下常用的编码有哪些,截图自Notepad++.其中ANSI在中国大陆即为GBK(以前是GB2312),最常用的是 GBK 和 UTF8无BOM 编码格式.后面三个都是有BOM头的文本格式,U ...

  9. IDEA 无法显示 Run Dashboard 的解决方法

    前言 最近开始接触微服务的项目,项目很多有时候本地调测需要启动多个项目,看到同事都是使用dashboard管理项目,服务和端口排列的整整齐齐.但是我dashboard里面啥都没有,一顿百度最后解决问题 ...

  10. 使用.NET 6开发TodoList应用(7)——使用AutoMapper实现GET请求

    系列导航 使用.NET 6开发TodoList应用文章索引 需求 需求很简单:实现GET请求获取业务数据.在这个阶段我们经常使用的类库是AutoMapper. 目标 合理组织并使用AutoMapper ...