保存系统的操作日志,通过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) CHARACTER SET utf32 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 '系统',
  `ip` varchar(18) DEFAULT NULL COMMENT '登录ip',
  `area` varchar(255) DEFAULT NULL COMMENT '登录地点',
  `equipment` varchar(255) DEFAULT NULL COMMENT '操作系统',
  `browser` varchar(255) DEFAULT NULL COMMENT '浏览器',
  `execution_time` varchar(50) DEFAULT NULL COMMENT '执行时间,单位ms',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 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.common.utils.WebUtils;
import com.zcckj.commons.ip.IPUtils;
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 eu.bitwalker.useragentutils.UserAgent;
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 javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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;

    @Resource
    private HttpServletRequest request;

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

    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long startTime = System.currentTimeMillis();
        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);
            // 设置ip和区域
            this.setIpAndArea(operatorLog);
        } catch (Exception e) {
            String exMsg = StrFormatter.format("[{}]操作日志切面,执行代理方法前。class:{},方法名:{}\n异常Error:\n{}", system, aClass.getName(), point.getSignature().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:{}", system, ExceptionUtils.getStackTrace(e));
            log.error(exMsg);
            DingDingUtil.sendDingTalkRisk(exMsg, null, DingDingUtil.DingTalkEnum.BASE_GIVE_AN_ALARM);
        }
        // 返回值
        operatorLog.setResult(resultJson);
        String execTime = String.valueOf(System.currentTimeMillis() - startTime);
        operatorLog.setExecutionTime(execTime);
        CompletableFuture.runAsync(() -> operatorLogWriteService.save(operatorLog));
        return result;
    }

    /**
     * 设置ip和区域
     * @author lwh
     * @date 2022/5/12
     **/
    private void setIpAndArea(OperatorLogDTO operatorLog) {
        try {
            String ip = WebUtils.getIpAddr(request);
            String area = IPUtils.getArea(ip).toString();
            UserAgent agent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
            operatorLog.setIp(ip).setArea(area).setEquipment(agent.getOperatingSystem().getName()).setBrowser(agent.getBrowser().getName() + agent.getBrowserVersion());
        } catch (Exception e) {
            log.error("记录操作日志,设置IP和区域错误。Error:{}", ExceptionUtils.getStackTrace(e));
        }
    }

    /**
     * 获获取方法描述
     *
     * @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 || values[i] instanceof HttpServletResponse || values[i] instanceof HttpServletRequest) {
                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);
    }

}

保存数据的效果
在这里插入图片描述
白嫖到这,我劝你点赞再走。

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技术武器库

一句真诚的谢谢,胜过千言万语

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值