自定义注解+AOP切面实现记录操作日志到数据库

需求

● 每当有增、删、改、上传等编辑操作时,需要记录日志到数据库,用于溯源。
● 记录字段比如:操作人id、名称、ip地址、请求方式、调用方法、参数、操作类型、操作时间、操作模块、返回结果。
使用AOP切面编程方式,对增删改操作进行增强,在操作完成后切入,获取所需要的信息,存入数据库。

创建自定义注解

● 使用@interface创建自定义注解OptLog
● 1个参数:optType,用于记录操作类型。

1
2
3
4
5
6
7
8
9
10
11
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OptLog {

/**
* @return 操作类型
*/
String optType() default "";

}

创建常量,定义操作类型

定义常量做为optType的值,统一规范,方便后期维护。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class OptTypeConst {

/**
* 新增操作
*/
public static final String SAVE_OR_UPDATE = "新增或修改";

/**
* 新增
*/
public static final String SAVE = "新增";

/**
* 修改操作
*/
public static final String UPDATE = "修改";

/**
* 删除操作
*/
public static final String REMOVE = "删除";

/**
* 上传操作
*/
public static final String UPLOAD = "上传";

}

编写切面方法,实现记录日志

@Aspect:声明为切面类。
@Pointcut("@annotation(com.minzheng.blog.annotation.OptLog)"):在OptLog注解的位置切入代码。
@AfterReturning(value = "optLogPointCut()", returning = "keys"):表示对optLogPointCut()方法定义的切入点进行增强的实现方法,returning = “keys”表示返回的参数。
● 通过RequestContextHolder获取到request,获取请求中的信息。
joinPoint通过反射,获取到切入点所在的方法,以及注解中的内容。
● 将信息存入operationLog实体类,最终通过operationLogDao存入数据库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@Aspect
@Component
public class OptLogAspect {

@Autowired
private OperationLogDao operationLogDao;

/**
* 设置操作日志切入点 记录操作日志 在注解的位置切入代码
*/
@Pointcut("@annotation(com.minzheng.blog.annotation.OptLog)")
public void optLogPointCut() {}


/**
* 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
*
* @param joinPoint 切入点
* @param keys 返回结果
*/
@AfterReturning(value = "optLogPointCut()", returning = "keys")
@SuppressWarnings("unchecked")
public void saveOptLog(JoinPoint joinPoint, Object keys) {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) Objects.requireNonNull(requestAttributes).resolveReference(RequestAttributes.REFERENCE_REQUEST);
OperationLog operationLog = new OperationLog();
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取操作
Api api = (Api) signature.getDeclaringType().getAnnotation(Api.class);
ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
OptLog optLog = method.getAnnotation(OptLog.class);
// 操作模块
operationLog.setOptModule(api.tags()[0]);
// 操作类型
operationLog.setOptType(optLog.optType());
// 操作描述
operationLog.setOptDesc(apiOperation.value());
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
// 获取请求的方法名
String methodName = method.getName();
methodName = className + "." + methodName;
// 请求方式
operationLog.setRequestMethod(Objects.requireNonNull(request).getMethod());
// 请求方法
operationLog.setOptMethod(methodName);
// 请求参数
operationLog.setRequestParam(JSON.toJSONString(joinPoint.getArgs()));
// 返回结果
operationLog.setResponseData(JSON.toJSONString(keys));
// 请求用户ID
operationLog.setUserId(UserUtils.getLoginUser().getId());
// 请求用户
operationLog.setNickname(UserUtils.getLoginUser().getNickname());
// 请求IP
String ipAddress = IpUtils.getIpAddress(request);
operationLog.setIpAddress(ipAddress);
operationLog.setIpSource(IpUtils.getIpSource(ipAddress));
// 请求URL
operationLog.setOptUrl(request.getRequestURI());
operationLogDao.insert(operationLog);
}

}

段落引用知识点
@Around @Before @After @AfterReturning 执行顺序为:
● Around
● AroundBefore
● before
● method.invoke()
● AroundAfter
● After
● AfterReturning