1.创建pojo对象
package com.jt.system.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 基于此对象封装用户行为日志?
* 谁在什么时间执行了什么操作,访问了什么方法,传递了什么参数,访问时长是多少.
*/
@Data
@TableName("tb_logs")
public class Log implements Serializable {
private static final long serialVersionUID = 3054471551801044482L;
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String operation;
private String method;
private String params;
private Long time;
private String ip;
@TableField("createdTime")
private Date createdTime;
private Integer status;
private String error;
}
2.DAO层逻辑实现
package com.jt.system.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jt.system.pojo.Log;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户行为日志数据层对象
*/
@Mapper
public interface LogMapper extends BaseMapper<Log> {
}
3.service层接口实现
package com.jt.system.service;
import com.jt.system.pojo.Log;
/**
* 用户行为日志业务逻辑接口定义
*/
public interface LogService {
/**
* 保存用户行为日志.
* @param log
*/
void insertLog(Log log);
//.....
}
4 service层接口实现类
package com.jt.system.service.impl;
import com.jt.system.dao.LogMapper;
import com.jt.system.pojo.Log;
import com.jt.system.service.LogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class LogServiceImpl implements LogService {
@Autowired
private LogMapper logMapper;
/**
* @Async描述的方法底层会异步执行(不由web服务线程执行,
* 而是交给spring自带的线程池中的线程去执行)但是@Async注解的
* 应用有个前提,需要启动类上启动异步执行(添加@EnableAsync注解描述).
* 优点:不会长时间阻塞web服务(例如tomcat)线程
*/
@Async
@Override
public void insertLog(Log log) {
logMapper.insert(log);
}
}
5 controller层实现
package com.jt.system.controller;
import com.jt.system.pojo.Log;
import com.jt.system.service.LogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/log")
public class LogController {
@Autowired
private LogService logService;
@PostMapping
public void doInsertLog(@RequestBody Log log){
logService.insertLog(log);
}
}
6 自定义切入点
package com.jt.resource.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 定义RequiredLog注解,通过此注解对需要
* 进行日志记录的方法进行描述
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredLog {
String value() default "";
}
7AOP日志记录
package com.jt.resource.aspect;
import com.jt.resource.annotation.RequiredLog;
import com.jt.resource.pojo.Log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.lang.reflect.Method;
import java.util.Date;
/**
* @Aspect 注解描述的类型为一个切面类型,在此类中可以定义:
* 1)切入点(切入扩展逻辑的位置~例如权限控制,日志记录,事务处理的位置),在
* @Aspect描述的类中,通常使用@Pointcut注解进行定义.使用切入点描述的方法为切入点方法.
*
* 2)通知方法(在切入点对应的目标方法执行前后要执行逻辑需要写到这样的方法中),在
* @Aspect描述的类中,通过@Before,@After,@Aroud,@AfterReturning,@AfterThrowing
* 这样的注解进行描述.
* a: @Before切入点方法执行之前执行
* b: @After 切入点方法执行之后执行(不管切入点方法是否执行成功了,它都会执行)
* c: @Aroud 切入点方法执行之前和之后都可以执行(最重要)
* d: @AfterReturning 切入点方法成功执行之后执行
* e: @AfterThrowing 切入点方法执行时出了异常会执行
*/
@Aspect
@Component
public class LogAspect {
/**
* @Pointcut 注解用于定义切入点,此注解中的内容为切入点表达式
* @annotation 为注解方式的切入点表达式,此方式的表达式为一种细粒度的切入点表达式,
* 因为它可以精确到方法,例如我们现在使用RequiredLog注解描述方法时,由它描述的方法
* 就是一个切入点方法.
*/
@Pointcut("@annotation(com.jt.resource.annotation.RequiredLog)")
public void doLog(){
//此方法中不需要写任何内容,只负责承载@Pointcut注解
}
/**
* @Around 注解描述的方法为Aspect中的一个环绕通知方法,在此方法
* 内部可以控制对目标方法的调用.
* @param joinPoint 连接点对象,此对象封装了你要执行的切入点方法信息,可以基于
* 此对象对切入点方法进行反射调用
* @return 目标执行链中切入点方法的返回值.
* @throws Throwable
*/
@Around("doLog()")
public Object doAround(ProceedingJoinPoint joinPoint)throws Throwable{
int status=1;//状态
String error=null;//错误信息
long time=0l;//执行时长
long t1=System.currentTimeMillis();
try {
//手动调用目标执行链(这个执行链中包含切入点方法~目标方法)
Object result = joinPoint.proceed();
long t2=System.currentTimeMillis();
time=t2-t1;
return result;
}catch (Throwable e){
long t3=System.currentTimeMillis();
time=t3-t1;
status=0;
error=e.getMessage();
throw e;
}finally {
saveLog(joinPoint,time,status,error);
}
}
//存储用户行为日志
private void saveLog(ProceedingJoinPoint joinPoint,long time,
int status,String error)throws Throwable{
//1.获取用户行为日志
//1.1获取目标对象类型(切入点方法所在类的类型)
Class<?> targetClass = joinPoint.getTarget().getClass();
//1.2.获取目标方法
//1.2.1获取方法签名(包含方法信息,....)
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
//1.2.2获取方法对象
Method targetMethod=
targetClass.getDeclaredMethod(signature.getName(),signature.getParameterTypes());
//1.3获取方法上的RequiredLog注解内容
//1.3.1获取目标方法上注解
RequiredLog requiredLog=targetMethod.getAnnotation(RequiredLog.class);
//1.3.2获取注解中的内容(这个内容为我们定义的操作名)
String operation=requiredLog.value();
//1.4获取目标方法名(类名+方法名)
String targetMethodName=targetClass.getName()+"."+targetMethod.getName();
//1.5获取目标方法执行时传入的参数
String params=new ObjectMapper().writeValueAsString(joinPoint.getArgs());
//1.6获取登录用户名(参考了Security官方的代码)
String username=(String)
SecurityContextHolder.getContext()
.getAuthentication()
.getPrincipal();
//1.7获取ip地址(从当前线程获取request对象,然后基于request获取ip地址)
//String ip="192.168.1.100";
ServletRequestAttributes requestAttributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String ip=requestAttributes.getRequest().getRemoteAddr();
//2.将用户行为日志,封装到Log对象
Log logInfo=new Log();
logInfo.setIp(ip);//后续获取
logInfo.setUsername(username);
logInfo.setOperation(operation);
logInfo.setMethod(targetMethodName);
logInfo.setParams(params);
logInfo.setTime(time);
logInfo.setStatus(status);
logInfo.setError(error);
logInfo.setCreatedTime(new Date());
System.out.println("logInfo="+logInfo);
}
}