保存系统的操作日志,通过swagger注解获取请求描述(通用版本)

Posted 技术武器库

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了保存系统的操作日志,通过swagger注解获取请求描述(通用版本)相关的知识,希望对你有一定的参考价值。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

大数据系列文章目录

前言

系统之前没有全局的记录操作日志,只是个别单个功能记录了操作日志,这样个别功能没有记录操作日志,就会出现和运营扯皮的问题,我们4月份就出现这个问题,活动商品在活动结束后,运营统计商品下单数量,发现超卖了,卖的数量比上架的库存要多,找了两天发现是运营在活动进行中的时候,改了商品库存(第一次上架库存的运营和改库存的运营不是一个人,而且改后没有同步),还好最后发现编辑活动的操作日志是有记录的,如果没有呢?那是不是要开发背锅了,之前就想趁早做掉这个功能,一直拖着,通过这件事后,没商量了,直接开干。

操作日志表结构

CREATE TABLE `t_operator_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `create_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建日期',
  `modify_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改日期',
  `operator_id` bigint(20) DEFAULT NULL COMMENT '操作人ID',
  `operator` varchar(50) DEFAULT NULL COMMENT '操作人',
  `operator_phone` varchar(50)  DEFAULT NULL COMMENT '操作人手机号',
  `class_path` varchar(255) DEFAULT NULL COMMENT '请求路径前缀',
  `method_path` varchar(255) DEFAULT NULL COMMENT '请求路径后缀',
  `request_path` varchar(255) DEFAULT NULL COMMENT '请求路径',
  `class_name` varchar(255) DEFAULT NULL COMMENT '全限定类名',
  `method_name` varchar(50) DEFAULT NULL COMMENT '方法名',
  `class_desc` varchar(255) DEFAULT NULL COMMENT '类描述',
  `method_desc` varchar(255) DEFAULT NULL COMMENT '方法描述',
  `request_parameters` text COMMENT '满参数json',
  `request_exists_parameters` text COMMENT '传递参数json',
  `result` text COMMENT '返回值',
  `system` varchar(255) DEFAULT NULL COMMENT '系统',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1  COMMENT='工厂后台操作记录表';

代码
我是通过切面的方式拦截请求,通过获取代理类的swagger注解和mapping注解的方式,获取描述和请求路径,当然还包含请求参数和响应参数。代码中获取操作人的代码,你们结合自己项目改下,其他的通用

package com.zcckj.puxian.web.common.aspect;

import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.ArrayUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.zcckj.puxian.account.api.model.dto.OperatorLogDTO;
import com.zcckj.puxian.account.api.service.write.IOperatorLogWriteService;
import com.zcckj.puxian.web.security.util.CurrentAdminUtil;
import com.zcckj.puxian.web.utils.DingDingUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.dubbo.config.annotation.Reference;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;


/**
 * @author lwh
 * @date 2022/5/7
 * @description 操作日志切面类
 **/
@Aspect
@Component
@Slf4j
public class OperationLogAspect 

    @Value("$spring.application.name")
    private String system;

    @Reference(version = "1.0.0", group = "$dubbo.service.group", check = false)
    private IOperatorLogWriteService operatorLogWriteService;

    @Pointcut("execution(public * com.zcckj.puxian.web.controller..*.*(..))")
    public void logPointCut() 

    

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable 
        OperatorLogDTO operatorLog = new OperatorLogDTO();
        Class<?> aClass = point.getTarget().getClass();
        try 
            Long currentId = null;
            String currentMobile = null;
            String currentName = null;
            try 
                currentId = CurrentAdminUtil.getCurrentId();
                currentMobile = CurrentAdminUtil.getCurrentMobile();
                currentName = CurrentAdminUtil.getCurrentName();
             catch (Exception e) 
                // skip
            
            String name = aClass.getName();
            String param1 = this.getParam(point, true);
            String param2 = this.getParam(point, false);
            // 操作人ID
            operatorLog.setOperatorId(currentId)
                    // 操作人
                    .setOperator(currentName)
                    // 操作人手机号
                    .setOperatorPhone(currentMobile)
                    // 全限定类名
                    .setClassName(name)
                    // 方法名
                    .setMethodName(point.getSignature().getName())
                    // 满参数json
                    .setRequestParameters(param1)
                    // 传递参数json
                    .setRequestExistsParameters(param2)
                    // 系统
                    .setSystem(system);
            // 类描述
            this.setClassDesc(operatorLog, aClass);
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();
            // 请求路径
            this.getRequestPath(aClass, method, operatorLog);
            // 方法描述
            this.setMethodDesc(operatorLog, method);
         catch (Exception e) 
            String exMsg = StrFormatter.format("操作日志切面,执行代理方法前。class:\\n异常Error:\\n", aClass.getName(), ExceptionUtils.getStackTrace(e));
            log.error(exMsg);
            DingDingUtil.sendDingTalkRisk(exMsg, null, DingDingUtil.DingTalkEnum.BASE_GIVE_AN_ALARM);
        
        //执行方法
        Object result = point.proceed();
        String resultJson = null;
        try 
            resultJson = this.postHandle(result);
         catch (Exception e) 
            String exMsg = StrFormatter.format("操作日志切面,执行代理方法后,转换返回值为Json,异常Error:", ExceptionUtils.getStackTrace(e));
            log.error(exMsg);
            DingDingUtil.sendDingTalkRisk(exMsg, null, DingDingUtil.DingTalkEnum.BASE_GIVE_AN_ALARM);
        
        // 返回值
        operatorLog.setResult(resultJson);
        CompletableFuture.runAsync(() -> operatorLogWriteService.save(operatorLog));
        return result;
    

    /**
     * 获获取方法描述
     *
     * @author lwh
     * @date 2022/5/7
     **/
    private void setMethodDesc(OperatorLogDTO operatorLog, Method method) 
        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
        if (Objects.nonNull(apiOperation) && StringUtils.isNotBlank(apiOperation.value())) 
            // 方法描述
            operatorLog.setMethodDesc(apiOperation.value());
        
    

    /**
     * 取类描述
     *
     * @author lwh
     * @date 2022/5/7
     **/
    private void setClassDesc(OperatorLogDTO operatorLog, Class<?> aClass) 
        Api api = aClass.getAnnotation(Api.class);
        if (Objects.nonNull(api) && ArrayUtil.isNotEmpty(api.tags())) 
            // 类描述
            operatorLog.setClassDesc(api.tags()[0]);
        
    

    /**
     * 获取返回值
     *
     * @author lwh
     * @date 2022/5/7
     **/
    private String postHandle(Object retVal) 
        if (null == retVal) 
            return "";
        
        return JSONObject.toJSONString(retVal, SerializerFeature.WriteMapNullValue);
    

    /**
     * 获取参数名和参数值
     *
     * @author lwh
     * @date 2022/5/7
     **/
    public String getParam(ProceedingJoinPoint proceedingJoinPoint, boolean isWriteNullValue) 
        Map<String, Object> map = new LinkedHashMap<>();
        Object[] values = proceedingJoinPoint.getArgs();
        String[] names = ((CodeSignature) proceedingJoinPoint.getSignature()).getParameterNames();
        for (int i = 0; i < names.length; i++) 
            map.put(names[i], values[i]);
            if (values[i] instanceof MultipartFile) 
                map.put(names[i], null);
            
        
        if (isWriteNullValue) 
            return JSONObject.toJSONString(map, SerializerFeature.WriteMapNullValue);
        
        return JSONObject.toJSONString(map);

    


    /**
     * 获取请求路径
     *
     * @author lwh
     * @date 2022/5/7
     **/
    private void getRequestPath(Class<?> aClass, Method method, OperatorLogDTO operatorLog) 
        RequestMapping classRequestMapping = aClass.getAnnotation(RequestMapping.class);
        String methodDesc = "";
        GetMapping getMapping = method.getAnnotation(GetMapping.class);
        PostMapping postMapping = method.getAnnotation(PostMapping.class);
        PutMapping putMapping = method.getAnnotation(PutMapping.class);
        DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class);
        RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
        if (Objects.nonNull(getMapping) && ArrayUtil.isNotEmpty(getMapping.value())) 
            methodDesc = getMapping.value()[0];
        
        if (Objects.nonNull(postMapping) && ArrayUtil.isNotEmpty(postMapping.value())) 
            methodDesc = postMapping.value()[0];
        
        if (Objects.nonNull(putMapping) && ArrayUtil.isNotEmpty(putMapping.value())) 
            methodDesc = putMapping.value()[0];
        
        if (Objects.nonNull(deleteMapping) && ArrayUtil.isNotEmpty(deleteMapping.value())) 
            methodDesc = deleteMapping.value()[0];
        
        if (Objects.nonNull(requestMapping) && ArrayUtil.isNotEmpty(requestMapping.value())) 
            methodDesc = requestMapping.value()[0];
        
        if (Objects.nonNull(classRequestMapping) && ArrayUtil.isNotEmpty(classRequestMapping.value())) 
            // 请求路径前缀
            operatorLog.setClassPath(classRequestMapping.value()[0]);
            // 请求路径
            operatorLog.setRequestPath(classRequestMapping.value()[0] + methodDesc);
        
        // 请求路径后缀
        operatorLog.setMethodPath(methodDesc);
    



保存数据的效果

白嫖到这,我劝你点赞再走。

以上是关于保存系统的操作日志,通过swagger注解获取请求描述(通用版本)的主要内容,如果未能解决你的问题,请参考以下文章

Swagger 中 paramType的值

swagger常用注解

golang的swagger注解

javaWEB SSM AOP+注解保存操作日志

通过切面将请求日志记录到数据库

自定义日志注解 + AOP实现记录操作日志