AOP日志(利用切面实现将请求信息保存到数据库)(可直接使用)(简单易懂)

演示

根据下边的项目结构,我是在_00common里面测试的

controller

在这里插入图片描述

数据库记录

会记录请求正常和请求异常,根据log_type区分,INFO正常,ERROR异常
在这里插入图片描述

介绍

在controller上边加@Log注解,里面输入接口的信息,调用完接口之后会直接保存到数据库里面,如果要使用的话直接引入这个jar包就可以,然后直接使用

代码

测试调用接口就不写了,直接看切面实现代码

项目结构

在这里插入图片描述

添加依赖

还需要引入一些启动依赖,比如数据库依赖啊,mybatisplus依赖,springboot依赖啊,这些自行引入

<dependencies>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.14.0</version>
        </dependency>
        <dependency>
            <groupId>eu.bitwalker</groupId>
            <artifactId>UserAgentUtils</artifactId>
            <version>1.21</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>
    </dependencies>

日志注解接口

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

日志注解实现

import com.alibaba.fastjson.JSON;
import com.baicaizhi.utils.RequestHolder;
import com.baicaizhi.utils.StringUtils;
import com.baicaizhi.utils.ThrowableUtils;
import com.baicaizhi.entity.SysLog;
import com.baicaizhi.service.ISysLogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Component
@Aspect
@Slf4j
@RequiredArgsConstructor
public class LogAspect {

    private final ISysLogService iSysLogService;
    private static ThreadLocal<Long> currentTime = new ThreadLocal<>();

    /**
     * 配置切入点
     */
    @Pointcut("@annotation(com.baicaizhi.annotation.Log)")
    public void logPointcut() {
        // 该方法无方法体,主要为了让同类中其他方法使用此切入点
    }

    /**
     * 配置环绕通知,使用在方法logPointcut()上注册的切入点
     *
     * @param joinPoint join point for advice
     */
    @Around("logPointcut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result;

        // 设置当前时间戳
        currentTime.set(System.currentTimeMillis());

        // 参数值
        List<Object> argValues = new ArrayList<>(Arrays.asList(joinPoint.getArgs()));
        String paramStr;
        try {
            // 将参数值转换为JSON字符串
            paramStr = JSON.toJSONString(argValues);
        } catch (Exception e1) {
            // 如果转换失败,则使用参数值的toString方法生成字符串
            paramStr = argValues.toString();
        }

        result = joinPoint.proceed();
        // 创建SysLog对象,记录执行时间
        //SysLog sysLog = new SysLog("INFO",System.currentTimeMillis() - currentTime.get());
        SysLog sysLog = new SysLog("INFO", System.currentTimeMillis());
        currentTime.remove();
        HttpServletRequest request = RequestHolder.getHttpServletRequest();
        String thisUser = getUsername();
        // 储存日志信息
        iSysLogService.saveLog(request,
                paramStr,
                argValues,
                thisUser,
                StringUtils.getBrowser(request),
                StringUtils.getIp(request),
                joinPoint,
                sysLog
        );
        // 控制台和日志文件只在debug模式下进行记录,具体信息存储在
        // 输出日志信息到控制台和日志文件
        log.debug("收到 用户 :" + thisUser + " 请求URL :" + request.getRequestURI() + "  参数为 :" + paramStr);
        log.debug("返回前端的结果为 :  " + JSON.toJSONString(result));
        return result;
    }


    /**
     * 配置异常通知
     *
     * @param joinPoint join point for advice
     * @param e         exception
     */
    @AfterThrowing(pointcut = "logPointcut()", throwing = "e")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
        // 设置当前时间为系统时间
        currentTime.set(System.currentTimeMillis());
        // 创建SysLog对象,设置状态为"ERROR",并计算耗时
        SysLog sysLog = new SysLog("ERROR", System.currentTimeMillis() - currentTime.get());
        // 移除当前时间
        currentTime.remove();
        // 设置异常详情
        sysLog.setExceptionDetail(ThrowableUtils.getStackTrace(e).getBytes());
        // 获取当前请求对象
        HttpServletRequest request = RequestHolder.getHttpServletRequest();

        //参数值
        List<Object> argValues = new ArrayList<>(Arrays.asList(joinPoint.getArgs()));
        String thisUser = getUsername();
        String paramStr;
        try {
            // 将参数值转换为JSON字符串
            paramStr = JSON.toJSONString(argValues);
        } catch (Exception e1) {
            // 转换失败,则直接将参数值转换为字符串
            paramStr = argValues.toString();
        }
        // 保存日志
        iSysLogService.saveLog(request,
                paramStr,
                argValues,
                thisUser,
                StringUtils.getBrowser(request),
                StringUtils.getIp(request),
                (ProceedingJoinPoint) joinPoint,
                sysLog
        );
        //打印结果
        // 打印异常日志,包含用户、请求URL和参数
        log.error(">>>>>>异常异常异常<<<<<<< 收到 用户 :{} 请求URL :{}  参数 : {}", thisUser, request.getRequestURI(), joinPoint.getArgs());
    }

    /**
     * 获取当前用户名称
     * @return
     */
    private String getUsername() {
//        final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//        return Objects.isNull(authentication) ? "" : authentication.getName();
        return "baicaizhi";
    }
}

SysLog实体类

import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.time.LocalDateTime;

@Data
@NoArgsConstructor
public class SysLog implements Serializable {

    private Long id;

    /** 操作用户 */
    private String username;

    /** 描述 */
    private String description;

    /** 方法名 */
    private String method;

    /** 参数 */
    private String params;

    /** 日志类型 */
    private String logType;

    /** 请求ip */
    private String requestIp;

    /** 地址 */
    private String address;

    /** 浏览器  */
    private String browser;

    /** 请求耗时 */
    private Long time;

    /** 异常详细  */
    private byte[] exceptionDetail;

    /** 创建日期 */
    private LocalDateTime createTime;

    public SysLog(String logType, Long time) {
        this.logType = logType;
        this.time = time;
    }
}

Dao层

这里需要注意一下啊,我这边引用了mybatisplus,所以这里复制后会报错,需要引入下依赖

<dependency>
       <groupId>com.baomidou</groupId>
       <artifactId>mybatis-plus-boot-starter</artifactId>
       <version>${mybaits-plus.version}</version>
</dependency>
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baicaizhi.entity.SysLog;

/**
 * Created by sunjx on 2020/06/12
 */
public interface SysLogMapper extends BaseMapper<SysLog>{


}

Service类

import com.baomidou.mybatisplus.extension.service.IService;
import com.baicaizhi.entity.SysLog;
import org.aspectj.lang.ProceedingJoinPoint;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

/**
 * Created by sunjx on 2020/06/12
 */
public interface ISysLogService extends IService<SysLog> {

    void saveLog(HttpServletRequest request,
                 String paramStr,
                 List<Object> argValues,
                 String username,
                 String browser,
                 String ip,
                 ProceedingJoinPoint joinPoint,
                 SysLog sysLog
    );

}

Service实现类

import cn.hutool.json.JSONObject;
import com.baicaizhi.utils.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baicaizhi.annotation.Log;
import com.baicaizhi.entity.SysLog;
import com.baicaizhi.mapper.SysLogMapper;
import com.baicaizhi.service.ISysLogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.List;

/**
 * baicaizhi
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class SysLogServiceImpl extends ServiceImpl<SysLogMapper, SysLog> implements ISysLogService {

    private final SysLogMapper sysLogMapper;

    /**
     * 保存日志信息
     *
     * @param request       HttpServletRequest对象
     * @param paramStr      参数字符串
     * @param argValues     方法参数值列表
     * @param username      用户名
     * @param browser       浏览器类型
     * @param ip            用户IP地址
     * @param joinPoint     切面连接点
     * @param sysLog        系统日志对象
     */
    @Override
    public void saveLog(HttpServletRequest request,
                        String paramStr,
                        List<Object> argValues,
                        String username,
                        String browser,
                        String ip,
                        ProceedingJoinPoint joinPoint,
                        SysLog sysLog
    ) {
        // 获取当前执行的方法的签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        // 获取方法上的Log注解
        Log aopLog = method.getAnnotation(Log.class);
    
        // 方法路径
        String methodName = joinPoint.getTarget().getClass().getName()+"."+signature.getName()+"()";
    
    
    
        // 描述
        // 判断Log注解是否存在
        if (aopLog != null) {
            sysLog.setDescription(aopLog.value());
        }
        assert aopLog != null;
        sysLog.setRequestIp(ip);
    
        String loginPath = "login";
        // 判断当前执行的方法名是否为login
        if(loginPath.equals(signature.getName())){
            try {
                // 从参数列表中获取username参数
                username = new JSONObject(argValues.get(0)).get("username").toString();
            }catch (Exception e){
                e.printStackTrace();
            }
        } else if (StringUtils.isBlank(username)){
            // 如果username为空,则从Log注解的from属性中获取
            username = aopLog.from();
        }
        sysLog.setAddress("system");
        sysLog.setMethod(methodName);
        sysLog.setUsername(username);
        sysLog.setParams(paramStr);
        sysLog.setBrowser(browser);
        sysLog.setCreateTime(LocalDateTime.now());
        sysLogMapper.insert(sysLog);
    }

}

utils工具类

RequestHolder

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Objects;

public class RequestHolder {

    /**
     * 获取当前线程的 HttpServletRequest 对象。
     *
     * @return 当前线程的 HttpServletRequest 对象
     * @throws IllegalStateException 如果当前线程没有关联的 HttpServletRequest 对象,则抛出此异常
     */
    public static HttpServletRequest getHttpServletRequest() {
        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
    }
}

StringUtils

import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.UserAgent;

import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Calendar;
import java.util.Date;

/**
 * 字符串工具类, 继承org.apache.commons.lang3.StringUtils类
 */
public class StringUtils extends org.apache.commons.lang3.StringUtils {

    private static final char SEPARATOR = '_';

    private static final String UNKNOWN = "unknown";

    /**
     * 驼峰命名法工具
     *
     * @return toCamelCase(" hello_world ") == "helloWorld"
     * toCapitalizeCamelCase("hello_world") == "HelloWorld"
     * toUnderScoreCase("helloWorld") = "hello_world"
     */
    public static String toCamelCase(String s) {
        if (s == null) {
            return null;
        }

        s = s.toLowerCase();

        StringBuilder sb = new StringBuilder(s.length());
        boolean upperCase = false;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);

            if (c == SEPARATOR) {
                upperCase = true;
            } else if (upperCase) {
                sb.append(Character.toUpperCase(c));
                upperCase = false;
            } else {
                sb.append(c);
            }
        }

        return sb.toString();
    }

    /**
     * 驼峰命名法工具
     *
     * @return toCamelCase(" hello_world ") == "helloWorld"
     * toCapitalizeCamelCase("hello_world") == "HelloWorld"
     * toUnderScoreCase("helloWorld") = "hello_world"
     */
    public static String toCapitalizeCamelCase(String s) {
        if (s == null) {
            return null;
        }
        s = toCamelCase(s);
        return s.substring(0, 1).toUpperCase() + s.substring(1);
    }

    /**
     * 驼峰命名法工具
     *
     * @return toCamelCase(" hello_world ") == "helloWorld"
     * toCapitalizeCamelCase("hello_world") == "HelloWorld"
     * toUnderScoreCase("helloWorld") = "hello_world"
     */
    static String toUnderScoreCase(String s) {
        if (s == null) {
            return null;
        }

        StringBuilder sb = new StringBuilder();
        boolean upperCase = false;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);

            boolean nextUpperCase = true;

            if (i < (s.length() - 1)) {
                nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
            }

            if ((i > 0) && Character.isUpperCase(c)) {
                if (!upperCase || !nextUpperCase) {
                    sb.append(SEPARATOR);
                }
                upperCase = true;
            } else {
                upperCase = false;
            }

            sb.append(Character.toLowerCase(c));
        }

        return sb.toString();
    }

    /**
     * 获取ip地址
     */
    public static String getIp(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        String comma = ",";
        String localhost = "127.0.0.1";
        if (ip.contains(comma)) {
            ip = ip.split(",")[0];
        }
        if  (localhost.equals(ip))  {
            // 获取本机真正的ip地址
            try {
                ip = InetAddress.getLocalHost().getHostAddress();
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
        }
        return ip;
    }

    /**
     * 根据请求头中的User-Agent信息获取浏览器名称
     *
     * @param request HttpServletRequest对象
     * @return 返回浏览器名称字符串
     */
    public static String getBrowser(HttpServletRequest request){
        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
        Browser browser = userAgent.getBrowser();
        return browser.getName();
    }

    /**
     * 获得当天是周几
     */
    public static String getWeekDay(){
        String[] weekDays = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
        Calendar cal = Calendar.getInstance();
        cal.setTime(new Date());

        int w = cal.get(Calendar.DAY_OF_WEEK) - 1;
        if (w < 0){
            w = 0;
        }
        return weekDays[w];
    }

    /**
     * 获取当前机器的IP
     * @return /
     */
    public static String getLocalIp(){
        InetAddress addr;
        try {
            addr = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            return "unknown";
        }
        byte[] ipAddr = addr.getAddress();
        StringBuilder ipAddrStr = new StringBuilder();
        for (int i = 0; i < ipAddr.length; i++) {
            if (i > 0) {
                ipAddrStr.append(".");
            }
            ipAddrStr.append(ipAddr[i] & 0xFF);
        }
        return ipAddrStr.toString();
    }

}

ThrowableUtils

import java.io.PrintWriter;
import java.io.StringWriter;

/**
 * 异常工具 2019-01-06
 */
public class ThrowableUtils {

    /**
     * 获取堆栈信息
     */
    public static String getStackTrace(Throwable throwable){
        StringWriter sw = new StringWriter();
        try (PrintWriter pw = new PrintWriter(sw)) {
            throwable.printStackTrace(pw);
            return sw.toString();
        }
    }
}

日志SQL

CREATE TABLE `sys_log` (
  `username` varchar(255) DEFAULT NULL COMMENT '操作用户',
  `description` varchar(255) DEFAULT NULL COMMENT '描述',
  `params` longtext COMMENT '参数',
  `create_time` datetime DEFAULT NULL COMMENT '创建日期',
  `log_type` varchar(20) DEFAULT NULL COMMENT '日志类型',
  `exception_detail` text COMMENT '异常详细',
  `method` varchar(255) DEFAULT NULL COMMENT '方法名',
  `request_ip` varchar(255) DEFAULT NULL COMMENT '请求ip',
  `time` bigint(20) DEFAULT NULL COMMENT '请求耗时',
  `address` varchar(255) DEFAULT NULL COMMENT '地址',
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `browser` varchar(255) DEFAULT NULL COMMENT '浏览器',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `log_create_time_index` (`create_time`) USING BTREE,
  KEY `inx_log_type` (`log_type`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1145606 DEFAULT CHARSET=utf8mb4;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java白菜治

祝愿赞赏大佬薪资翻倍

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值