Hello你們好,本章咱們添加aop異步記錄日誌功能 。有問題能夠聯繫我mr_beany@163.com。另求各路大神指點,感謝
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>複製代碼
建立core→aop文件夾java
在aop文件夾下中建立註解,其中remark爲記錄的備註git
package com.example.demo.core.aop;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AnnotationLog {
String remark() default "";
}複製代碼
建立切面github
注:關於SystemLog
類,以前集成generator自動生成model,xml,dao功能這篇文章提到過的,忘記的能夠再去看一下web
package com.example.demo.core.aop;
import com.alibaba.fastjson.JSON;
import com.example.demo.core.systemlog.SystemLogQueue;
import com.example.demo.core.ret.ServiceException;
import com.example.demo.core.utils.ApplicationUtils;
import com.example.demo.model.SystemLog;
import com.example.demo.model.UserInfo;
import com.example.demo.service.SystemLogService;
import org.apache.ibatis.javassist.*;
import org.apache.ibatis.javassist.bytecode.CodeAttribute;
import org.apache.ibatis.javassist.bytecode.LocalVariableAttribute;
import org.apache.ibatis.javassist.bytecode.MethodInfo;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @Description: aop記錄操做日誌
* @author 張瑤
* @date 2018年5月28日 18:43:28
*/
@Aspect
@Component
public class AspectLog {
private static final Logger logger = LoggerFactory.getLogger(AspectLog.class);
@Resource
private SystemLogService systemLogService;
/**
* 定義切點
*/
@Pointcut("@annotation(com.example.demo.core.aop.AnnotationLog)")
public void methodCachePointcut() {
}
@Before("methodCachePointcut()")
public void doBefore(JoinPoint p) throws Exception{
SystemLog systemLog = getSystemLogInit(p);
systemLog.setLogType(SystemLog.LOGINFO);
systemLogService.insert(systemLog);
}
/**
* 調用後的異常處理
* @param p
* @param e
*/
@AfterThrowing(pointcut = "methodCachePointcut()", throwing = "e")
public void doAfterThrowing(JoinPoint p, Throwable e) throws Throwable {
//業務異常不用記錄
if(!(e instanceof ServiceException)) {
try {
SystemLog systemLog =getSystemLogInit(p);
systemLog.setLogType(SystemLog.LOGERROR);
systemLog.setExceptionCode(e.getClass().getName());
systemLog.setExceptionDetail(e.getMessage());
systemLogService.insert(systemLog);
} catch (Exception ex) {
logger.error("==異常通知異常==");
logger.error("異常信息:{}", ex.getMessage());
}
}
}
private SystemLog getSystemLogInit(JoinPoint p){
SystemLog systemLog = new SystemLog();
try{
//類名
String targetClass = p.getTarget().getClass().toString();
//請求的方法名
String tartgetMethod = p.getSignature().getName();
//獲取類名 UserController
String classType = p.getTarget().getClass().getName();
Class<?> clazz = Class.forName(classType);
String clazzName = clazz.getName();
//請求參數名+參數值的map
Map<String, Object> nameAndArgs = getFieldsName(this.getClass(), clazzName, tartgetMethod, p.getArgs());
systemLog.setId(ApplicationUtils.getUUID());
systemLog.setDescription(getMthodRemark(p));
systemLog.setMethod(targetClass+"."+tartgetMethod);
//你們可自行百度獲取ip的方法
systemLog.setRequestIp("192.168.1.104");
systemLog.setParams(JSON.toJSONString(nameAndArgs));
systemLog.setUserId(getUserId());
systemLog.setCreateTime(new Date());
}catch (Exception ex){
logger.error("==異常通知異常==");
logger.error("異常信息:{}", ex.getMessage());
}
return systemLog;
}
/**
* 經過反射機制 獲取被切參數名以及參數值
*
* @param cls
* @param clazzName
* @param methodName
* @param args
* @return
* @throws NotFoundException
*/
private Map<String, Object> getFieldsName(Class cls, String clazzName, String methodName, Object[] args) throws NotFoundException {
Map<String, Object> map = new HashMap<>();
ClassPool pool = ClassPool.getDefault();
ClassClassPath classPath = new ClassClassPath(cls);
pool.insertClassPath(classPath);
CtClass cc = pool.get(clazzName);
CtMethod cm = cc.getDeclaredMethod(methodName);
MethodInfo methodInfo = cm.getMethodInfo();
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
for (int i = 0; i < cm.getParameterTypes().length; i++) {
//HttpServletRequest 和HttpServletResponse 不作處理
if(!(args[i] instanceof HttpServletRequest || args[i] instanceof HttpServletResponse)){
//paramNames即參數名
map.put(attr.variableName(i + pos), JSON.toJSONString(args[i]));
}
}
return map;
}
/**
* 獲取方法的中文備註____用於記錄用戶的操做日誌描述
* @param joinPoint
* @return
* @throws Exception
*/
private static String getMthodRemark(JoinPoint joinPoint) throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] method = targetClass.getMethods();
String methode = "";
for (Method m : method) {
if (m.getName().equals(methodName)) {
Class[] tmpCs = m.getParameterTypes();
if (tmpCs.length == arguments.length) {
AnnotationLog methodCache = m.getAnnotation(AnnotationLog.class);
if (methodCache != null) {
methode = methodCache.remark();
}
break;
}
}
}
return methode;
}
private static String getUserId() {
String userId = "";
UserInfo userInfo = (UserInfo) SecurityUtils.getSubject().getPrincipal();
if(userInfo != null){
userId = userInfo.getId();
}
return userId;
}
}複製代碼
在UserInfoController的selectById上添加咱們剛建立的註解spring
@PostMapping("/selectById")
@AnnotationLog(remark = "查詢")
public RetResult<UserInfo> selectById(@RequestParam String id) {
UserInfo userInfo = userInfoService.selectById(id);
return RetResponse.makeOKRsp(userInfo);
}複製代碼
輸入localhost:8080/userInfo/selectById數據庫
拿到結果apache
查看數據庫System_logjson
能夠看到咱們的日誌bash
咱們知道,日誌系統主要是方便咱們分析程序,定位異常的。可是對於用戶來講,用戶一點都不在意,因此咱們不能由於須要記錄日誌,而延長用戶的等待時間,因此,這裏咱們建立隊列來異步執行記錄日誌操做app
1:建立批量添加方法
SystemLogMapper.xml
<insert id="insertByBatch" parameterType="java.util.List" >
insert into system_log (
id, description, method, log_type, request_ip, exception_code,
exception_detail, params, user_id, create_time
)
values
<foreach collection="list" item="item" index= "index" separator =",">
(
#{item.id,jdbcType=VARCHAR},
#{item.description,jdbcType=VARCHAR},
#{item.method,jdbcType=VARCHAR},
#{item.logType,jdbcType=VARCHAR},
#{item.requestIp,jdbcType=VARCHAR},
#{item.exceptionCode,jdbcType=VARCHAR},
#{item.exceptionDetail,jdbcType=VARCHAR},
#{item.params,jdbcType=VARCHAR},
#{item.userId,jdbcType=VARCHAR},
#{item.createTime,jdbcType=TIMESTAMP}
)
</foreach>複製代碼
SystemLogMapper.java
Integer insertByBatch(List<SystemLog> list);複製代碼
Service層可參考碼雲地址,這裏就不作示例
2:建立日誌的存放隊列
建立core→systemlog文件夾
在該文件夾下建立SystemLogQueue
package com.example.demo.core.systemlog;
import com.example.demo.model.SystemLog;
import org.springframework.stereotype.Component;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@Component
public class SystemLogQueue {
private BlockingQueue<SystemLog> blockingQueue = new LinkedBlockingQueue<>();
public void add(SystemLog systemLog) {
blockingQueue.add(systemLog);
}
public SystemLog poll() throws InterruptedException {
return blockingQueue.poll(1, TimeUnit.SECONDS);
}
}
複製代碼
3:建立日誌隊列的消費者
package com.example.demo.core.systemlog;
import com.example.demo.model.SystemLog;
import com.example.demo.service.SystemLogService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
@Component
public class SystemLogConsumer implements Runnable{
private static Logger logger = LoggerFactory.getLogger(SystemLogConsumer.class);
public static final int DEFAULT_BATCH_SIZE = 64;
private SystemLogQueue auditLogQueue;
private SystemLogService systemLogService;
private int batchSize = DEFAULT_BATCH_SIZE;
private boolean active = true;
private Thread thread;
@PostConstruct
public void init() {
thread = new Thread(this);
thread.start();
}
@PreDestroy
public void close() {
active = false;
}
@Override
public void run() {
while (active) {
execute();
}
}
public void execute() {
List<SystemLog> systemLogs = new ArrayList<>();
try {
int size = 0;
while (size < batchSize) {
SystemLog systemLog = auditLogQueue.poll();
if (systemLog == null) {
break;
}
systemLogs.add(systemLog);
size++;
}
} catch (Exception ex) {
logger.info(ex.getMessage(), ex);
}
if (!systemLogs.isEmpty()) {
try {
//休眠10秒來模擬業務複雜,正在計算,測試以後你們別忘記刪除這句話
Thread.sleep(10000);
systemLogService.insertByBatch(systemLogs);
}catch (Exception e){
logger.error("異常信息:{}", e.getMessage());
}
}
}
@Resource
public void setAuditLogQueue(SystemLogQueue auditLogQueue) {
this.auditLogQueue = auditLogQueue;
}
@Resource
public void setAuditLogService(SystemLogService systemLogService) {
this.systemLogService = systemLogService;
}
public void setBatchSize(int batchSize) {
this.batchSize = batchSize;
}
}
複製代碼
4:修改切面AspectLog
將:
@Resource
private SystemLogService systemLogService;複製代碼
修改成:
@Resource
private SystemLogQueue systemLogQueue;複製代碼
將:
systemLogQueue.insert(systemLog);複製代碼
修改成:
systemLogQueue.add(systemLog);複製代碼
5:測試
輸入localhost:8080/userInfo/selectById
注意:這裏是馬上返回結果,並無等待10秒,10秒以後打開數據庫,獲得咱們剛操做的日誌
碼雲地址: gitee.com/beany/mySpr…
GitHub地址: github.com/MyBeany/myS…
寫文章不易,如對您有幫助,請幫忙點下star
添加aop異步記錄日誌功能已完成,後續功能接下來陸續更新,有問題能夠聯繫我mr_beany@163.com。另求各路大神指點,感謝你們。