Spring Boot 实现记录操作日志
1、需求分析
最近产品经理提出“要查看用户操作日志"的需求。于是就想到了用AOP来实现,为了提高效率,记录日志使用异步,日志的记录可选择保存在mysql或者MongoDB中。
2、代码实现
1、添加AOP依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、日志注解
package com.zjson.together.mall.admins.annotation;
import java.lang.annotation.*;
/**
* 系统日志注解
* @author zj
* @date 2020 2020/10/10 15:18
* @describe
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AddSysLog {
/**
* 操作名
* @return
*/
String value() default "操作日志";
/**
* 添加到数据库中
* @return
*/
boolean intoDB() default true;
/**
*添加到缓存
* @return
*/
boolean intoCache() default false;
}
3、日志的切面处理
package com.zjson.together.mall.admins.annotation;
import com.google.gson.Gson;
import com.zjson.together.common.utils.HttpContextUtils;
import com.zjson.together.common.utils.IpUtils;
import com.zjson.together.mall.admins.entity.SysLog;
import com.zjson.together.mall.admins.service.SysLogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
/**
* @describe 系统日志 切面处理类
* @author zj
* @date 2020 2020/10/13 15:23
*/
@Slf4j
@Aspect
@Component
public class SysLogAspect {
@Resource
private SysLogService sysLogService;
@Pointcut("@annotation(com.zjson.together.mall.admins.annotation.AddSysLog)")
public void sysLogPointCut() {
}
@Around("sysLogPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
//执行方法
Object result = point.proceed();
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
//设置IP地址
String ip = IpUtils.getIpAddr(HttpContextUtils.getHttpServletRequest());
//保存日志
CompletableFuture.runAsync(() -> saveSysLog(point, time, ip)) ;
return result;
}
/**
* 保存日志
* @param joinPoint
* @param time
*/
private void saveSysLog(ProceedingJoinPoint joinPoint, long time, String ip) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SysLog sysLog = new SysLog();
sysLog.setIp(ip);
AddSysLog addSysLog = method.getAnnotation(AddSysLog.class);
if (addSysLog != null) {
//注解上的描述
sysLog.setOperation(addSysLog.value());
}
//请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
sysLog.setMethod(className + "." + methodName + "()");
//请求的参数
Object[] args = joinPoint.getArgs();
try {
String params = new Gson().toJson(args);
sysLog.setParams(params);
//用户名
String userName = getUser();
sysLog.setUserName(userName);
sysLog.setTime(time);
sysLog.setCreateTime(new Date());
sysLog.setId(UUID.randomUUID().toString());
//保存系统日志
if (addSysLog.intoDB()) {
sysLogService.save(sysLog);
}
if (addSysLog.intoCache()) {
}
} catch (Exception ignored) {
log.error(ignored.getMessage());
}
}
}
4、用到的工具类
HttpContextUtils
package com.zjson.together.common.utils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* HttpContextUtils
*
* @author zj
*/
public class HttpContextUtils {
public static HttpServletRequest getHttpServletRequest() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
public static String getDomain() {
HttpServletRequest request = getHttpServletRequest();
StringBuffer url = request.getRequestURL();
return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString();
}
public static String getOrigin() {
HttpServletRequest request = getHttpServletRequest();
return request.getHeader("Origin");
}
}
IpUtils
package com.zjson.together.common.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest;
/**
* IP地址
*
* @author zj
*/
@Slf4j
public class IpUtils {
/**
* 获取IP地址
* <p>
* 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
* 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
*/
public static String getIpAddr(HttpServletRequest request) {
String srtUnknown = "unknown";
String ip = null;
try {
ip = request.getHeader("x-forwarded-for");
if (StringUtils.isEmpty(ip) || srtUnknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || ip.length() == 0 || srtUnknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || srtUnknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (StringUtils.isEmpty(ip) || srtUnknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (StringUtils.isEmpty(ip) || srtUnknown.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
} catch (Exception e) {
log.error("IPUtils ERROR ", e);
}
return ip;
}
}
5、使用
直接在方法头上加上注解
@ApiOperation("分页查询(排序)")
@GetMapping("/list")
@AddSysLog("分页查询")
public Resp<PageVo> list(QueryCondition queryCondition) {