spring aop使用异步的方式批量处理系统日志

在软件开发的过程中,进行系统日志记录是必不可少的,我利用了Spring aop 自定义注解  面向切面编程 队列 异步 批量处理的方式 完成了我们系统的日志记录,提升了日志记录的性能。

首先 :编写自定义注解,使用该注解注解在controller层的方法上,便于后续的aop记录日志。

/**
 * @author 徐塬峰 2019/4/14
 * 以自定义注解的方式记录日志
 * 注解在方法级别上 运行时启动
 **/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
    String value() default "";
}

日志记录的实体类,可以根据自己的业务来进行修改。

package com.gaoshu.web.audit;

import java.util.Date;

/**
 * 日志记录实体层
 * @author 徐塬峰
 * @email [email protected]
 * @date 2019-05-13
 * @description
 **/
public class AuditLog {

    private String id;
    private String description;
    private String method;
    private String type;
    private String requestIp;
    private String exceptionCode;
    private String exceptionDetail;
    private String params;
    private Date createDate;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getRequestIp() {
        return requestIp;
    }

    public void setRequestIp(String requestIp) {
        this.requestIp = requestIp;
    }

    public String getExceptionCode() {
        return exceptionCode;
    }

    public void setExceptionCode(String exceptionCode) {
        this.exceptionCode = exceptionCode;
    }

    public String getExceptionDetail() {
        return exceptionDetail;
    }

    public void setExceptionDetail(String exceptionDetail) {
        this.exceptionDetail = exceptionDetail;
    }

    public String getParams() {
        return params;
    }

    public void setParams(String params) {
        this.params = params;
    }


    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    @Override
    public String toString() {
        return "AuditLog{" +
                "id='" + id + '\'' +
                ", description='" + description + '\'' +
                ", method='" + method + '\'' +
                ", type='" + type + '\'' +
                ", requestIp='" + requestIp + '\'' +
                ", exceptionCode='" + exceptionCode + '\'' +
                ", exceptionDetail='" + exceptionDetail + '\'' +
                ", params='" + params + '\'' +
                ", createDate=" + createDate +
                '}';
    }
}

日志的存放的队列编写

package com.gaoshu.web.audit;

import org.springframework.stereotype.Component;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * @author 徐塬峰
 * @email [email protected]
 * @date 2019-05-13
 * @description
 **/
@Component
public class AuditLogQueue {
    //    BlockingDequeu为双端阻塞队列,blockingQueue阻塞队列
    private BlockingQueue blockingQueue = new LinkedBlockingQueue();

    public void add(AuditLog auditLog) {
        blockingQueue.add(auditLog);
    }
    //poll从队列的头部获取到信息
    public AuditLog poll() throws InterruptedException {
        return blockingQueue.poll(1, TimeUnit.SECONDS);//每秒钟执行一次
    }


}

紧接着:编写系统日志的消费者。可以在DEFAULT_BATCH_SIZE配置当数量达到多少时,进行消费。

/**
 * @author 徐塬峰
 * @email [email protected]
 * @date 2019-05-13
 * @description 消息队列的消费者
 * 日志保存的线程
 **/
@Component
public class AuditLogConsumer implements Runnable {

    private static Logger logger = LoggerFactory.getLogger(AuditLogConsumer.class);
    private static final int DEFAULT_BATCH_SIZE = 16;
    private AuditLogQueue auditLogQueue;
    private AuditLogService auditLogService;
    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 auditLogList = new ArrayList();
        try {
            int size = 0;
            while (size < batchSize) {
                AuditLog auditLog = auditLogQueue.poll();//从队列中取出一个
                if (auditLog == null) {
                    break;
                }
                auditLogList.add(auditLog);
                size++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            logger.error(e.toString());
        }
        //如果当前的日志list不为空
        if (!auditLogList.isEmpty()) {
           // auditLogService.batchLog(auditDtos);
            auditLogService.batchLog(auditLogList);
            //在这里执行了servivce的方法
        }
    }

    @Resource
    public void setAuditLogQueue(AuditLogQueue auditLogQueue) {
        this.auditLogQueue = auditLogQueue;
    }
    @Resource
    public void setAuditLogService(AuditLogService auditLogService) {
        this.auditLogService = auditLogService;
    }

    public void setBatchSize(int batchSize) {
        this.batchSize = batchSize;
    }

然后是service层的编写:

package com.gaoshu.web.audit;

import com.gaoshu.mapper.AuditLogMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author 徐塬峰
 * @email [email protected]
 * @date 2019-05-13
 * @description
 **/
@Service
public class AuditLogService {


    @Autowired
    private AuditLogMapper mapper;

    /**
     * 对消息队列的日志进行批量处理
     *
     * @param auditLogList
     */
    public void batchLog(List auditLogList) {
        auditLogList.parallelStream().forEach((e)->{
            System.out.println(e.toString());
            mapper.add(e);
        });
    }
}

配置aop切面

/**
 * @author 徐塬峰 2019/3/31
 * 使用AOP面向切面编程的方式实现登录拦截 将请求跳转到登录页面
 */
@Aspect
@Component
public class LogAspect {

    @Autowired
    private AuditLogQueue auditLogQueue;
    private static final Logger logger = LogManager.getLogger("用户操作日志");

    @Before("@annotation(com.gaoshu.web.annotation.SysLog)")
    public Object SysLog(JoinPoint joinPoint) throws Throwable {
        Object[] ob = joinPoint.getArgs();
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpSession session = request.getSession();
        AdminUserVo user = (AdminUserVo) session.getAttribute("adminUser");
        String username = null;
        String ip = IpUtils.getRemoteIP(request);
        if (user != null) {
            username = user.getUsername();
        }
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof ServletRequest) {
                args[i] = "request对象";
            } else if (args[i] instanceof ServletResponse) {
                args[i] = "response对象";
            } else if (args[i] instanceof MultipartFile) {
                args[i] = "MultipartFile对象";
            } else if (args[i] instanceof BindingResult) {
                args[i] = "BindingResult对象";
            }
        }
        //控制台输出
       // logger.info("用户id:{},方法签名:{},方法参数:{} ip地址:{}", username, joinPoint.getSignature(), JsonUtils.toJson(args), ip);
        //*========数据库日志=========*//
        AuditLog log = new AuditLog();
        log.setId(Sid.nextShort());
        log.setDescription(joinPoint.getSignature().toString());
        log.setMethod(joinPoint.getSignature().toString());
        log.setParams(JsonUtils.toJson(args));
        log.setType("0");
        log.setRequestIp(ip);
        log.setExceptionCode( null);
        log.setExceptionDetail( null);
        log.setCreateDate(new Date());
        //保存数据库
        auditLogQueue.add(log);
        //拿到请求中的所有参数args
        return ob;
    }


}

controller层处理:使用@SysLog注解在controller层的方法上即可

@SysLog
@ResponseBody
@PostMapping("/resetPassword.do")
public JSONResult resetPassword(HttpServletRequest request, String password, String newPassword) {
    AdminUserVo adminUserVo = (AdminUserVo) request.getSession().getAttribute("adminUser");
    if (adminUsersService.resetPassword(adminUserVo, password, newPassword)) {
        return JSONResult.ok("ok");
    }
    return JSONResult.errorMsg("error");
}

效果展示:

 

代码地址:https://github.com/RAOE/next1-boot

以上就是我归纳的对日志记录的实现,如果有哪些地方有问题,欢迎大家留言指正,感激不尽!

你可能感兴趣的:(Java,多线程与并发编程,spring,数据结构与算法,Springboot)