转载

从零搭建自己的SpringBoot后台框架(十五)

Hello大家好,本章我们添加aop异步记录日志功能 。有问题可以联系我mr_beany@163.com。另求各路大神指点,感谢

一:添加AOP依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

二:创建自定义注解和切面

创建core→aop文件夹

在aop文件夹下中创建注解,其中remark为记录的备注

package com.example.demo.core.aop;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AnnotationLog {
    String remark() default "";

}

创建切面

注:关于 SystemLog 类,之前 集成generator自动生成model,xml,dao功能这篇文章 提到过的,忘记的可以再去看一下

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上添加我们刚创建的注解

@PostMapping("/selectById")
@AnnotationLog(remark = "查询")
public RetResult<UserInfo> selectById(@RequestParam String id) {
    UserInfo userInfo = userInfoService.selectById(id);
    return RetResponse.makeOKRsp(userInfo);
}

四:测试

输入localhost:8080/userInfo/selectById

拿到结果

从零搭建自己的SpringBoot后台框架(十五)

查看数据库System_log

从零搭建自己的SpringBoot后台框架(十五)

可以看到我们的日志

五:优化

我们知道,日志系统主要是方便我们分析程序,定位异常的。但是对于用户来说,用户一点都不在乎,所以我们不能因为需要记录日志,而延长用户的等待时间,所以,这里我们创建队列来异步执行记录日志操作

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

从零搭建自己的SpringBoot后台框架(十五)

注意:这里是立刻返回结果,并没有等待10秒,10秒之后打开数据库,得到我们刚操作的日志

项目地址

gitee.com/beany/mySpr…

写文章不易,如对您有帮助,请帮忙点下star 从零搭建自己的SpringBoot后台框架(十五)

结尾

添加aop异步记录日志功能已完成,后续功能接下来陆续更新,有问题可以联系我mr_beany@163.com。另求各路大神指点,感谢大家。

原文  https://juejin.im/post/5b0c01fd51882515870fcd55
正文到此结束
Loading...