前言
很久以前都想写这篇文章,一直没有空,但直到现在我对当时的情景还有印象,之所以有印象是因为需求很简单,业务操作日志的记录与查询的功能,但是具体实现真的很烂,具体的烂法会在反面示例里细说,领导以及客户层面很认可,一系列迷之操作,让我印象深刻。
需求描述与分析
客户侧提出需求很简单:要对几个关键的业务功能进行操作日志记录,即什么人在什么时间操作了哪个功能,操作前的数据报文是什么、操作后的数据报文是什么,必要的时候可以一键回退。
日志在业务系统中是必不可少的一个功能,常见的有系统日志、操作日志等:
系统日志
这里的系统日志是指的是程序执行过程中的关键步骤,根据实际场景输出的debug、info、warn、error等不同级别的程序执行记录信息,这些一般是给程序员或运维看的,一般在出现异常问题的时候,可以通过系统日志中记录的关键参数信息和异常提示,快速排除故障。
操作日志
操作日志,是用户实际业务操作行为的记录,这些信息一般存储在数据库里,如什么时间哪个用户点了某个菜单、修改了哪个配置等这类业务操作行为,这些日志信息是给普通用户或系统管理员看到。
通过对需求的分析,客户想要是一个业务操作日志管理的功能:
1、记录用户的业务操作行为,记录的字段有:操作人、操作时间、操作功能、日志类型、操作内容描述、操作内容报文、操作前内容报文
2、提供一个可视化的页面,可以查询用户的业务操作行为,对重要操作回溯;
3、提供一定的管理功能,必要的时候可以对用户的误操作回滚;
反面实现
明确需求后,就是怎么实现的问题了,这里先上一个反面的实现案例,也是因为这一个反面案例,才让我对这个简单的需求印象深刻。
这里我以一个人员管理的功能为例还原一下,当时的具体实现:
1、每个接口里都加一段记录业务操作日志的记录;
2、每个接口里都要捕获一下异常,记录异常业务操作日志;
下面是伪代码:
@RestController
@Slf4j
@BusLog(name = "人员管理")
@RequestMapping("/person")
public class PersonController2 {
@Autowired
private IPersonService personService;
@Autowired
private IBusLogService busLogService;
//添加人员信息
@PostMapping
public Person add(@RequestBody Person person) {
try{
//添加信息信息
Person result = this.personService.registe(person);
//保存业务日志
this.saveLog(person);
log.info("//增加person执行完成");
}catch(Exception e){
//保存异常操作日志
this.saveExceptionLog(e);
}
return result;
}
}
这种通过硬编码实现的业务操作日志管理功能,最大的问题就是业务操作日志收集与业务逻辑耦合严重,和代码重复,新开发的接口在完成业务逻辑后要织入一段业务操作日志保存的逻辑,已开发上线的接口,还要重新再修改织入业务操作日志保存的逻辑并测试,且每个接口需要织入的业务操作日志保存的逻辑是一样的。
设计思路
如果对AOP有一些印象的话,最好的方法就是使用aop实现:
1、定义业务操作日志注解,注解内可以定义一些属性,如操作功能名称、功能的描述等;
2、把业务操作日志注解标记在需要进行业务操作记录的方法上(在实际业务中,一些简单的业务查询行为通常没有必要记录);
3、定义切入点,编写切面:切入点就是标记了业务操作日志注解的目标方法;切面的主要逻辑就是保存业务操作日志信息;
Spring AOP
AOP (Aspect Orient Programming),直译过来就是 面向切面编程,