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

2,346 阅读3分钟

业务场景

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

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

搭建Starter项目

  • rest-log-spring-boot-start
  • 自定义注解 RestLog
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface RestLog {
    
    /**
     * 模块名称 
     */
    String module();

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

    /**
     * 描述
     */
    String describe() default "";
}
我们业务场景需要记录用户操作的模块,做了什么操作,描述信息为拓展字段,可以根据实际需求增加更多信息。
  • 定义日志对象
@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;
}
  • 定义切面
@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依赖

<dependency>
   <groupId>com.demo</groupId>
   <artifactId>rest-log-spring-boot-start</artifactId>
   <version>0.0.1-SNAPSHOT</version>
</dependency>

添加配置到application.yaml

rest-log:
  topic: rest_log

在接口上添加注解

@RestController
@RequestMapping("/demo")
public class DemoController {
    @PostMapping
    @RestLog(module = "测试模块", action = "新增", describe = "这是一个描述")
    public void add(@RequestBody Demo demo) {
        // 省略实现
    }
}

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

@Service
public class LogListener {
    @KafkaListener(topics = "iot-original-history-message", containerFactory = "batchConsumerFactory")
    public void listener(List<ConsumerRecord<String, String>> records) {
        // 遍历记录 实现存储业务即可 省略
    }
}

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