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) {