说明:
一般情况下,我们项目都会保留用户的操作记录,而最简单有效的方案就是,aop切面,
但是:
使用aop切面保留用户操作记录的原理:
是采用代理模式实现了功能增强,
生成了一个代理对象,在原有的功能(也就是我们的业务逻辑)上,添加了日志记录的功能实现;
是的没问题,方案ok;
但是有一个问题:保存操作记录的这部分操作也是需要时间的,虽然正常情况下都很短,就是往数据库insert一条数据,但是还是会有效率的影响;
在极高并发的情况下,也是有可能因为插入操作记录的这段时间-导致整段业务的异常回滚;
所以:使用异步线程来优化aop切面;
代码如下:
1.切面类;
注意Pointcut切点配置时,配置目标拦截的包;
不要拦截了操作记录插入的操作,不然自己拦截了自己肯定就死循环了;
/**
* 系统日志,切面处理类
*
* @author
*/
@Aspect
@Component
public class SysLogAspect {
/**
* 日志处理类
*/
@Autowired
private DefaultWebLogHandler webLogHandler;
// @Pointcut("@annotation(com.msun.mrs.common.annotation.mrLog)")
@Pointcut("execution(* com.msun.mrs.business.server.*.controller.*.*(..))")
public void logPointCut() {
}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
//执行方法
Object result = point.proceed();
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
//保存日志
try {
saveSysLog(point, time);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
private void saveSysLog(ProceedingJoinPoint joinPoint, long time) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SysLogEntity sysLog = new SysLogEntity();
//请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
sysLog.setClassMethod(className);
sysLog.setClassMethod(methodName);
//获取request
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
if(request == null) return;
sysLog.setHttpMethod(request.getMethod());
//请求的参数
Object[] args = joinPoint.getArgs();
try {
if (ArrayUtils.isNotEmpty(args)) {
String params = new Gson().toJson(args[0]);
if(StringUtils.isNotEmpty(params) && params.length()<4096) {
sysLog.setRequestParams(params);
}else {
sysLog.setRequestParams(StringZipUtils.compactString(params));
}
}
} catch (Exception e) {
e.printStackTrace();
}
//设置IP地址
sysLog.setIp(IPUtils.getIpAddr(request));
//设置url
sysLog.setUrl(request.getRequestURL().toString());
//用户名
Long userId = ContextVariableThreadLocal.getUserId();
sysLog.setUserId(String.valueOf(userId));
sysLog.setTimeCost(Integer.parseInt(String.valueOf(time)));
sysLog.setCreateTime(new Date());
//保存系统日志
webLogHandler.persistenceLog(sysLog);
}
}
2.日志线程池处理器
其中线程池,简单操作就直接创建了:Executors.newFixedThreadPool(10);
如有需要也可以使用配置文件,项目加载的形式,一样的;
/**
* @author weixz
* @date 2021/3/26 9:00
*/
@Component
public class DefaultWebLogHandler implements WebLogHandler<SysLogEntity> {
@Autowired
private SysLogService sysLogService;
private static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
@Override
public void persistenceLog(SysLogEntity sysLogEntity){
fixedThreadPool.execute(new OperationLogThread<>(sysLogService,sysLogEntity));
// sysLogService.save(sysLogEntity);
}
}
3.日志线程任务
/**
* @author weixz
* @date 2021/3/26 8:55
*/
@Slf4j
public class OperationLogThread<T> implements Runnable {
private volatile SysLogService sysLogService;
private volatile SysLogEntity sysLogEntity;
public OperationLogThread(SysLogService sysLogService,
SysLogEntity sysLogEntity) {
this.sysLogService=sysLogService;
this.sysLogEntity=sysLogEntity;
}
@Override
public void run() {
try {
this.sysLogService.save(sysLogEntity);
} catch (Exception e) {
log.error("thread name "+Thread.currentThread().getName()+" save sysLog error",e);
log.error("save sysLog error Url: " + sysLogEntity.getUrl());
}finally {
sysLogEntity = null;
}
}
}
其中:
SysLogService : 操作记录对应的service;也可以直接用dao-save一样的,在此处的功能就是保存到数据库;
SysLogEntity : 操作记录对应的实体;
不做详细展示了,需要什么保留什么就行;