【实战开发】SpringBoot实现操作日志功能

13.系统日志管理

目的:描述谁在什么时间干了什么事情得到什么结果,执行接口的时间过程记录清楚,如果报错则要记录报错的过程和信息

实现思路:Spring-AOP功能

技术点:AOP基于注解化、自定义注解、项目中获取当前登录的用户信息

1. 设计系统日志表

CREATE TABLE `huaweidemo`.`t_log`  (
  `logID` varchar(80) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '日志记录id',
  `insertIP` varchar(80) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '记录插入者IP',
  `isDelete` int(2) NULL DEFAULT 0,
  `insertTime` datetime(0) NULL DEFAULT NULL COMMENT '记录插入时间',
  `updateTime` datetime(0) NULL DEFAULT NULL COMMENT '记录更新时间',
  `comment` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注',
  `accountName` varchar(80) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作者用户名',
  `path` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求路径',
  `getTime` datetime(0) NULL DEFAULT NULL COMMENT '请求发送时间',
  `overTime` datetime(0) NULL DEFAULT NULL COMMENT '请求结束响应回复的时间',
  `doTime` int(5) NULL DEFAULT NULL COMMENT '执行时间,单位毫秒',
  `methodName` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作方法名',
  `methodType` int(2) NULL DEFAULT NULL COMMENT '方法类型: 1:get 2:post 3:put 4 :delete 5:其他',
  `methodParam` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '方法入参',
  `methodDesc` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '方法描述-中文',
  `modelName` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '功能模块名',
  `sysType` int(2) NULL DEFAULT NULL COMMENT '调用系统类型:1 web 2 app端',
  `resType` int(2) NULL DEFAULT NULL COMMENT '方法调用结果:1 正常 2: 异常',
  `ycMsg` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '异常信息概述',
  PRIMARY KEY (`logID`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

根据表生成对应的controller、service等类

2. 定义一个日志注解类

/**
 * 模块名称: 系统自定义注解类
 * 模块类型: 注解
 * 编码人:高靖博
 * 创建时间:2023/3/23
 * 联系电话:18587388612
 */
@Target({
    
    ElementType.METHOD,ElementType.PARAMETER}) // 注解能放置的位置
@Retention(RetentionPolicy.RUNTIME) // 注解实例是在该注解被访问时创建且保存在运行内容中(临时对象)
public @interface MySysLog {
    
    
    
    /**
     * 模块名称
     * @return
     */
    String modelName() default "";

    /**
     * 方法类型
     * @return
     */
    int methodType() default 1;

    /**
     * 方法描述
     * @return
     */
    String methodDesc() default "";

    /**
     * 系统类型:1:web调用 2:APP调用
     * @return
     */
    int sysType() default 1;
    
    
}

3. 注解使用

@GetMapping("/captcha")
    @ApiOperation("获取验证码")
    @MySysLog(modelName="验证码",methodDesc = "获取验证码",methodType = MySysLogOpration.GET) 
    public MyResult getRegCode(String localID){
    
    
        
        。。。

4. 采用AOP方法实现日志记录

引入aop依赖

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

创建一个日志记录aop配置类

package com.example.springbootdemo2023.core.aop;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.example.springbootdemo2023.bus.log.dto.Log;
import com.example.springbootdemo2023.bus.log.service.ILogService;
import com.example.springbootdemo2023.core.annotations.MySysLog;
import com.example.springbootdemo2023.core.util.MyJWTUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

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

/**
 * 模块名称: 系统日志记录AOP配置类
 * 模块类型: AOP
 * 编码人:高靖博
 * 创建时间:2023/3/23
 * 联系电话:18587388612
 */

@Aspect
@Component
public class SysLogAOPConfig {
    
    

    @Resource(name = "logServiceImpl")
    private ILogService logService;

    private long methodDoStartTime;

    // 描述一个切点 - 只要调用了@MySysLog注解的方法都要进行日志记录
    @Pointcut("@annotation(com.example.springbootdemo2023.core.annotations.MySysLog)")
    public void sysLogPointCut(){
    
    }

    // 通知- 前置通知: 记录方法被调用的时间,方法执行时间计时器开始
    @Before("sysLogPointCut()")
    public void beforeMethod(JoinPoint point){
    
    
        this.methodDoStartTime = System.currentTimeMillis();
    }

    // 通知- 返回后通知 - 方法调用成功
    @AfterReturning(value = "sysLogPointCut()", returning = "result")
    public void afterReturn(JoinPoint joinPoint, Object result){
    
    

        // 获取当前时间和开始时间比较 得到方法的执行时间
        long doMethodTime = System.currentTimeMillis() - this.methodDoStartTime;

        // 编写正常日志记录
        Log myLog = new Log();

        // 在spring容器中快速获取当前线程的请求对象
        HttpServletRequest httpServletRequest = (HttpServletRequest) RequestContextHolder.getRequestAttributes().resolveReference(RequestAttributes.REFERENCE_REQUEST);

        try{
    
    

            myLog.setInsertIP(getIPAddress(httpServletRequest)); // 获取客户端IP地址
            myLog.setAccountName(getUserNameByToken(httpServletRequest)); // 当前请求操作的用户名(账户名)
            myLog.setPath(httpServletRequest.getRequestURI()); // 请求后端地址( 如: /sys/login)
            myLog.setGetTime(new Date(this.methodDoStartTime)); // 方法调用时间
            myLog.setOverTime(new Date()); // 方法执行完成返回的时间
            myLog.setDoTime(String.valueOf(doMethodTime)+"ms"); // 方法执行时间
            
            // 获取aop拦截的方法 - class字节码对象
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod(); // aop拦截的方法字节码对象

            String methodName = method.getName();// java方法名 - controller方法名称

            myLog.setMethodParam(getParamStrByObjectArr(joinPoint.getArgs()));
            myLog.setModelName(methodName);
            myLog.setMethodName(methodName);
            // 获取当前方法头部的@MySysLog注解
            MySysLog annotation = method.getAnnotation(MySysLog.class);

            if(annotation!=null){
    
     // 方法头部编写了该注解
                String modelName = annotation.modelName();
                String methodDesc = annotation.methodDesc();
                int methodType = annotation.methodType();
                int sysType = annotation.sysType();
                myLog.setModelName(modelName);
                myLog.setMethodDesc(methodDesc);
                myLog.setMethodType(methodType);
                myLog.setSysType(sysType);
            }

            myLog.setResType(1);

            logService.save(myLog); // 日志保存成功

        }catch (Exception e){
    
    
            e.printStackTrace();
        }


    }

    // 通知 - 异常后通知 - 错误日志记录
    @AfterThrowing(value = "sysLogPointCut()", throwing = "e")
    public void afterException(JoinPoint joinPoint, Throwable e){
    
    

        // 获取当前时间和开始时间比较 得到方法的执行时间
        long doMethodTime = System.currentTimeMillis() - this.methodDoStartTime;

        // 编写正常日志记录
        Log myLog = new Log();

        // 在spring容器中快速获取当前线程的请求对象
        HttpServletRequest httpServletRequest = (HttpServletRequest) RequestContextHolder.getRequestAttributes().resolveReference(RequestAttributes.REFERENCE_REQUEST);

        try{
    
    

            myLog.setInsertIP(getIPAddress(httpServletRequest)); // 获取客户端IP地址
            myLog.setAccountName(getUserNameByToken(httpServletRequest)); // 当前请求操作的用户名(账户名)
            myLog.setPath(httpServletRequest.getRequestURI()); // 请求后端地址( 如: /sys/login)
            myLog.setGetTime(new Date(this.methodDoStartTime)); // 方法调用时间
            myLog.setOverTime(new Date()); // 方法执行完成返回的时间
            myLog.setDoTime(String.valueOf(doMethodTime)+"ms"); // 方法执行时间

            // 获取aop拦截的方法 - class字节码对象
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod(); // aop拦截的方法字节码对象

            String methodName = method.getName();// java方法名 - controller方法名称

            myLog.setMethodParam(getParamStrByObjectArr(joinPoint.getArgs()));
            myLog.setModelName(methodName);
            myLog.setMethodName(methodName);

            // 获取当前方法头部的@MySysLog注解
            MySysLog annotation = method.getAnnotation(MySysLog.class);

            if(annotation!=null){
    
     // 方法头部编写了该注解
                String modelName = annotation.modelName();
                String methodDesc = annotation.methodDesc();
                int methodType = annotation.methodType();
                int sysType = annotation.sysType();
                myLog.setModelName(modelName);
                myLog.setMethodDesc(methodDesc);
                myLog.setMethodType(methodType);
                myLog.setSysType(sysType);
            }

            myLog.setResType(2);
            myLog.setYcMsg(e.getMessage()+"||"+e.toString().substring(80)+"...");

            logService.save(myLog); // 日志保存成功

        }catch (Exception e2){
    
    
            e2.printStackTrace();
        }
    }

    /**-------------- 工具方法 ------------------------*/

    /**
     * 获取客户端IP地址
     * @param request
     * @return
     */
    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;
    }


    /**
     *从请求中获取请求头信息,从请求头获取token,token解析出userName签名属性
     * @param request
     * @return
     */
    public static String getUserNameByToken(HttpServletRequest request){
    
    

        String token = request.getHeader("token");
        if(token == null){
    
    
            return "";
        }

        // 获取token签名内容
        try{
    
    
            DecodedJWT verify = JWT.require(Algorithm.HMAC256(MyJWTUtil.getSecretKey())).build().verify(token);

            String userName = verify.getClaim("userName").toString().replaceAll("\"","");
            return userName;
        }catch (Exception e){
    
    
            return "";
        }

    }


    public static String getParamStrByObjectArr(Object [] arr){
    
    

        String str = "";

        for(Object obj : arr){
    
    
            str += obj.toString()+",";
        }

        str = str.substring(0,str.length()-1);

        return str;

    }

}

Guess you like

Origin blog.csdn.net/gjb760662328/article/details/129939708