项目场景:
最近维护项目的时候,需要增加一个日志模块来记录用户每次的操作请求,一开始习惯的使用了aop切面功能,后面发现要配置整个项目的话,需要在每个请求前都加一个自定义注解才能实现切面日志记录,不太实际,后面选用了shiro框架配合拦截器就能实现全局的日志写入,下面简单的说下这两种方法步骤吧。
方法一:AOP实现请求日志记录:
1.导入maven
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.6.1</version>
</dependency>
2.创建对应的日志实体类和自定义注解
根据项目需求创建相应的实体类,接着创建自定义注解。
/**
* @author wangCj
* @date 2021/12/13
* 操作日志自定义注解类
*/
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented //生成文档
public @interface OperationLog {
String value() default "";
}
3.创建对应的aop切面类
/**
* @author wangCj
* @date 2021/12/13
* 系统日志:切面处理类
*/
@Aspect
@Component
public class OperationLogAspect {
//定义切点 @Pointcut
//在注解的位置切入代码
@Pointcut("@annotation(com.ylzinfo.framework.log.loginlog.domain.OperationLog)")
public void logPoinCut() {
}
//切面 配置通知
@AfterReturning("logPoinCut()")
public void writeLog(JoinPoint joinPoint) {
//保存日志
Log log = new Log();
//从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取切入点所在的方法
Method method = signature.getMethod();
//获取操作
OperationLog operationLog = method.getAnnotation(OperationLog.class);
if (operationLog != null) {
String value = operationLog.value();
log.setOperation(value);//保存获取的操作
}
//获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
//获取请求的方法名
String methodName = method.getName();
log.setMethod(className + "." + methodName);
//请求的参数
Object[] args = joinPoint.getArgs();
//将参数所在的数组转换成json
String params = JSON.toJSONString(args);
log.setParams(params);
log.setCreateDate(new Date());
//获取用户账号、ip地址、uuid
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
String name = session.getAttribute("key").toString();
log.setUsername(name);
String host = subject.getSession().getHost();
log.setIp(host);
log.setId(UUID.randomUUID().toString().replace("-",""));
//对应service 将日志对象log写入数据库
}
}
4.在需要记录日志的请求上添加自定义注解
@RequestMapping(value = "/findAll")
@ResponseBody
@RequiresPermissions("log:loginlog:view")
@OperationLog(value = "查询登录日志") //AOP的自定义注解
public AjaxPageResponse query(@RequestParam(required = false) String username,
@RequestParam(required = false) String status,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date logindatebegin,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date logindateend,
AjaxPageResponse page) {
loginLogRecordService.query(username, status,logindatebegin,logindateend, page);
return page;
}
方法二:shiro框架配合拦截器实现日志写入:
1.引入shiro框架后,配置相应文件spring-shiro-kisso.xml
2.spring-mvc中如下配置
3.对应的AccessLogInterceptor类
package com.ylzinfo.framework.log.accesslog.interceptor;
import com.ylzinfo.eva.core.util.IpUtils;
import com.ylzinfo.framework.log.accesslog.domain.AccessLog;
import com.ylzinfo.framework.log.accesslog.service.AccessLogService;
import com.ylzinfo.framework.log.loginlog.service.SysLogService;
import com.ylzinfo.framework.sys.utils.UserUtils;
import com.ylzinfo.framework.util.LogUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
public class AccessLogInterceptor implements HandlerInterceptor {
@Autowired(required = false)
private AccessLogService logService;
@Autowired(required = false)
private SysLogService sysLogService;
private static final ThreadLocal<Date> startTimeThreadLocal = new NamedThreadLocal<Date>("ThreadLocal StartTime");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
startTimeThreadLocal.set(new Date()); // 线程绑定变量(该数据只有当前请求的线程可见)
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
if (logService == null) {
return;
}
String sessionid = request.getRequestedSessionId().trim();
String ip = IpUtils.getIpAddr(request).trim();
String accept = request.getHeader("accept").trim();
String referer = request.getHeader("Referer").trim();
String url = request.getRequestURI().trim();
String method = request.getMethod().trim();
String headers = LogUtils.getHeaders(request).trim();
String useragent = request.getHeader("User-Agent").trim();
String params = LogUtils.getParams(request).trim();
Date begindate = startTimeThreadLocal.get();// 得到线程绑定的局部变量(开始时间)
Date enddate = new Date(); // 2、结束时间
long c = enddate.getTime() - begindate.getTime();
double time = c / 1000;
AccessLog log = new AccessLog(sessionid, ip, accept, referer, url, method, headers, useragent, params, begindate, enddate, time);
log.setUserid(UserUtils.getUserid());
log.setUsername(UserUtils.getUsername().trim());
if (ex != null) {
log.setExceptionname(ex.getClass().getName());
log.setException(ExceptionUtils.getStackTrace(ex));
}
startTimeThreadLocal.remove();
sysLogService.save(log);
}
}
实现的功能界面截图如下
总结
由于是已经上线的项目,在接口都已经编写好的情况下,我更倾向于用第二种方法来实现请求的日志写入功能。