SpringBoot增加操作日志记录 SpringBoot实现操作日志记录 springboot 操作日志记录 SpringBoot日志记录 SpringBoot spring mvc操作日志记录

1、操作日志SQL表

表名:t_operation_log 数据库:MySQL 版本: 5.6.40

/*
 Navicat Premium Data Transfer

 Source Server         : localhost
 Source Server Type    : MySQL
 Source Server Version : 50640
 Source Host           : localhost:3306
 Source Schema         : Test

 Target Server Type    : MySQL
 Target Server Version : 50640
 File Encoding         : 65001

 Date: 20/12/2022 14:47:30
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_operation_log
-- ----------------------------
DROP TABLE IF EXISTS `t_operation_log`;
CREATE TABLE `t_operation_log`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `operation_user_id` int(11) NULL DEFAULT NULL COMMENT '操作人ID',
  `operation_username` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作人名称',
  `operation_module` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '操作模块',
  `operation_events` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '具体操作事件',
  `operation_url` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '操作url',
  `operation_data` varchar(3048) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作附带数据',
  `operation_status` tinyint(1) NOT NULL COMMENT '操作是否正常,1正常操作, 0 操作异常',
  `operation_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作所在IP',
  `operation_result` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作结果',
  `add_time` datetime(0) NOT NULL COMMENT '操作时间',
  `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '1 删除,0 未删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 32 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '操作日志表' ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

2、表映射实体类

增删改查用的是 mybatis-plus, 如果不是用的 mybatis-plus 需要增加一个 insert 的方法
实体类名称:OperationLog.java

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.apache.commons.lang3.builder.ToStringBuilder;

/**
 * <p>
 * 操作日志表
 * </p>
 *
 */
@TableName("t_operation_log")
@ApiModel(value = "OperationLog对象", description = "操作日志表")
public class OperationLog implements Serializable,Cloneable {
    
    

    private static final long serialVersionUID = 1L;

    /**
     * 实现 Cloneable 克隆拷贝
     * 创建一个 默认 对象,用于作为克隆的源数据
     */
    private static final OperationLog log = new OperationLog();

    /**
     * 获取克隆对象, 避免new的方式创建
     * @return {@link OperationLog}
     */
    public static OperationLog getInstance(){
    
    
        try {
    
    
            return log.clone();
        } catch (CloneNotSupportedException e) {
    
    
            return new OperationLog();
        }
    }


    /**
     * 重写克隆方法
     * @return {@link OperationLog}
     */
    public OperationLog clone() throws CloneNotSupportedException {
    
    
        return (OperationLog) super.clone();
    }

    /**
     * 私有化构造函数,不允许 new
     */
    private OperationLog(){
    
    
        this.deleted = false;
    }

    @TableId(type = IdType.AUTO)
    private Integer id;

    @ApiModelProperty("操作人ID")
    private Integer operationUserId;

    @ApiModelProperty("操作人名称")
    private String operationUsername;


    @ApiModelProperty("操作模块")
    private String operationModule;

    @ApiModelProperty("具体操作事件")
    private String operationEvents;

    @ApiModelProperty("操作Url")
    private String operationUrl;

    @ApiModelProperty("操作附带数据")
    private String operationData;

    @ApiModelProperty("操作是否正常,1正常操作, 0 操作异常")
    private Boolean operationStatus;

    @ApiModelProperty("操作结果")
    private String operationResult;

    @ApiModelProperty("操作所在IP")
    private String operationIp;

    @ApiModelProperty("操作时间")
    private LocalDateTime addTime;

    @ApiModelProperty("1 删除,0 未删除")
    private Boolean deleted;



    public String getOperationUsername() {
    
    
        return operationUsername;
    }

    public void setOperationUsername(String operationUsername) {
    
    
        this.operationUsername = operationUsername;
    }

    public Integer getId() {
    
    
        return id;
    }

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

    public Integer getOperationUserId() {
    
    
        return operationUserId;
    }

    public void setOperationUserId(Integer operationUserId) {
    
    
        this.operationUserId = operationUserId;
    }

    public String getOperationModule() {
    
    
        return operationModule;
    }

    public void setOperationModule(String operationModule) {
    
    
        this.operationModule = operationModule;
    }

    public String getOperationEvents() {
    
    
        return operationEvents;
    }

    public void setOperationEvents(String operationEvents) {
    
    
        this.operationEvents = operationEvents;
    }

    public String getOperationUrl() {
    
    
        return operationUrl;
    }

    public void setOperationUrl(String operationUrl) {
    
    
        this.operationUrl = operationUrl;
    }

    public String getOperationData() {
    
    
        return operationData;
    }

    public void setOperationData(String operationData) {
    
    
        this.operationData = operationData;
    }

    public Boolean getOperationStatus() {
    
    
        return operationStatus;
    }

    public void setOperationStatus(Boolean operationStatus) {
    
    
        this.operationStatus = operationStatus;
    }

    public String getOperationResult() {
    
    
        return operationResult;
    }

    public void setOperationResult(String operationResult) {
    
    
        this.operationResult = operationResult;
    }

    public String getOperationIp() {
    
    
        return operationIp;
    }

    public void setOperationIp(String operationIp) {
    
    
        this.operationIp = operationIp;
    }

    public LocalDateTime getAddTime() {
    
    
        return addTime;
    }

    public void setAddTime(LocalDateTime addTime) {
    
    
        this.addTime = addTime;
    }

    public Boolean getDeleted() {
    
    
        return deleted;
    }

    public void setDeleted(Boolean deleted) {
    
    
        this.deleted = deleted;
    }

    @Override
    public String toString() {
    
    
        return new ToStringBuilder(this)
                .append("id", id)
                .append("operationUserId", operationUserId)
                .append("operationUsername", operationUsername)
                .append("operationModule", operationModule)
                .append("operationEvents", operationEvents)
                .append("operationUrl", operationUrl)
                .append("operationData", operationData)
                .append("operationStatus", operationStatus)
                .append("operationResult", operationResult)
                .append("operationIp", operationIp)
                .append("addTime", addTime)
                .append("deleted", deleted)
                .toString();
    }
}

3、Service层

新增 操作日志,查询 我并没有写 SQL语句,都是用的 mybatis-plus 默认增删改查接口

4、操作日志注注解

4.1 创建 日志注解文件

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

/**
 * <h2>操作日志注解</h2>
 * <p>
 *
 * </p>
 */
@Target({
    
    ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLogDesc {
    
    
    /**
     * 操作模块
     */
    String module();

    /**
     * 操作事件
     */
    String events();
}

4.2 在请求方法上标注注解

    /**
     * 分页查询
     */
    @GetMapping("/pageQuery")
    @OperationLogDesc(module = "测试管理>测试记录", events = "删除记录")
    public String deleted(QueryDTO dto) {
    
    
        return "请求成功";
    }

5、AOP 操作日志记录

使用 AOP 切入请求层 记录操作日志并保存
AOP maven依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

``

AOP操作日志配置文件

import com.mh.jishi.annotation.OperationLogDesc;
import com.mh.jishi.util.IpUtil;
import io.netty.util.concurrent.FastThreadLocal;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
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 javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Objects;

/**
 *
 **/
@Aspect
@Component
@Slf4j
public class LoggerAspect{
    
    
	/**
	* FastThreadLocal 依赖于  netty,如果不想用netty,可以使用jdk自带的 ThreadLocal
	*/
    final FastThreadLocal<OperationLog> logFastThreadLocal = new FastThreadLocal<>();
    final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");


    /**
     * 切入连接点,使用固定 controller层下的所有文件
     */
    @Pointcut(value = "execution(* com.test.api.controller..*(..)))")
    public void logPointcut() {
    
    
    }

    /**
     * 请求前置通知
     */
    @Before("logPointcut()")
    public void beforLogger(JoinPoint joinPoint) {
    
    

        // 获取请求参数
        String params = Arrays.toString(joinPoint.getArgs());
		
		// 鉴权会话获取 当前登录的用户信息,我用的是 shiro,根据情况改变
		Subject currentUser = SecurityUtils.getSubject();
 
		User user = (User)currentUser.getPrincipal();
		
        Integer userId = null;
        
        String userName = null;
        
        if(User!= null){
    
    
            userId = User.getId();
            
            userName = User.getUsername();
            
        }else{
    
    
            /*
              因为登录接口没有登录会话状态,无法获取到用户信息,从 登录请求参数中获取 登录用户名
              @see 例如:登录请求肯定是 post请求,上方 "params" 参数已经获取到 请求参数信息,只要判断里面是否有用户名信息 验证是否为登录接口,然后字符串截取获取用户名。。这个方法是我能想到最快捷的
              示例登录接口参数:我的登录 请求json [LoginDTO(username=1001010, password=132456,code='A5C5')]
             */
            if(params.contains("username=")){
    
    
                userName = params.substring(params.indexOf("username=") + 9, params.indexOf(", password="));
                // 登录参数密码 简单脱密一下
                params = params.replace("password=", "changshayueluqu_");
            }


        }
        LocalDateTime now = LocalDateTime.now();

        log.info("--------请求前置日志输出开始--------");

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();

        log.info("请求访问时间: {}", dateTimeFormatter.format(now));
        
        // 获取请求url
        String requestUrl = request.getRequestURL().toString();
        
        log.info("请求url: {}", requestUrl);
        
        // 获取method
        log.info("请求方式: {}", request.getMethod());

        log.info("请求参数列表: {}", params);

        log.info("操作人ID: {}", userId);

        // 验证请求方法是否带有操作日志注解
        Method signature = ((MethodSignature) joinPoint.getSignature()).getMethod();
        OperationLogDesc operationLogDesc = signature.getAnnotation(OperationLogDesc.class);
        if (operationLogDesc != null) {
    
    

            // 操作日志记录
            OperationLog operationLog = OperationLog.getInstance();
            operationLog.setAddTime(now);
            operationLog.setOperationModule(operationLogDesc.module());
            operationLog.setOperationEvents(operationLogDesc.events());
            operationLog.setOperationData(params);
            operationLog.setOperationUrl(requestUrl);
            // 操作人ID
            operationLog.setOperationUserId(userId);
            operationLog.setOperationUsername(userName);
            
            // IP地址
            operationLog.setOperationIp(IpUtil.getIpAddr(request));
            logFastThreadLocal.set(operationLog);
        }
    }

    /**
     * 请求后置通知,请求完成会进入到这个方法
     *
     * @param result 响应结果json
     */
    @AfterReturning(value = "logPointcut()", returning = "result")
    public void afterReturningLogger(Object result) {
    
    

        // 程序运时间(毫秒)
        log.info("请求结束时间: {}", dateTimeFormatter.format(LocalDateTime.now()));

        log.info("--------后台管理请求后置日志输出完成--------");

        // 保存操作日志
        OperationLog operationLog = logFastThreadLocal.get();
        
        if (operationLog != null) {
    
    
            operationLog.setOperationStatus(true);
            
            // 用的 是 阿里巴巴的 fastjson
            operationLog.setOperationResult(JSONObject.toJSONString(result));
            
            // 调用具体的 service 保存到数据库中
            operationLogService.save(operationLog);
            
            // 移除本地线程数据
            logFastThreadLocal.remove();
        }

    }


    /**
     * 异常通知,请求异常会进入到这个方法
     */
    @AfterThrowing(value = "logPointcut()", throwing = "throwable")
    public void throwingLogger(Throwable throwable) {
    
    

        log.error("ErrorMessage:请根据异常产生时间前往异常日志查看相关信息");
        log.error("--------后台管理请求异常日志输出完成--------");
        // 保存操作日志
        OperationLog operationLog = logFastThreadLocal.get();
        if (operationLog != null) {
    
    
            operationLog.setOperationStatus(false);
             String throwableStr = throwable.toString();
            if(throwableStr.contains(":")){
    
    
                throwableStr = throwableStr.substring(throwableStr.indexOf(":") + 1);
            }
            operationLog.setOperationResult(throwableStr);
             // 调用具体的 service 保存到数据库中
            operationLogService.save(operationLog);
            // 移除本地线程数据
            logFastThreadLocal.remove();
        }

    }

}

IP 工具类


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class IpUtil {
    
    
    private static final Logger log = LoggerFactory.getLogger(IpUtil.class);

    private IpUtil() {
    
    
    }

    public static String getIpAddr(HttpServletRequest request) {
    
    
        String ipAddress;
        try {
    
    
            ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
    
    
                ipAddress = request.getHeader("Proxy-Client-IP");
            }

            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
    
    
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }

            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
    
    
                ipAddress = request.getRemoteAddr();
                if ("127.0.0.1".equals(ipAddress)) {
    
    
                    InetAddress inet = null;

                    try {
    
    
                        inet = InetAddress.getLocalHost();
                    } catch (UnknownHostException var4) {
    
    
                        log.error(var4.getMessage(), var4);
                    }

                    ipAddress = ((InetAddress)Objects.requireNonNull(inet)).getHostAddress();
                }
            }

            if (ipAddress != null && ipAddress.length() > 15 && ipAddress.indexOf(",") > 0) {
    
    
                ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
            }
        } catch (Exception var5) {
    
    
            ipAddress = "";
        }

        return ipAddress;
    }

    public static String getIpAddr() {
    
    
        return getV4OrV6IP();
    }

    public static String getV4OrV6IP() {
    
    
        String ip = null;
        String test = "http://test.ipw.cn";
        StringBuilder inputLine = new StringBuilder();
        BufferedReader in = null;

        try {
    
    
            URL url = new URL(test);
            HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection();
            in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), StandardCharsets.UTF_8));

            String read;
            while((read = in.readLine()) != null) {
    
    
                inputLine.append(read);
            }

            ip = inputLine.toString();
        } catch (Exception var16) {
    
    
            log.error("获取网络IP地址异常,这是具体原因: ", var16);
        } finally {
    
    
            if (in != null) {
    
    
                try {
    
    
                    in.close();
                } catch (IOException var15) {
    
    
                    var15.printStackTrace();
                }
            }

        }

        if (ip == null) {
    
    
            ip = "127.0.0.1";
            log.info("获取网络IP地址异常, 赋值默认ip: 【{}】", ip);
        }

        return ip;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_40739917/article/details/128384867