# 有话说
不知道大家是否和我一样每次去接第三方数据,或者说推数据给第三方的时候都会打印日志,随后屁颠屁颠的去找运维要日志文件,看是否推送成功。
应该有不少人是这样的,日志打印都放到某一个日志文件夹下,每次找日志都很麻烦
存在的问题:
- 会有很多日志文件产生(实时性很强的或者说数据大的)
- 不容易定位数据
- 如果日志文件你可以查看(有权限)还好,如果你不能查看,每次都要去麻烦别人
- 不好分类或者发现问题(第三方很多,数据偶尔会断掉)
如果你所在公司比较厉害,可能使用如下方法
- 使用数据库来存储(单表数据会很大)
- 专用的日志系统 从零开始,用Docker-compose打造SkyWalking、Elasticsearch和Spring Cloud的完美融合 (opens new window)
还有很多比较牛X的方法大家可以推荐一哈
# 构思
基础逻辑如下
# 为什么使用@after通知
在 AOP(Aspect-Oriented Programming)中,after
和 afterReturning
是两种常见的后置通知,它们的执行时机有所不同:
# after
(后置通知)
执行时机:方法执行 结束后 无论结果如何(正常返回、抛出异常等),都会执行。
适合用于清理资源或记录日志等不依赖方法结果的操作。
示例
:
@After("execution(* com.example.service.*.*(..))") public void afterAdvice() { System.out.println("After method execution"); }
1
2
3
4- 不管方法是成功返回还是抛出了异常,都会打印日志。
# afterReturning
(返回通知)
执行时机:方法执行 成功返回结果后 才会执行(即未抛出异常时)。
适合用于需要处理方法返回值的场景。
示例
:
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result") public void afterReturningAdvice(Object result) { System.out.println("Method returned with result: " + result); }
1
2
3
4- 只有方法正常返回时才会打印结果日志。
# 谁更“后”一点?
after
比afterReturning
更后,因为:
afterReturning
只会在方法正常返回后执行;after
无论方法是正常返回还是抛出异常,都会在方法执行完成的最后时刻触发。
# 执行时机对比
以下是执行流程示例:
# 方法执行的四种阶段:
前置通知:
@Before
目标方法执行
后置通知
:
- 如果是正常返回:
@AfterReturning
->@After
- 如果是异常:
@AfterThrowing
->@After
- 如果是正常返回:
# 对比:
通知类型 | 执行条件 | 触发时机 |
---|---|---|
@After | 无论方法是否抛异常 | 方法执行结束后 |
@AfterReturning | 方法正常返回时才触发 | 方法执行结束后 |
# 示例说明
假设目标方法如下:
public String testMethod() {
return "success";
}
1
2
3
2
3
- 如果执行正常:
- 执行顺序:
@Before
-> 方法体 ->@AfterReturning
->@After
- 执行顺序:
- 如果方法抛出异常:
- 执行顺序:
@Before
-> 方法体 ->@AfterThrowing
->@After
- 执行顺序:
# 总结
@After
的触发范围更广,始终是最后一个执行的通知。@AfterReturning
只在方法正常返回时触发,发生在@After
之前。
# 为什么使用先创建表
如果我们采用的是时间纬度的分表,可以创建12个表对应的12个月,如果采用实时创建的话对于创建时机可能要仔细定夺,所以还不如直接给它创建完
# 注解实现
package com.todoitbo.baseSpringbootDasmart.annotation;
import java.lang.annotation.*;
/**
* 自定义操作日志注解
*
* @author bo
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
/**
* 功能模块
*
* @return java.lang.String
*/
String operModule() default "";
/**
* 请求类型
*
* @return java.lang.String
*/
String operType() default "";
/**
* description: describe
* @return java.lang.String
* @since 2024/11/22
*/
String describe() default "";
}
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
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
# AOP实现
/**
* 设置操作日志切入点 记录操作日志 在注解的位置切入代码
*/
@Pointcut("@annotation(com.todoitbo.baseSpringbootDasmart.annotation.SysLog)")
public void operLogPointCut() {
}
/**
* 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
*
* @param joinPoint 切入点
* @param keys 返回结果
*/
@AfterReturning(value = "operLogPointCut()", returning = "keys")
public void saveOperLog(JoinPoint joinPoint, Object keys) {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes
.resolveReference(RequestAttributes.REFERENCE_REQUEST);
// 获取用户信息
SysLoginInfo sysLoginInfo = LoginContextUtil.getSysLoginInfo();
SysOperationLog sysOperationLog = new SysOperationLog();
try {
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取操作
SysLog opLog = method.getAnnotation(SysLog.class);
if (opLog != null) {
sysOperationLog.setOpDescribe(opLog.describe());
sysOperationLog.setModule(opLog.operModule());
}
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
// 获取请求的方法名
String methodName = method.getName();
methodName = className + "." + methodName;
sysOperationLog.setId(IdUtil.getSnowflake().nextId());
sysOperationLog.setMethod(methodName);
sysOperationLog.setRequest(JSONObject.toJSONString(joinPoint.getArgs()));
sysOperationLog.setResponse(JSONObject.toJSONString(keys));
sysOperationLog.setUrl(request.getRequestURI());
sysOperationLog.setIp(IpUtil.getIpAddr(request));
sysOperationLog.setUserId(ObjectUtil.isNotEmpty(sysLoginInfo) ? sysLoginInfo.getSysUser().getId() : 0);
sysOperationLog.setUserName(ObjectUtil.isNotEmpty(sysLoginInfo) ? sysLoginInfo.getSysUser().getUserName() : "无登录信息!");
sysOperationLogService.insertSysOperationLog(sysOperationLog);
} catch (Exception e) {
throw new BusinessException("保存操作日志失败!");
}
}
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
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
# 效果展示
依次调用post和get方法