AOP日志
我们在一般做后台管理系统的时候日志系统是一定要有的,因为我们系统一旦数据有哪些异常我们就可以很方便的通过日志记录中谁在什么时间干了什么事情来判断出哪些用户的什么操作给系统带来了异常
日志分析
我们来分析一下一般的日志都需要记录哪些信息
- 访问时间 (必须,因为我们很多时候可以通过日期范围选择快速定位到我们要查询的日志记录上)
- 操作者用户名 (必须 我们需要指定是谁操作的)
- 访问IP (可选)
- url (必须,因为后台系统一般URL就是对应着一个操作)
- 执行时长 (可选)
- 访问方法 (可选,其实我们通过URL就已经指定了操作者访问的具体是哪个方法了)
实体类
package cn.kevinlu98.ssm.daomain;
import cn.kevinlu98.ssm.utils.DateUtil;
import java.util.Date;
/**
* @Author: Kevin·Lu
* @Date: 11:00 AM 2019/7/29
* @Description:
*/
public class SysLog {
private String id;
private Date visitTime;
private String username;
private String ip;
private String url;
private Long executionTime;
private String method;
//getter and setter ...
@Override
public String toString() {
return "SysLog{" +
"id='" + id + '\'' +
", visitTime=" + visitTime +
", username='" + username + '\'' +
", ip='" + ip + '\'' +
", url='" + url + '\'' +
", executionTime=" + executionTime +
", method='" + method + '\'' +
'}';
}
}
与实体类对应的SQL语句(以Oracle为例)
CREATE TABLE SYSLOG(
ID VARCHAR2(32) DEFAULT SYS_GUID() PRIMARY KEY,
VISITTIME TIMESTAMP,
USERNAME VARCHAR2(50),
IP VARCHAR2(30),
URL VARCHAR2(50),
EXECUTIONTIME INT,
METHOD VARCHAR2(200)
)
分析
我们的日志系统基于AOP,所以我们先看看哪些方法是我们的切点
我们一般的后台系统和用户直接打交道的其实只有web层,也就是说web层的controller类中的所有方法就是我们的切点,即:* cn.kevinlu98.ssm.controller.*.*(..)
我们在用户刚调用方法的时候可以得到执行的开始时间,访问的类对象,以及访问的方法和参数
- 开始时间怎么得到呢? 直接去new一个Date的实例出来就好了,但因为我们要计算访问时长,所以我们在用户调用完方法治后还需要用到这个变量,所以我们可以将其封装成全局变量
- 访问的类对象怎么得到? 我们可以直接通过JoinPoint得到
- 访问的方法怎么得到?一样我们可以通过JoinPoint得到,但只能得到方法名,但有了类对象和方法名想要得到方法的对象也是非常容易的
我们在用户调用完方法治后可以得到一个结束时间,URL,IP地址,登录用户等信息
- URL怎么能获取到呢?我们来分析一下我们URL一般是怎么组成,假设我的系统用的是springmvc,那么我的URL应该就是类的RequestMapping与方法上的RequestMapping组合而成的,类上的注解我们可以通过反射得到
- IP地址: 可以通过request对象得到
- 登录用户: 这个根据你系统的设计也很容易得到
得到这么多信息后我们就可以将其保存到我们的数据库中了
分析完成,现在开始coding
代码
package cn.kevinlu98.ssm.controller;
import cn.kevinlu98.ssm.daomain.SysLog;
import cn.kevinlu98.ssm.service.ISysLogService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
/**
* @Author: Kevin·Lu
* @Date: 11:02 AM 2019/7/29
* @Description:
*/
@Component
@Aspect
public class LogAop {
@Autowired
private ISysLogService sysLogService;
@Autowired
private HttpServletRequest request;
//开始时间
private Date startTime;
//访问的类
private Class clazz;
//访问的方法
private Method method;
/**
* 前置通知: 主要获取开始时间,执行的类是哪一个,执行了哪一个方法
*
* @param joinPoint
*/
@Before("execution(* cn.kevinlu98.ssm.controller.*.*(..))")
public void doBefore(JoinPoint joinPoint) throws NoSuchMethodException {
startTime = new Date();//设置开始时间
clazz = joinPoint.getTarget().getClass();//具体访问的类对象
String methodName = joinPoint.getSignature().getName();//获取访问的方法名称
// 获取方法参数
Object[] args = joinPoint.getArgs();
//获取具体执行方法的method对象
//判断方法有没有参数
if (args == null || args.length == 0) {
method = clazz.getMethod(methodName);
} else {
Class[] classArgs = new Class[args.length];
for (int i = 0; i < args.length; i++) {
classArgs[i] = args[i].getClass();
}
method = clazz.getMethod(methodName, classArgs);
}
}
/**
* 后置通知
*
* @param joinPoint
*/
@After("execution(* cn.kevinlu98.ssm.controller.*.*(..))")
public void doAfter(JoinPoint joinPoint) throws Exception {
long time = new Date().getTime() - startTime.getTime();//获取访问时长
//获取URL
String url = "";
if (clazz != null && method != null && clazz != LogAop.class) {
//1.获取类上的@RequestMapping
RequestMapping classAnnotation = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
if (classAnnotation != null) {
String[] classValue = classAnnotation.value();
//2获取方法上的@RequestMapping
RequestMapping methodAnnotation = method.getAnnotation(RequestMapping.class);
if (methodAnnotation != null) {
String[] methodValue = methodAnnotation.value();
url = classValue[0] + methodValue[0];
}
}
}
//获取访问IP地址
String ip = request.getRemoteAddr();
//获取当前操作用户
//我这里以spring-security为例
SecurityContext context = SecurityContextHolder.getContext(); //获取上下文登录用户
User user = (User) context.getAuthentication().getPrincipal();
String username = user.getUsername();
//封装日志信息到SysLog
SysLog sysLog = new SysLog();
sysLog.setIp(ip);
sysLog.setExecutionTime(time);
sysLog.setUrl(url);
sysLog.setUsername(username);
sysLog.setVisitTime(startTime);
sysLog.setMethod(String.format("[类名] %s [方法名] %s", clazz.getName(), method.getName()));
System.out.println(sysLog);
//调用service完成操作
sysLogService.save(sysLog);
}
}
结果
我们看看最后写在数据库中的数据