序
对于日志的记录,大家肯定会考虑使用aop,但是aop能不能记录业务操作前后的数据呢?今天跟大家分享的就是数据业务级日志记录的2个方案
一、数据业务级日志是什么意思
我这里的意思其实就是业务操作前的数据、业务操作后的数据。意义在于,可以通过对比直观显示业务操作前后字段值变化情况,方便运维、客服人员查看,同时出现事故可以通过日志追责。
二、设计思路
1、使用aop,想办法记录业务操作前后的数据
2、业务方法调用日志保存方法
三、使用aop实现记录操作前后业务数据
aop的集成:
pom文件引入aop支持
切面类:
@Aspect
@Component
@Slf4j
public class AppLogAop {
@Pointcut("execution(public * com.xw.controller..*.*(..))")
public void Pointcut() {
}
//前置通知
@Before("Pointcut()")
public void beforeMethod(JoinPoint joinPoint){
log.info("调用了前置通知");
}
//@After: 后置通知
@After("Pointcut()")
public void afterMethod(JoinPoint joinPoint){
log.info("调用了后置通知");
}
//@AfterRunning: 返回通知 rsult为返回内容
@AfterReturning(value="Pointcut()",returning="result")
public void afterReturningMethod(JoinPoint joinPoint,Object result){
log.info("调用了返回通知");
}
//@AfterThrowing: 异常通知
@AfterThrowing(value="Pointcut()",throwing="e")
public void afterReturningMethod(JoinPoint joinPoint, Exception e){
log.info("调用了异常通知");
}
//@Around:环绕通知
@Around("Pointcut()")
public Object Around(ProceedingJoinPoint pjp) throws Throwable {
//TODO 切面记录日志:对于数据业务级的日志,缺陷是方法没有执行也会记录(方法里有很多校验)
/*
Signature sig = pjp.getSignature();
MethodSignature msig = null;
if (!(sig instanceof MethodSignature)) {
throw new IllegalArgumentException("该注解只能用于方法");
}
msig = (MethodSignature) sig;
Object target = pjp.getTarget();
Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
String methodName = currentMethod.getName();
Object targetName = pjp.getTarget().getClass().getName();
Object[] args = pjp.getArgs();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
OperateLog operateLog = OperateLogUtil.initOperateLog(methodName,targetName,args,request);
*/
log.info("around执行方法之前");
Object object = pjp.proceed();
log.info("around执行方法之后--返回值:" +object);
return object;
}
}
注意注解标签@Aspect、@Component,然后@Pointcut里注意设置controller包路径,具体的这里不细说。
主要看我的环绕事件Around,这个事件在Object object = pjp.proceed();前后做文章,在这之前是还没有执行业务方法,这之后是执行了。这里的思路:
1、取到方法名称、请求路径、controller等信息
2、定义一个枚举类,设置方法名、controller名、业务中文描述,凡是配置在这个枚举类的就是要记录的,否则就直接放过(更灵活一点的做法就是建表记录,考虑性能问题的化,可以将表记录的信息缓存到redis,避免查询数据库影响接口性能)
3、方法执行前初始一条日志记录数据,这个里面会根据controller、方法名称获取到对应的service或mapper,从入参获取id,查询业务数据。
4、方法执行后调用异步方法(使用多线程实现,上期分享讲过),同3一样查询数据(此时应该是已经操作过的数据,其实也可以直接在方法执行前就调用异步方法,查询的数据作为操作之前,用入参替换对应字段的值作为之后的数据,这样性能更优,对接口可以说是没有一点延迟影响)
5、保存生成的日志记录,这里操作之前、操作之后都使用json字符串记录数据,增加一个className,方便后面可以将json字符串转对象。
我的日志记录对象:
@Data
public class OperateLog {
/**
* 业务内部区分id
*/
private String difSourceId;
/**
* 流水号
*/
private Long operateNo;
/**
* 操作类别
*/
private String operateType;
/**
* 数据json串,操作之前
*/
private String beforeData;
/**
* 数据json串,操作之后
*/
private String afterData;
/**
* 请求url
*/
private String requestUrl;
/**
* 方法名称
*/
private String methodName;
/**
* 操作人id
*/
private Integer userId;
/**
* 操作人名称(冗余)
*/
private String userName;
/**
* 记录时间
*/
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private String recordTime;
/**
* 参数接收,开始时间
*/
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private String startTime;
/**
* 参数接收,接收时间
*/
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private String endTime;
/**
* 关键字
*/
private String paramKey;
/**
* 请求ip
*/
private String reqIp;
/**
* 对象class名称
*/
private String entityName;
/*
* 操作名称
*/
private String operateName;
}
字段上有注释,相信大家一看就懂。
四、业务系统调用
这个就更好解释了,实际就是每个方法里直接调用一个公共方法,传入之前、之后的json字符串、操作中文描述等,当然也是异步方法,不影响接口性能的。这个公共方法应该不用我贴代码大家就可以自己实现。
总结:
使用aop会简单点,每个service里的逻辑还是改怎么写就怎么写。但是如果遇到方法里操作多张业务数据表,那这就有点尴尬了。另外呢,方法里面还有参数校验嘛,可能参数校验后就不执行操作了呢?aop是无法感知是否真正执行了吧,也许有人说可以看返回结果啊,但是如果这样,你就不能开启异步方法了,对性能会有影响的,接口的执行时间肯定会边长。因此,我这里最终还是放弃了aop这种方法,使用了封装公共方法,之前批量初始日志,开启异步方法保存日志。
希望能启发到大家,有更好的思路还原评论指正。