Springboot & Aop实现日志记录

280 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第24天,点击查看活动详情

使用Aop实现操作日志记录

1. 创建一个注解用来做日志记录

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SysOperaLog {
    String value() default "";
}

2. 准备Aop 操作类

package com.xk.bugvip.base.aop.log;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.xk.bugvip.base.aop.log.event.SysLogEvent;
import com.xk.bugvip.entity.SysLog;
import com.xk.bugvip.utils.R;
import com.xk.bugvip.utils.constant.LogConstant;
import com.xk.bugvip.utils.util.IpData;
import com.xk.bugvip.utils.util.IpGetAdders;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
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 java.time.Instant;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Objects;

@Aspect
@Order
@Component
public class LogAspect {
    private ThreadLocal<SysLog> sysLogThreadLocal = new ThreadLocal<>();
    /**
     * 事件发布是由ApplicationContext对象管控的,
     * 我们发布事件前需要注入ApplicationContext对象调用publishEvent方法完成事件发布
     **/
    @Resource
    private ApplicationContext applicationContext;


    /**
     * 业务操作断点位置 定义controller 切入点拦截规则 ,拦截SysLog注解得方法
     */
    @Pointcut("@annotation(com.xk.bugvip.base.aop.log.SysOperaLog)")
    public void sysLogAspect(){

    }

    /**
     * 拦截控制器得操作日志
     * @param joinpoint
     * @throws Throwable
     */
    @Before(value = "sysLogAspect()")
    public void recordLog(JoinPoint joinpoint ) throws Throwable{
        SysLog sysLog  =new SysLog();
        //将当前实体保存到threadLoacl
        sysLogThreadLocal.set(sysLog);
        //开始时间
        long beginTime = Instant.now().toEpochMilli();
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        sysLog.setActionUrl(URLUtil.getPath(request.getRequestURI()));
        sysLog.setStartTime(LocalDateTime.now().toString());
        //todo 设置操作人
        // sysLog.setUserName();
        //设置ip
        String ip = ServletUtil.getClientIP(request);
        sysLog.setIp(ip);
        //设置地址
        IpData ipData = IpGetAdders.doPostOrGet();
        sysLog.setLocation(ipData.getCname());
        sysLog.setRequestMethod(request.getMethod());
        //设置浏览器
            String header = request.getHeader("User-Agent");
        sysLog.setBrowser(UserAgentUtil.parse(header).getBrowser().toString());
        //设置操作系统
        sysLog.setOs(UserAgentUtil.parse(header).getOs().toString());
        //访问目标方法的参数  可动态改变参数值
        Object[] args=joinpoint.getArgs();
        //获取执行的方法名
        sysLog.setActionMethod(joinpoint.getSignature().getName());
        //类名
        sysLog.setClassPath(joinpoint.getTarget().getClass().getName());
        sysLog.setActionMethod(joinpoint.getSignature().getName());
        sysLog.setParams(Arrays.toString(args));
        String controllerMethodDescription = LogUtil.getControllerMethodDescription(joinpoint);
        sysLog.setDescription(controllerMethodDescription);
        long endTime = Instant.now().toEpochMilli();
        sysLog.setConsumingTime(endTime-beginTime);
    }

    /**
     * 返回通知
     * @param ret
     */
    @AfterReturning(returning = "ret",pointcut = "sysLogAspect()")
    public void doAfterReturning(Object ret) {
        SysLog sysLog = sysLogThreadLocal.get();
        //处理完请求 返回内容
        //处理添加日志
        sysLog.setRunType(LogConstant.LOG_INFO);
        sysLog.setLogType(LogConstant.OPERATION_LOG);
        sysLog.setFinishTime(LocalDateTime.now().toString());
        sysLog.setStatus(1);
        //sysLog.setReturnData(ret.toString());
        //发布事件
        applicationContext.publishEvent(new SysLogEvent(sysLog));
        //移除当前log实体
        sysLogThreadLocal.remove();

    }
    /**
     * 异常通知
     * @param e
     */
    @AfterThrowing(pointcut = "sysLogAspect()", throwing = "e")
    public void doAfterThrowable(Throwable e){
        SysLog sysLog = sysLogThreadLocal.get();
        //异常
        sysLog.setRunType(LogConstant.LOG_THORW);
        sysLog.setLogType(LogConstant.OPERATION_LOG);
        //异常对象
        sysLog.setExDetail(LogUtil.getStackTrace(e));
        //异常信息
        sysLog.setExDesc(e.getMessage());
        sysLog.setStatus(2);
        sysLog.setReturnData(e.toString());
        //发布事件
        applicationContext.publishEvent(new SysLogEvent(sysLog));
        //移除当前log实体
        sysLogThreadLocal.remove();
    }


}

3 另外我们这边用到了 ApplicationEvent

package com.xk.bugvip.base.aop.log.event;

import org.springframework.context.ApplicationEvent;

/**
 * @Description  系统日志事件
 * ApplicationEvent以及Listener是Spring为我们提供的一个事件监听、
 * 订阅的实现,内部实现原理是观察者设计模式,设计初衷也是为了系统业务逻辑之间的解耦,
 * 提高可扩展性以及可维护性。事件发布者并不需要考虑谁去监听,监听具体的实现内容是什么,
 * 发布者的工作只是为了发布事件而已。
 *
 */
public class SysLogEvent extends ApplicationEvent {

    /**
     * 我们自定义事件SysLogEvent继承了ApplicationEvent,继承后必须重载构造函数,构造函数的参数可以任意指定,
     * 其中source参数指的是发生事件的对象,一般我们在发布事件时使用的是this关键字代替本类对象,
     * 而sysLog参数是我们自定义的注册用户对象,该对象可以在监听内被获取。
     * @param source
     */
    public SysLogEvent(Object source) {
        super(source);
    }
}

4 创建监听类并交给容器管理 在AOP更新日志时 publishEvent会被监测到

@Slf4j
@Component
public class SysLogListener {

    @Autowired
    private ILogService sysLogService;

    /*
    让监听类被Spring所管理即可,在我们用户注册监听实现方法上添加@EventListener注解,
    该注解会根据方法内配置的事件完成监听。
     */
    @Async
    @Order
    @EventListener(SysLogEvent.class)
    public void saveSysLog(SysLogEvent event){
       SysLog sysLog=(SysLog) event.getSource();
       //保存日志
        sysLogService.save(sysLog);
    }
}

5 使用 @SysOperaLog注解 在controller上

@PostMapping("/getlist")
@SysOperaLog("获取日志列表")
public R getlist(@RequestBody ListVo parm){
    return  iLogService.getList(parm);
}

image.png

实践是检验真理的唯一方法! 明天见🥰🥰🥰