SpringBoot AOP 记录操作日志并保存到数据库

1.实现类

package com.sino.platform.service.cgm.common.service;

import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.ApiOperation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @Description: 日志切面、重复请求验证 服务类
 * @Date 2021/9/10
 */
@Aspect
@Component
public class ApiOperationAspectService {

    private static Logger LOGGER = LoggerFactory.getLogger(ApiOperationAspectService.class);

    @Autowired
    private HttpServletRequest request;


    private long start;
    private SysOperationLog sysLog;

    /**
     * 配置切入点,在注解的位置切入代码
     */
    @Pointcut("@annotation(io.swagger.annotations.ApiOperation)")
    public void apiOperationPointcut(){}

    /* 切面 配置通知,各注解作用
     * @Before:前置通知,在方法执行之前执行
     * @After:后置通知,在方法执行之后执行
     * @AfterRunning:返回通知,在方法返回结果之后执行,用这个注解参数会随方法改变,例新增一个实体,参数是一个id为null的,用这个注解后就会赋上真实的id
     * @AfterThrowing:异常通知,在方法抛出异常之后执行
     * @Around:环绕通知,围绕着方法执行
     *  */
    @Before("apiOperationPointcut()")
    public void beforeAdvice(JoinPoint joinPoint){
        start = System.currentTimeMillis();
        //从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取切入点所在的方法
        Method method = signature.getMethod();
        //操作简述
        String describe="";
        //操作类型
        String operationType = "";
        //是否涉及敏感信息
        ApiOperation log = method.getAnnotation(ApiOperation.class);
        if (log != null) {
            describe = log.value()!=null? log.value():"未知";
            operationType = log.notes()!= null?log.notes():"未知";
        }

        //获取请求的类名
        String className = joinPoint.getTarget().getClass().getName();
        //获取请求的方法名
        String methodName = method.getName();
        //参数
        String params = "";
        //请求的参数
        Object[] args = joinPoint.getArgs();
        params = getParamsToJSONStr(args,true);

        String currentDateTime= LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        //创建sysLog对象
        sysLog = new SysOperationLog();
        sysLog.setUrl(request.getRequestURI() ==null ? "" :request.getRequestURI());
        sysLog.setOperationMethod(className + "." + methodName);
        sysLog.setOperationDesc(describe);
        sysLog.setOperationType(operationType);
        sysLog.setParameter(params);
        sysLog.setIp(getIpAddress(request));
        sysLog.setOperationTime(currentDateTime);
    }

    /**
     * 获取请求入参
     * @param args 参数数组
     * @param assignConvertJsonException 是否在参数转换json失败时将失败异常赋值给返回变量
     * @return
     */
    private String getParamsToJSONStr(Object[] args,boolean assignConvertJsonException){
        String params = "";
        int argsLength = args.length;
        Object argumentObj=null;
        if(argsLength==1){
            argumentObj = args[0];
            if (argumentObj instanceof ServletRequest || argumentObj instanceof ServletResponse || argumentObj instanceof MultipartFile || argumentObj instanceof MultipartFile[]) {
                argumentObj = null;
            }
        }
        if(argsLength > 1){
            Object[] arguments  = new Object[argsLength];
            for (int i = 0; i < argsLength; i++) {
                if (args[i] instanceof ServletRequest || args[i] instanceof ServletResponse || args[i] instanceof MultipartFile || args[i] instanceof MultipartFile[]) {
                    //ServletRequest不能序列化,从入参里排除,否则报异常:java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
                    //ServletResponse不能序列化 从入参里排除,否则报异常:java.lang.IllegalStateException: getOutputStream() has already been called for this response
                    continue;
                }
                arguments[i] = args[i];
            }
            argumentObj = arguments;
        }
        //将参数转换成json
        if (argumentObj != null) {
            try {
                params = JSONObject.toJSONString(argumentObj);
            } catch (Exception e) {
                if(assignConvertJsonException){
                    params = "入参转换至JSON异常:"+e.toString();
                }
            }
        }
        return params;
    }



    /**
     * AfterRunning: 返回通知
     * 如果这里是@After在目标切入点的方法抛出异常时执行到@AfterThrowing方法后,也会执行到这里
     */
    @AfterReturning("apiOperationPointcut()")
    public void afterAdvice(){
        Long timeConsuming = System.currentTimeMillis() - start;
        if(sysLog!=null){
            sysLog.setTimeConsuming(timeConsuming.intValue());
            sysLog.setLogType((byte)1);
            sysLog.setErrorLogMsg("");
            //保存日志
            saveSysLog(sysLog);
        }
    }

    /**
     * AfterThrowing:异常通知
     * @param e
     */
    @AfterThrowing(value = "apiOperationPointcut()",throwing = "e")
    public void errorAdvice(Throwable e){
        Long timeConsuming = System.currentTimeMillis() - start;
        if(sysLog!=null){
            sysLog.setTimeConsuming(timeConsuming.intValue());
            sysLog.setLogType((byte)2);
            //这里用的e.toString()获取到异常的异常类型和异常详细消息,而e.getMessage()只会获取到异常的消息字符串,如:
            //java.lang.NullPointerException 或 java.lang.ArithmeticException: / by zero   这是e.toString()获取到的
            //null  或 / by zero  这是e.getMessage()获取到的
            String exToString = e.toString();
            int limitOfLength = 1000;
            if(exToString.length()>limitOfLength){
                exToString = exToString.substring(0,limitOfLength);
            }
            sysLog.setErrorLogMsg(exToString);
            //保存日志
            saveSysLog(sysLog);
        }
    }

    private void saveSysLog(SysOperationLog sysLog){
        LOGGER.info("【操作记录】用户:{},在:{},操作了方法:{},耗时:{}ms",sysLog.getUserName()+"("+sysLog.getUserId()+")", sysLog.getOperationTime(),sysLog.getOperationMethod(),sysLog.getTimeConsuming());
        try{
            //从请求头拿到平台类型
            HttpServletRequest request = WebUtil.getRequest();
            //app端没有 所有默认值为1
            String platformType = 1+"";
            if(request.getHeader("platformType") != null){
                platformType = request.getHeader("platformType");
            }
            sysLog.setPlatformType(Integer.parseInt(platformType));
            //TODO insert日志

        }catch (Exception e){
            LOGGER.error("保存系统操作日志错误"+e.getMessage());
        }
    }



    public static String getIpAddress(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.getHeader("HTTP_CLIENT_IP");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }

        return ip;
    }
}

2.实体类

package com.sino.platform.service.cgm.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Data;

/**
 * @Description:
 * @Date 2021/9/10
 */
@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
@Data
public class SysOperationLog {

    private Integer id;

    //操作方法
    private String operationMethod;

    //请求url
    private String url;

    //操作简称
    private String operationDesc;

    //参数
    private String parameter;

    //ip
    private String ip;

    //耗时
    private Integer timeConsuming;

    //操作时间
    private String operationTime;

    //日志类型 1:正常操作日志 2:错误日志
    private byte logType;

    //错误日志msg
    private String errorLogMsg;

    //操作类型
    private String operationType;
    
}

3.工具类

 
public class WebUtil {
    /**
     * 获取request对象
     **/
    public static HttpServletRequest getRequest() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            return null;
        }
        return ((ServletRequestAttributes) requestAttributes).getRequest();
    }
 
    /**
     * 获取response对象
     **/
    public static HttpServletResponse getResponse() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            return null;
        }
        return ((ServletRequestAttributes) requestAttributes).getResponse();
    }
}

4.控制层方法添加注解

    @ApiOperation(value = "新增数据")

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你可以通过Spring AOP实现这个功能。具体步骤如下: 1. 创建一个实体类,用于保存错误日志信息,包括用户信息、请求url、方法、参数、异常信息等。 2. 创建一个切面类,使用@Aspect注解标注,该切面类中定义一个@Before通知,用于捕获Controller中抛出的异常,并将异常信息和其他相关信息保存数据库中。 3. 在切面类中通过@Pointcut注解指定切入点,即对哪些Controller中的方法进行切面处理。 4. 在切面类中使用@Autowired注解注入实体类对应的Repository,然后在@Before通知中调用Repository中的方法将错误信息保存数据库中。 具体实现细节可以参考以下代码: ```java @Aspect @Component public class ErrorLogAspect { @Autowired private ErrorLogRepository errorLogRepository; @Pointcut("execution(* com.example.demo.controller..*.*(..))") public void logPointcut() {} @Before("logPointcut()") public void logError(JoinPoint joinPoint) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); ErrorLog errorLog = new ErrorLog(); errorLog.setUserId(request.getHeader("userId")); // 从请求头中获取用户信息 errorLog.setUrl(request.getRequestURI()); // 获取请求url errorLog.setMethod(joinPoint.getSignature().toShortString()); // 获取方法名 errorLog.setArgs(Arrays.toString(joinPoint.getArgs())); // 获取方法参数 errorLog.setExceptionMessage(getExceptionMessage(joinPoint)); // 获取异常信息 errorLogRepository.save(errorLog); // 保存错误日志数据库 } private String getExceptionMessage(JoinPoint joinPoint) { Throwable ex = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(ExceptionHandler.class).value(); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); ex.printStackTrace(pw); return sw.toString(); } } ``` 其中,ErrorLog是保存错误日志信息的实体类,ErrorLogRepository是对应的Repository。在logError方法中,通过JoinPoint获取方法名、方法参数等信息,通过RequestContextHolder获取请求信息,最后将错误日志保存数据库中。getExceptionMessage方法用于获取异常信息。在Controller中,需要使用@ExceptionHandler注解来捕获异常,并将异常信息保存数据库中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值