springboot通过切面编程实现系统请求操作日志记录

Posted _不正

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot通过切面编程实现系统请求操作日志记录相关的知识,希望对你有一定的参考价值。

1、引入依赖包

 <!-- aop 依赖包 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.6</version>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>

 <dependency>
     <groupId>cn.hutool</groupId>
     <artifactId>hutool-all</artifactId>
     <version>5.3.8</version>
</dependency>
View Code

2、相应数据库

系统操作日志表

CREATE TABLE `sys_log_operation` (
  `id` varchar(32) NOT NULL COMMENT \'id\',
  `request_uri` varchar(200) DEFAULT NULL COMMENT \'请求URI\',
  `request_method` varchar(20) DEFAULT NULL COMMENT \'请求方式\',
  `class_method` varchar(200) DEFAULT NULL COMMENT \'请求函数\',
  `request_params` text COMMENT \'请求参数\',
  `request_time` bigint DEFAULT NULL COMMENT \'请求时长(毫秒)\',
  `user_agent` varchar(500) DEFAULT NULL COMMENT \'用户代理\',
  `request_ip` varchar(32) DEFAULT NULL COMMENT \'操作ip\',
  `state` int DEFAULT NULL COMMENT \'状态(0、失败,1、成功)\',
  `create_time` datetime NOT NULL COMMENT \'创建时间\',
  `create_user` varchar(32) DEFAULT NULL COMMENT \'创建人\',
  `creator_name` varchar(32) DEFAULT NULL COMMENT \'创建人名\',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT=\'系统操作日志\';
View Code

系统错误日志表

CREATE TABLE `sys_log_error` (
  `id` varchar(32) NOT NULL COMMENT \'id\',
  `request_uri` varchar(200) DEFAULT NULL COMMENT \'请求URI\',
  `request_method` varchar(20) DEFAULT NULL COMMENT \'请求方式\',
  `class_method` varchar(200) DEFAULT NULL COMMENT \'请求函数\',
  `request_params` text COMMENT \'请求参数\',
  `request_time` int DEFAULT NULL COMMENT \'请求时长(毫秒)\',
  `user_agent` varchar(500) DEFAULT NULL COMMENT \'用户代理\',
  `request_ip` varchar(32) DEFAULT NULL COMMENT \'操作ip\',
  `error_info` text COMMENT \'异常信息\',
  `create_time` datetime NOT NULL COMMENT \'创建时间\',
  `create_user` varchar(32) DEFAULT NULL COMMENT \'创建人\',
  `creator_name` varchar(32) DEFAULT NULL COMMENT \'创建人名\',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT=\'系统错误日志\';
View Code

3、日志实体类

package com.bz.bean;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;

/**
 * 异常日志
 */
@Data
@TableName(value = "sys_log_error")
public class SysLogError implements Serializable {
    /**
     * id
     */
    @TableId(value = "id", type = IdType.INPUT)
    @ApiModelProperty(value = "id")
    private String id;

    /**
     * 请求URI
     */
    @TableField(value = "request_uri")
    @ApiModelProperty(value = "请求URI")
    private String requestUri;

    /**
     * 请求方式
     */
    @TableField(value = "request_method")
    @ApiModelProperty(value = "请求方式")
    private String requestMethod;

    /**
     * 请求参数
     */
    @TableField(value = "request_params")
    @ApiModelProperty(value = "请求参数")
    private String requestParams;

    /**
     * 用户代理
     */
    @TableField(value = "user_agent")
    @ApiModelProperty(value = "用户代理")
    private String userAgent;

    /**
     * 操作IP
     */
    @TableField(value = "ip")
    @ApiModelProperty(value = "操作IP")
    private String ip;

    /**
     * 异常信息
     */
    @TableField(value = "error_info")
    @ApiModelProperty(value = "异常信息")
    private String errorInfo;

   /**
     * 创建人
     */
    @TableField(value = "create_user")
    @ApiModelProperty(value = "创建人")
    private String createUser;

    /**
     * 创建人名
     */
    @TableField(value = "creator_name")
    @ApiModelProperty(value = "创建人名")
    private String creatorName;

    /**
     * 创建时间
     */
    @TableField(value = "create_date")
    @ApiModelProperty(value = "创建时间")
    private Date createDate;

    /**
     * 类方法
     */
    @TableField(value = "class_method")
    @ApiModelProperty(value = "类方法")
    private String classMethod;

    private static final long serialVersionUID = 1L;
}


package com.enzenith.homemaking.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 系统操作日志
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "sys_log_operation")
public class SysLogOperation implements Serializable {
    /**
     * id
     */
    @TableId(value = "id", type = IdType.INPUT)
    @ApiModelProperty(value = "id")
    private String id;

    /**
     * 请求URI
     */
    @TableField(value = "request_uri")
    @ApiModelProperty(value = "请求URI")
    private String requestUri;

    /**
     * 请求方式
     */
    @TableField(value = "request_method")
    @ApiModelProperty(value = "请求方式")
    private String requestMethod;

    /**
     * 请求函数
     */
    @TableField(value = "class_method")
    @ApiModelProperty(value = "请求函数")
    private String classMethod;

    /**
     * 请求参数
     */
    @TableField(value = "request_params")
    @ApiModelProperty(value = "请求参数")
    private String requestParams;

    /**
     * 请求时长(毫秒)
     */
    @TableField(value = "request_time")
    @ApiModelProperty(value = "请求时长(毫秒)")
    private Long requestTime;

    /**
     * 用户代理
     */
    @TableField(value = "user_agent")
    @ApiModelProperty(value = "用户代理")
    private String userAgent;

    /**
     * 操作ip
     */
    @TableField(value = "request_ip")
    @ApiModelProperty(value = "操作ip")
    private String requestIp;

    /**
     * 状态(0、失败,1、成功)
     */
    @TableField(value = "state")
    @ApiModelProperty(value = "状态(0、失败,1、成功)")
    private Integer state;

    /**
     * 创建时间
     */
    @TableField(value = "create_time")
    @ApiModelProperty(value = "创建时间")
    private Date createTime;

    /**
     * 创建人
     */
    @TableField(value = "create_user")
    @ApiModelProperty(value = "创建人")
    private String createUser;

    /**
     * 创建人名
     */
    @TableField(value = "creator_name")
    @ApiModelProperty(value = "创建人名")
    private String creatorName;

    /**
     * 备注(用于数据迁移)
     */
    @TableField(value = "memo")
    @ApiModelProperty(value = "备注(用于数据迁移)")
    private String memo;

    private static final long serialVersionUID = 1L;
}
View Code

4、切面实现类

package com.bz.aspect;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.bz.bean.SysLogError;
import com.bz.bean.SysLogOperation;
import com.bz.common.enums.OperationStatusEnum;
import com.bz.service.SysLogErrorService;
import com.bz.service.SysLogOperationService;
import org.apache.commons.lang.StringEscapeUtils;
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.aspectj.lang.reflect.MethodSignature;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * 切面日志
 * @author Buzheng
 */
@Aspect
@Component
public class RequestLogAspect {
    private final static Logger LOGGER = LoggerFactory.getLogger(RequestLogAspect.class);

    @Autowired(required = false)
    private SysLogOperationService sysLogOperationService;

    @Autowired(required = false)
    private SysLogErrorService sysLogErrorService;

    /**
     * 定义切入点
     */
    @Pointcut("execution(* com.bz.api..*.*(..))")
    public void requestServer() {
    }

    /**
     * 环绕通知
     * 既可以在目标方法之前织入增强动作,也可以在执行目标方法之后织入增强动作;
     * 可以决定目标方法在什么时候执行,如何执行,甚至可以完全阻止目标目标方法的执行;
     * 可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值; 当需要改变目标方法的返回值时,只能使用Around方法;
     */
    @Around("requestServer()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //获取系统时间
        long start = System.currentTimeMillis();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        //获取请求信息
        HttpServletRequest request = attributes.getRequest();
        Object result = proceedingJoinPoint.proceed();
        //保存录入数据库
        SysLogOperation requestInfo = new SysLogOperation();
        requestInfo.setId(IdUtil.createSnowflake(1,1).nextId());
        //获取客户端的IP地址
        requestInfo.setRequestIp(request.getRemoteAddr());
        //获取请求的接口地址
        requestInfo.setRequestUri(request.getRequestURL().toString());
        requestInfo.setRequestMethod(request.getMethod());
        //获取请求哪个类以及哪个方法
        requestInfo.setClassMethod(String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(),
                proceedingJoinPoint.getSignature().getName()));
        //获取请求参数
        requestInfo.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint));
        requestInfo.setState(OperationStatusEnum.SUCCESS.value());
        requestInfo.setRequestTime(System.currentTimeMillis() - start);
        requestInfo.setCreateTime(new Date());
        /**
         *  判断是否有token,因为登录的时候是没有的。所以需要进行判断
         *  ps:前端每次请求接口时都会带token进行请求。
         */
        String token = request.getHeader("token");
        if(StrUtil.isNotBlank(token)){
            requestInfo.setCreatorName("获取请求者名称");
            requestInfo.setCreateUser("获取请求者用户id");
        }
        sysLogOperationService.save(requestInfo);

        return result;
    }

    /**
     * 异常通知:目标方法抛出异常时执行
     */
    @AfterThrowing(pointcut = "requestServer()", throwing = "e")
    public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        SysLogError requestErrorInfo = new SysLogError();

        requestErrorInfo.setId(String.valueOf(IdUtil.createSnowflake(1,1).nextId()));
        requestErrorInfo.setIp(request.getRemoteAddr());
        requestErrorInfo.setRequestUri(request.getRequestURL().toString());
        requestErrorInfo.setRequestMethod(request.getMethod());
        requestErrorInfo.setClassMethod(String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(),
                joinPoint.getSignature().getName()));
        requestErrorInfo.setRequestParams(getRequestParamsByJoinPoint(joinPoint));
        //获取报错信息
        requestErrorInfo.setErrorInfo(e.getMessage());
        requestErrorInfo.setCreateDate(new Date());
        /**
         *  判断是否有token,因为登录的时候是没有的。所以需要进行判断
         *  ps:前端每次请求接口时都会带token进行请求。
         */
        String token = request.getHeader("token");
        if(StrUtil.isNotBlank(token)){
            requestErrorInfo.setCreatorName("获取请求者名称");
            requestErrorInfo.setCreateUser("获取请求者用户id");
        }
        boolean save = sysLogErrorService.save(requestErrorInfo);
        LOGGER.info("Error Request Info      : {}", JSON.toJSONString(requestErrorInfo));
    }

    /**
     * 获取入参
     * @param proceedingJoinPoint
     * */
    private String getRequestParamsByProceedingJoinPoint(ProceedingJoinPoint proceedingJoinPoint) {
        //参数名
        String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames();
        //参数值
        Object[] paramValues = proceedingJoinPoint.getArgs();
        //去除反斜杠
        return   StringEscapeUtils.unescapejavascript(buildRequestParam(paramNames, paramValues).toString());
    }

     /**
     * 获取入参
     * @param proceedingJoinPoint
     * */
    private String getRequestParamsByJoinPoint(JoinPoint joinPoint) {
        //参数名
        String[] paramNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames();
        //参数值
        Object[] paramValues = joinPoint.getArgs();
        return  StringEscapeUtils.unescapeJavaScript(buildRequestParam(paramNames, paramValues).toString());
    }

    /**
     * 将参数名称以及参数值转成json字符串
     * @param paramNames    参数名称
     * @param paramValues   参数值
     * @return cn.hutool.json.JSONObject
     * @author Buzheng
     **/
    private JSONObject buildRequestParam(String[] paramNames, Object[] paramValues) {
        JSONObject jsonObject = new JSONObject();
        for (int i = 0; i < paramNames.length; i++) {
            Object value = paramValues[i];
            //如果是文件对象
            if (value instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) value;
                value = file.getOriginalFilename();  //获取文件名
            }
            jsonObject.putOpt(paramNames[i], value != null ? JSONUtil.toJsonStr(value) : null);
        }
        return jsonObject;
    }
}
View Code

相关内容链接跳转:

切面编程注解操作日志

以上是关于springboot通过切面编程实现系统请求操作日志记录的主要内容,如果未能解决你的问题,请参考以下文章

springboot-aop面向切面编程

Springboot通过切面校验参数完整性

springboot中如何在切面中重写请求参数

SpringBoot2.0 基础案例(11):配置AOP切面编程,解决日志记录业务

springBoot AOP学习

springboot配置aop切面日志打印