基于AOP及自定注解,实现接口操作日志记录Starter 业

业务场景

最近开发项目中有一个用户查看操作日志需求,因为我们后端是多个SpringBoot微服务提供接口,于是就准备开发一个日志记录的Starter,具体方案就是基于AOP+自定义注解,记录用户的操作日志。

1
markdown复制代码* 我会通过代码注释的方式介绍每个业务,结合注释阅读更易理解

搭建Starter项目

  • rest-log-spring-boot-start
  • 自定义注解 RestLog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
less复制代码@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface RestLog {

/**
* 模块名称
*/
String module();

/**
* 操作
*/
String action();

/**
* 描述
*/
String describe() default "";
}
1
复制代码我们业务场景需要记录用户操作的模块,做了什么操作,描述信息为拓展字段,可以根据实际需求增加更多信息。
  • 定义日志对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码@Data // Lombok注解 
public class LogBean {
private Long id;
// 操作userId
private String userId;
// 模块
private String module;
// 操作
private String action;
// 描述信息
private String describe;
// 接口地址
private String api;
// GET POST DELETE PUT...
private String method;
// 接口参数信息
private String paramJson;
// 操作时间
private Date restTime;
}
  • 定义切面
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
70
71
72
73
scss复制代码@Aspect
@Component
public class RestLogAspect {

private static final Logger LOG = LoggerFactory.getLogger(RestOperationLogAspect.class);

@Autowired
private KafkaTemplate<String, String> kafkaTemplate;

/**
* 生成日志后发送的目标topic
*/
@Value("${rest-log.topic:}")
private String topic;

/**
* 切面
*/
@Pointcut("@annotation(com.demo.RestLog)")
private void restLogCut() {
// 操作日志切面
}

/**
* 执行后日志存储操作
*
* @param joinPoint
*/
@After("restLogCut()")
public void after(JoinPoint joinPoint) {
try {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 获取注解对象
OperationLog annotation = method.getAnnotation(RestLog.class);
// 获取请求对象 HttpServletRequest
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
// 请求经过网关 鉴权通过 网关会像请求头中添加userId信息
String userId = request.getHeader("Auth-Userid");
if(userId == null) {
// 内部请求不记录日志 微服务之间接口不仅仅给前端调用 服务间也会有业务调用 服务间调用不会经过网关 也就不会有用户信息
return;
}
// 获取参数名称
String[] names = signature.getParameterNames();
// 获取参数值
Object[] args = joinPoint.getArgs();
// 将接口请求参数封装成json 便于存储及解析
JSONObject paramJson = new JSONObject();
for(int i = 0; i <names.lenth; i++) {
paramJson.put(names[i], args[i]);
}
// 创建日志对象 用于传输日志信息
LogBean logBean = new LogBean();
logBean.setUserId(userId);
logBean.setModule(annotation.module());
logBean.setAction(annotation.action());
logBean.setDescribe(annotation.describe());
logBean.setApi(request.getRequestURI());
logBean.setMethod(request.getMethod());
logBean.setParamJson();
logBean.setRestTime(new Date());
// 判断是否配置发送的topic 配置了则进行发送
if(StringUtils.isNotBlank(topic)) {
// 将日志内容发送到kafka
kafkaTemplate.send(topic, JSONObject.toJSONString(logBean));
}
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
}
}

总结:1、切面业务中,进行了请求头的判断(判断是微服务间请求还是客户端请求,避免微服务间的rest请求被记入日志中);2、将请求参数,模块信息等等封装为一个实体,发送到kafka,降低日志业务对接口性能影响;3、采用kafka发送能够更加灵活的应用于微服务系统,日志管理服务监听topic,微服务集成Stater后配置这个topic,最终微服务的操作日志系统就可以做到快速集成,统一管理。

应用starter

springboot服务引入starter依赖

1
2
3
4
5
xml复制代码<dependency>
<groupId>com.demo</groupId>
<artifactId>rest-log-spring-boot-start</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

添加配置到application.yaml

1
2
less复制代码rest-log:
topic: rest_log

在接口上添加注解

1
2
3
4
5
6
7
8
9
java复制代码@RestController
@RequestMapping("/demo")
public class DemoController {
@PostMapping
@RestLog(module = "测试模块", action = "新增", describe = "这是一个描述")
public void add(@RequestBody Demo demo) {
// 省略实现
}
}

添加kafka监听(只需要在提供日志存储及查询业务的服务添加监听)

1
2
3
4
5
6
7
java复制代码@Service
public class LogListener {
@KafkaListener(topics = "iot-original-history-message", containerFactory = "batchConsumerFactory")
public void listener(List<ConsumerRecord<String, String>> records) {
// 遍历记录 实现存储业务即可 省略
}
}

Kafka批量消费配置可以参考之前文章有介绍

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%