接口日志:储存到MySQL数据库

news2024/10/5 18:31:27

1、定义一个日志注解,把模块的接口调用日志储存到数据库中。
2、后续可能会产生性能问题,但对于当前快速扩张的业务而言,这种过渡性的功能,还是可以接受的。

用法:

在这里插入图片描述

一、自定义注解对象

package com.pkg.modelname.anno;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogOperation {

	String value() default "";
}

二、自定义切面处理类

@Aspect
@Component
@Slf4j
public class OperationAspect {

    @Resource
    ApiAopSaveComp logUtil;

    @Pointcut("@annotation(com.pkg.modelname.anno.LogOperation)")
    public void logPointCut() {

    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        return logUtil.onAround(point);
    }
}

三、日志保存组件

@Component
@Slf4j
@Setter
@Getter
public class ApiAopSaveComp {

    /**
     * 日志默认模块名
     */
    private String modelName = "def-model";

    @Resource
    ApiLogService apiLogService;

    /**
     * 默认日志保存
     */
    private InsertLog defInsert = (joinPoint, time, status, result) -> {
        try {
            // 默认保存日志
            saveLogDef(joinPoint, time, status, result);
        } catch (NoSuchMethodException | IOException e) {
            log.error("保存日志异常: ", e);
        }
    };

    /**
     * 保存日志
     * 重设此对象以实现自己的保存日志方法
     */
    private InsertLog insertLog = defInsert;

    /**
     * 环切
     *
     * @param point 切入点
     * @return 返回
     * @throws Throwable 错误
     */
    public Object onAround(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        Object result;
        try {
            // 执行方法
            result = point.proceed();
            // 保存日志
            onSuccess(point, result, beginTime);
            return result;
        } catch (Exception e) {
            // 执行时长(毫秒)
            long cost = cost(beginTime);
            // 保存日志 返回异常
            saveLog(point, cost, false, ExceptionUtils.getStackTrace(e));
            throw e;
        }
    }

    /**
     * 成功的日志
     *
     * @param point     切入点
     * @param result    返回结果
     * @param beginTime 开始时间
     */
    private void onSuccess(ProceedingJoinPoint point, Object result, long beginTime) {
        try {
            // 执行时长(毫秒)
            long cost = cost(beginTime);
            // 返回
            String data = "";
            if (null != result) {
                data = result.toString();
            }
            // 保存日志
            saveLog(point, cost, true, data);
        } catch (Exception e) {
            log.error("保存日志异常", e);
        }
    }

    /**
     * 计算耗时
     *
     * @param beginTime 开始时间
     * @return 间隔时间
     */
    private long cost(long beginTime) {
        return System.currentTimeMillis() - beginTime;
    }

    /**
     * 保存日志
     *
     * @param joinPoint 切入点
     * @param time      耗时
     * @param status    请求状态
     * @param result    返回参数
     */
    private void saveLog(ProceedingJoinPoint joinPoint, long time, boolean status, String result) {
        try {
            insertLog.insertLog(joinPoint, time, status, result);
        } catch (Exception e) {
            log.error("保存日志失败", e);
        }
    }

    /**
     * 默认的保存日志方法
     *
     * @param joinPoint 切入点
     * @param time      耗时
     * @param status    请求状态
     * @param result    返回参数
     * @throws NoSuchMethodException 异常
     */
    private void saveLogDef(ProceedingJoinPoint joinPoint, long time, boolean status, String result) throws NoSuchMethodException, IOException {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(), signature.getParameterTypes());
        LogOperation annotation = method.getAnnotation(LogOperation.class);
        // 组装日志
        WebApiLogEntity entity = new WebApiLogEntity();
        entity.setLogTime(new Date());
        entity.setModuleName(modelName);
        if (annotation != null) {
            //注解上的描述
            entity.setApi(annotation.value());
        }
        // 耗时
        entity.setCostTime(time);
        // 状态
        entity.setStatus(status);
        //请求相关信息
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        if (null != request) {
            entity.setIpAddr(HttpContextUtils.getRealIp(request));
            String header = HttpContextUtils.getAllHeader(request);
            entity.setHeader(limitLen(header));
            entity.setRequestUri(limitLen(request.getRequestURI()));
            entity.setMethod(request.getMethod());
        }
        // 请求参数
        try {
            String args = getArgs(joinPoint);
            entity.setArgs(limitLen(args, 2048));
            log.info("args : {}", args);
        } catch (Exception e) {
            log.error("参数序列错误:", e);
            e.printStackTrace();
        }
        // 返回参数
        if (null != result) {
            entity.setResult(limitLen(result, 2048));
            log.info("result : {}", result);
        }
        apiLogService.saveLog(entity);
    }

    /**
     * 限制长度
     *
     * @param header 头部
     * @return 限制后的长度
     */
    private static String limitLen(String header) {
        int maxLen = 500;
        return limitLen(header, maxLen);
    }

    /**
     * 限制长度
     *
     * @param str 字符
     * @param len 长度,不包含
     * @return 限制后的长度
     */
    private static String limitLen(String str, int len) {
        if (null == str) {
            return null;
        }
        return str.length() > len ? str.substring(0, len) : str;
    }

    /**
     * 获取请求参数
     *
     * @param point 切入点
     * @return 参数
     */
    public static String getArgs(ProceedingJoinPoint point) {
        Object[] args = point.getArgs();
        if (null != args && args.length > 0) {
            Map<Integer, String> map = new HashMap<>();
            for (int i = 0; i < args.length; i++) {
                map.put(i, args[i].toString());
            }
            return new Gson().toJson(map);
        }
        return null;
    }

}

日志打印接口

默认实现一个接口,支持其它模块再次实现,提升扩展性

public interface InsertLog {
    /**
     * 插入日志
     *
     * @param joinPoint 切入点
     * @param time      耗时
     * @param status    请求结果状态
     * @param result    返回参数
     */
    void insertLog(ProceedingJoinPoint joinPoint, long time, boolean status, String result);
}
日志储存实体对象
@Data
@NoArgsConstructor
@ToString
@TableName("tb_web_api_log")
public class WebApiLogEntity {

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

    private Date logTime;

    /**
     * 模块名
     */
    private String moduleName;

    private String api;

    /**
     * 耗时
     */
    private Long costTime;

    private boolean status;

    private String ipAddr;

    private String header;

    private String requestUri;

    private String method;

    private String args;

    private String result;

}

实体表

SQL代码

CREATE TABLE `tb_web_api_log`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `log_time` datetime NULL DEFAULT NULL,
  `module_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `api` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `cost_time` int(11) NULL DEFAULT NULL,
  `status` tinyint(4) NULL DEFAULT NULL,
  `ip_addr` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `header` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `request_uri` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `method` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `args` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `result` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'api日志表' ROW_FORMAT = Compact;

图例:
在这里插入图片描述

使用到的第三方包

ExceptionUtils.getStackTrace(e)

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.9</version>
</dependency>

HTTP上下文工具类

public class HttpContextUtils {

    /**
     * 获取当前请求
     * @return 请求
     */
    public static HttpServletRequest getHttpServletRequest() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            return null;
        }
        return ((ServletRequestAttributes) requestAttributes).getRequest();
    }

    /**
     * 获取真正的ip
     * @param request 请求
     * @return
     */
    public static String getRealIp(HttpServletRequest request) {
        //
        String ip = request.getHeader("X-Real-IP");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            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.getRemoteAddr();
        }
        // 处理多IP的情况(只取第一个IP)
        if (ip != null && ip.contains(",")) {
            String[] ipArray = ip.split(",");
            ip = ipArray[0];
        }
        return ip;
    }

    /**
     * 获取所有头
     *
     * @param request 请求
     * @return 所有请求头
     */
    public static String getAllHeader(HttpServletRequest request) {
        Enumeration<String> eHeaders = request.getHeaderNames();
        Map<String,String> map =new HashMap<>();
        while (eHeaders.hasMoreElements()){
            String name = eHeaders.nextElement();
            String val = request.getHeader(name);
            map.put(name,val);
        }
        return JSONObject.toJSONString(map);
    }

}

四、扩展用法

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1510355.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

罐头鱼AI短视频矩阵营销|视频批量剪辑|矩阵系统

AI批量视频剪辑系统是一款功能丰富的视频编辑软件&#xff0c;提供了以下主要功能&#xff1a; 首页显示&#xff1a;在首页上显示用户的登录状态、已绑定的账号数量以及最近上传的视频素材和新上传素材列表。 抖音账号绑定功能&#xff1a;用户可以绑定抖音账号&#xff0c;Q…

【Python】新手入门学习:什么是硬编码?如何避免硬编码?

【Python】新手入门学习&#xff1a;什么是硬编码&#xff1f;如何避免硬编码&#xff1f; &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教…

在ubuntu上安装FastSufer【本机安装】

亲测:FastSurfer分割并重建一个大脑需要1个小时,而freeSurfer需要8个小时。确实很快! 这里我在网页端搭建了一个小的工具包,里面集成了经典的freeSurfer和较快的FastSurfer。如果你不想安装或者手头没有linux设备,您也可以直接从以下网址直接使用,跳过繁琐的安装步骤!!…

基于PLC的智能楼宇控制系统设计

目录 摘要 2 第一章 绪论 5 1.1 引言 5 1.2 智能楼宇的课题背景 5 1.3 智能楼宇的功能和优势 6 第二章 智能楼宇系统总体方案确定 7 2.1 智能楼宇系统总体方案的设计 7 2.1.1 智能楼宇概述 7 2.1.2 智能楼宇的分类 7 1、建筑设备自动化系统(BA) 7 2、通讯自动化系统&#xff0…

融资项目——网关微服务

1. 网关的路由转发功能 在前后端分离的项目中&#xff0c;网关服务可以将前端的相关请求转发到相应的后端微服务中。 2. 网关微服务的配置 首先需要创建一个网关微服务&#xff0c;并添加依赖。 <!-- 网关 --><dependency><groupId>org.springframework.cl…

更深层的去理解负载均衡

目录 前言&#xff1a; 一、进行负载均衡的原因 二、什么是负载均衡 三、负载均衡的分类 二层负载均衡 三层负载均衡 四层负载均衡 七层负载均衡 四、负载均衡工具 LVS &#xff1a;LVS主要用来做四层负载均衡 Nginx &#xff1a;Nginx主要用来做七层负载均衡 HAProxy &#xf…

推荐5款知道的人不多的小众软件

​ 今天推荐5款十分小众的软件&#xff0c;知道的人不多&#xff0c;但是每个都是非常非常好用的&#xff0c;有兴趣的小伙伴可以自行搜索下载。 1. 3D建模与设计——Fusion360 ​ Fusion360是一款强大的3D建模与设计软件&#xff0c;集CAD、CAM和CAE功能于一体。它支持从概念…

【Greenhills】MULTI IDE工程管理的目录结构

【更多软件使用问题请点击亿道电子官方网站查询】 1、 文档目标 关于的GHS的Project Manager中工程的目录结构的组成 2、 问题场景 在GHS中去创建项目后&#xff0c;对于在Project Manager窗口中的目录结构不太清晰&#xff0c;目录中有多个gpj文件&#xff0c;无法确认哪个是…

制冷系统管道焊接气焊安全操作

气焊操作安全教育&#xff1a; 1、检查气焊用具完好牢固无损&#xff0c;不得贴粘有&#xff08;机油&#xff09;&#xff1b; 2、气瓶余压&#xff08;2KG&#xff09;停止使用&#xff1b;清除动火 10 米范围内易燃易爆物料&#xff1b; 3、库房内动火要 做好通风排气&…

时间序列处理相关函数及案例

目录 常用函数日期对象ee.Date.fromYMD&#xff08;&#xff09;ee.List.sequence&#xff08;&#xff09; 序列影像处理ee.ImageCollection.fromImages&#xff08;&#xff09;ee.Filter.calendarRange&#xff08;&#xff09;.set&#xff08;&#xff09; 案例&#xff1…

15.0 Scrapy 使用中出现的错误分享

目录 一、把 Scrapy项目创建在一个 python 项目中 1、导致情况及解决方法(简述) 2、导致情况及解决方法(详述) 2.1 导致引入模块时找不到 2.2 后续 scrapy crawl 命令报错 一、把 Scrapy项目创建在一个 python 项目中 1、导致情况及解决方法(简述) &#xff08;1&#x…

Linux 多线程开发

第三章 Linux 多线程开发 3.1 线程3.1.2 线程操作3.1.2 线程属性 3.2 线程同步3.2.1 互斥量/锁3.2.2 死锁3.2.3 读写锁 3.3 生产者消费者模型3.3.1 条件变量3.3.2 信号量/灯 网络编程系列文章&#xff1a; 第1章 Linux系统编程入门&#xff08;上&#xff09; 第1章 Linux系统…

返回值不同算方法重载么?为什么?

1、典型回答 返回值不同不算方法重载 方法重载&#xff08;Overloading&#xff09;是指在同一个类中定义了多个同名方法&#xff0c;但它们的参数列表不同&#xff0c;方法重载要求方法&#xff1a; 名称相同参数类型、参数个数或参数顺序&#xff0c;至少有一个不同 方法…

QT----计算器

目录 1 搭建标准界面2、 逻辑编写2.1 初始化 github链接&#xff1a;基于qt的计算器 1 搭建标准界面 按照下图搭设界面 修改样式让这计算器看起来更像一点&#xff0c;同时对按钮分组进行样式编辑&#xff0c;添加字符串name,为number&#xff0c;其他按键为other。之前的文章…

2024会声会影 软件介绍、下载,功能介绍及问题解答

会声会影正式版是一款使用起来十分件便捷的影视后期视频编辑处理软件&#xff0c;会声会影正式版操作简单&#xff0c;还具备了独特创意、灵活有趣等特点&#xff0c;并且软件还具备了上百种滤镜和特效、调控速度、从多机新增视讯片段等等功能&#xff0c;会声会影官方版便捷好…

2024上海网络安全产业创新大会成功举办,天空卫士成为焦点

2月28日&#xff0c;由上海市经济和信息化委员会、上海市普陀区人民政府主办的2024上海网络安全产业创新大会在上海跨国采购会展中心举办。天空卫士受邀参加2023年重点行业网络安全解决方案揭榜结果发布暨合作签约仪式&#xff0c;并参加了工业互联网安全产业创新论坛和数据安全…

【Python】新手入门学习:什么是python解释器,它的作用是什么?

【Python】新手入门学习&#xff1a;什么是python解释器&#xff0c;它的作用是什么&#xff1f; &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基…

Day37:安全开发-JavaEE应用JNDI注入RMI服务LDAP服务JDK绕过调用链类

目录 JNDI注入-RMI&LDAP服务 JNDI远程调用-JNDI-Injection JNDI远程调用-marshalsec JNDI-Injection & marshalsec 实现原理 JNDI注入-FastJson漏洞结合 JNDI注入-JDK高版本注入绕过 思维导图 Java知识点&#xff1a; 功能&#xff1a;数据库操作&#xff0c;文…

C语言⽂件操作

1. 为什么使⽤⽂件 如果没有⽂件&#xff0c;我们写的程序的数据是存储在电脑的内存中&#xff0c;如果程序退出&#xff0c;内存回收&#xff0c;数据就丢失了&#xff0c;等再次运⾏程序&#xff0c;是看不到上次程序的数据的&#xff0c;如果要将数据进⾏持久化的保存&…

羊大师分析,羊奶养生智慧

羊大师分析&#xff0c;羊奶养生智慧 羊奶&#xff0c;这一古老而自然的饮品&#xff0c;近年来逐渐受到越来越多人的青睐。其独特的营养价值与养生功效&#xff0c;使得羊奶成为了追求健康生活的人们的理想选择。那么&#xff0c;羊奶究竟蕴藏着怎样的养生智慧呢&#xff1f;…