Java教程:如何使用切面环绕方法对所有接口进行添加出入参日志保存功能

news2024/12/24 11:29:30

背景:

----在很多时候我们做开发时,往往只是提供一个对外接口来进行前后端调试,或第三方系统联调,并使用log进行日志打印,每当出现问题进行排查时,只需要查看服务器日志就可以定位到问题,从而解决问题,但当接口慢慢变多,公司开发部署方案越来越成熟时,分工明确,查看服务器日志却变得不那么随意,这个时候如果还和以前一样出现问题就打开服务器查看日志文件就会变得越来越困难,所以我们必须对重要日志信息进行数据库存储,出现问题直接在浏览器页面查看即可,非常方便,本章就来教大家如何使用切面的方式完成这一操作,方便快捷,使用简单,话不多说,教学开始~

第一步、首先我们需要创建一个注解接口:

/**
 * 接口日志记录注解
 *
 * @author wfeil211@foxmail.com
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InterfaceLog {

    /**
     * 标题
     */
    public String title() default "";

}

第二步、创建接口日志保存实体类:

/**
 * 接口日志对象 interface_log
 *
 * @author wfeil211@foxmail.com
 */
@Data
public class InterfaceLog extends BaseEntity {
    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    private String id;

    /**
     * 接口说明
     */
    private String title;

    /**
     * 接口入参
     */
    private String requestParam;

    /**
     * 响应参数
     */
    private String responseParam;

    /**
     * 方法名称
     */
    private String methodName;

    /**
     * 请求方式
     */
    private String requestWay;

    /**
     * 请求耗时(ms)
     */
    private String handleTime;

    /**
     * 请求时间 yyyy-MM-dd HH:mm:ss
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date requestTime;

    /**
     * 错误信息
     */
    private String errorInfo;

    /**
     * 错误码
     */
    private String errorCode;

    /**
     * 用户ip
     */
    private String userIp;

    /**
     * 服务器ip
     */
    private String serverIp;

    /**
     * 接口状态(0正常 1异常)
     */
    private String status;
}

第三步、创建我们的切面类,将第一步接口进行切入:

/**
 * 接口日志记录处理
 *
 * @author wfeil211@foxmail.com
 */
@Aspect
@Component
public class InterfaceLogAspect {
    private static final Logger log = LoggerFactory.getLogger(InterfaceLogAspect.class);

    // 接口日志
    @Autowired
    private InterfaceLogService interfaceLogService;

    // 配置切入点
    @Pointcut("@annotation(com.feilin.InterfaceLog)")
    public void logPointCut() {
    }

    @Around("logPointCut()")
    public Object aroundBusAsMethod(ProceedingJoinPoint joinPoint) {
        // 获得注解
        InterfaceLog controllerLog = getAnnotationLog(joinPoint);
        if (controllerLog == null) {
            return null;
        }
        Object[] args = joinPoint.getArgs();
        long time;
        // 进入方法之前的时间
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        InterfaceLog interfaceLog = new InterfaceLog();
        // 请求的地址
        String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
        interfaceLog.setUserIp(ip);
        interfaceLog.setTitle(controllerLog.title());
        String hostAddress = "";
        try {
            hostAddress = InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            e.printStackTrace();
            log.error("获取服务器ip异常,原因={}", e.toString());
        }
        interfaceLog.setServerIp(hostAddress);

        // 设置方法名称
        String methodName = joinPoint.getSignature().getName();
        interfaceLog.setMethodName(methodName);
        Object respObj = null;
        // 设置请求方式
        interfaceLog.setRequestWay(ServletUtils.getRequest().getMethod());
        try {
            respObj = joinPoint.proceed(args);
        } catch (Throwable e) {
            log.error("获取方法响应异常,原因:{}", e.toString());
        }
        stopWatch.stop();
        time = stopWatch.getTotalTimeMillis();
        // 请求时间与耗时
        interfaceLog.setHandleTime(String.valueOf(time));
        interfaceLog.setRequestTime(new Date());
        // 请求入参出参
        interfaceLog.setRequestParam(StringUtils.substring(argsArrayToString(args), 0, 2048));
        if (!ObjectUtils.isEmpty(respObj)) {
            interfaceLog.setResponseParam(StringUtils.substring(JSON.toJSONString(respObj), 0, 2048));
            JSONObject parse = (JSONObject) JSONObject.parse(interfaceLog.getResponseParam());
            String code = parse.getString("code");
            String msg = parse.getString("msg");
            if (!"0".equals(code)) {
                interfaceLog.setErrorCode(code);
                interfaceLog.setErrorInfo(msg);
                interfaceLog.setStatus("1");
            } else {
                interfaceLog.setStatus("0");
            }
        }
        try {
            interfaceLogService.insertInterfaceLog(interfaceLog);
        } catch (Throwable e) {
            log.error("接口日志保存失败,interfaceLog={},原因={}", interfaceLog.toString(), e.toString());
        }
        return respObj;
    }

    /**
     * 是否存在注解,如果存在就获取
     */
    private InterfaceLog getAnnotationLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        if (method != null) {
            return method.getAnnotation(InterfaceLog.class);
        }
        return null;
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        String params = "";
        if (ArrayUtils.isNotEmpty(paramsArray) && paramsArray.length > 0 && !(paramsArray[0] instanceof BeanPropertyBindingResult)) {
            params = JSON.toJSON(paramsArray[0]).toString();
        } else {
            log.info("切面日志保存/打印,获取参数为空");
        }
        return params.trim();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象,则返回true;否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Iterator iter = collection.iterator(); iter.hasNext(); ) {
                return iter.next() instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); ) {
                Map.Entry entry = (Map.Entry) iter.next();
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse;
    }
}

第四步、在我们的请求接口上加上注解:

@InterfaceLog(title = "获取参数")
@PostMapping("/test")
public Map<String, String> test(@RequestBody Map<String, String> parms){
	return parms;
}

最后一步、使用PostMan进行测试,大功告成!

在这里插入图片描述

本次教程到这里就结束了,希望大家多多关注支持(首席摸鱼师 微信同号),持续跟踪最新文章吧~

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

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

相关文章

Three.js程序化3D城市建模【OpenStreetMap】

对于我在 Howest 的研究项目&#xff0c;我决定构建一个 3D 版本的 Lucas Bebber 的“交互式讲故事的动画地图路径”项目。 我将使用 OSM 中的矢量轮廓来挤出建筑物的形状并将它们添加到 3js 场景中&#xff0c;随后我将对其进行动画处理 推荐&#xff1a;用 NSDT编辑器 快速搭…

在抽象类中使用@Autowired注入其他bean

概述 今天写代码时&#xff0c;使用模板设计模式&#xff0c;需要在抽象类中使用Autowired注入指定的Bean&#xff0c;然后调用指定方法。 问题 发现Autowired注解有红色下划线 解决 其实没有什么关系&#xff0c;只要实现类继承这个抽象方法&#xff0c;然后加入IOC容器&am…

深入理解SSO原理,项目实践使用一个优秀开源单点登录项目(附源码)

深入理解SSO原理,项目实践使用一个优秀开源单点登录项目(附源码)。 一、简介 单点登录(Single Sign On),简称为 SSO。 它的解释是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。 ❝ 所谓一次登录,处处登录。同样一处退出,处处退出。 ❞ 二…

内核配置知识

Linux内核配置系统的组成 Linux内核源码很多&#xff0c;有上千条配置选项&#xff0c;配置相当复杂。 为了更好选择自己想要的功能配置&#xff0c;linux内核源码组织了一个配置系统&#xff1b; 配置系统包括三部分&#xff1a; Makefile&#xff1a;负责整体的配置编译 …

人工智能在网络安全中的作用:当前的局限性和未来的可能性

人工智能 (AI) 激发了网络安全行业的想象力&#xff0c;有可能彻底改变安全和 IT 团队处理网络危机、漏洞和勒索软件攻击的方式。 然而&#xff0c;对人工智能的能力和局限性的现实理解至关重要&#xff0c;并且存在许多挑战阻碍人工智能对网络安全产生直接的变革性影响。 在…

Python学习 -- 高阶、闭包、回调、偏函数与装饰器探究

Python函数作为编程的核心&#xff0c;涵盖了众多令人兴奋的概念&#xff0c;如高阶函数、闭包、回调、偏函数和装饰器。本篇博客将深入研究这些概念&#xff0c;结合实际案例为你解析函数的精妙&#xff0c;以及如何巧妙地运用它们来构建更强大、灵活的程序。 高阶函数&#…

模型数据处理-数据放入 session和@ModelAttribute 实现 prepare 方法详细讲解

&#x1f600;前言 本文详细讲解了模型数据处理-数据放入 session和ModelAttribute 实现 prepare 方法详细讲解 &#x1f3e0;个人主页&#xff1a;尘觉主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是尘觉&#xff0c;希望我的文章可以帮助到大家&#xff0c…

518. 零钱兑换 II

518. 零钱兑换 II 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a; 原题链接&#xff1a; 518. 零钱兑换 II https://leetcode.cn/problems/coin-change-ii/description/ 完成情况&#xff1a; 解题思路&#xff1a; 参考代码&#xff1…

数据在内存中的存储(deeper)

数据在内存中的存储&#xff08;deeper&#xff09; 一.数据类型的详细介绍二.整形在内存中的存储三.浮点型在内存中的存储 一.数据类型的详细介绍 类型的意义&#xff1a; 使用这个类型开辟内存空间的大小&#xff08;大小决定了使用范围&#xff09;如何看待内存空间的视角…

GBU816-ASEMI新能源专用整流桥GBU816

编辑&#xff1a;ll GBU816-ASEMI新能源专用整流桥GBU816 型号&#xff1a;GBU816 品牌&#xff1a;ASEMI 封装&#xff1a;GBU-4 恢复时间&#xff1a;&#xff1e;50ns 正向电流&#xff1a;8A 反向耐压&#xff1a;1600V 芯片个数&#xff1a;4 引脚数量&#xff1…

关于spring嵌套事务,我发现网上好多热门文章持续性地以讹传讹

事情起因是&#xff0c;摸鱼的时候在某平台刷到一篇spring事务相关的博文&#xff0c;文章最后贴了一张图。里面关于嵌套事务的表述明显是错误的。 更奇怪的是&#xff0c;这张图有点印象。在必应搜索关键词PROPAGATION_NESTED出来的第一篇文章&#xff0c;里面就有这这部份内…

使用chatgpt将中文翻译成学术英语

使用chatgpt将中文翻译成学术英语 方式一 使用chatgpt翻译 你是一个英文学术论文写作专家&#xff0c;以下是一篇学术论文中的一段内容&#xff0c;请先对其进行翻译为英文&#xff0c;并将此部分润色以满足学术标准&#xff0c;提高语法、清晰度和整体可读性&#xff0c;给…

408反向改考自命题的211学校,计算机招生近500人!今年能捡到漏吗?

贵州大学(C) 考研难度&#xff08;☆☆☆&#xff09; 内容&#xff1a;23考情概况&#xff08;拟录取和复试分析&#xff09;、院校概况、23专业目录、23复试详情、各专业考情分析。 正文1498字&#xff0c;预计阅读&#xff1a;3分钟。 2023考情概况 贵州大学计算机相关各…

基于SpringCloud的会议室预约系统Java基于微服务的会议室报修系统【源码+lw】

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人七年开发经验&#xff0c;擅长Java、微信小程序、Python、Android、大数据等&#xff0c;大家有这一块的问题可以一起交流&#xff01; &#x1f495;&#x1f495…

.net通过S7.net读写西门子PLC中,字符串,bool,整数,小数及byte型

注&#xff1a;.net中通过TCP/IP方式通过S7.net.dll动态库&#xff0c;连接到西门子PLC&#xff0c;西门子程序中许勾选优化块&#xff0c;程序读取需要 db块号偏移量 一。使用VS项目&#xff0c;在项目中添加S7.net动态库 代码中引用S7.net动态库 using S7.Net; 实例化PLC服…

Linux网络编程:Socket套接字编程

文章目录&#xff1a; 一&#xff1a;定义和流程分析 1.定义 2.流程分析 3.网络字节序 二&#xff1a;相关函数 IP地址转换函数inet_pton inet_ntop&#xff08;本地字节序 网络字节序&#xff09; socket函数(创建一个套接字) bind函数(给socket绑定一个服务器地址结…

使用线性回归模型优化权重:探索数据拟合的基础

文章目录 前言一、示例代码二、示例代码解读1.线性回归模型2.MSE损失函数3.优化过程4.结果解读 总结 前言 在机器学习和数据科学中&#xff0c;线性回归是一种常见而重要的方法。本文将以一个简单的代码示例为基础&#xff0c;介绍线性回归的基本原理和应用。将使用Python和Nu…

安卓框架中的常见问题汇总

目录 1.安卓操作系统的组件结构图如下 2.问题汇总 1.安卓操作系统的组件结构图如下 2.问题汇总 问题1&#xff1a;安卓框架中的库和应用程序框架之间什么关系&#xff1f; 在安卓系统中&#xff0c;应用程序框架层&#xff08;Application Framework&#xff09;是核心应用程…

迈向通用听觉人工智能!清华电子系、火山语音携手推出认知导向的听觉大语言模型SALMONN

日前&#xff0c;清华大学电子工程系与火山语音团队携手合作&#xff0c;推出认知导向的开源听觉大语言模型SALMONN (Speech Audio Language Music Open Neural Network)。 大语言模型 SALMONN LOGO 相较于仅仅支持语音输入或非语音音频输入的其他大模型&#xff0c;SALMONN对…

Python爬虫的scrapy的学习(学习于b站尚硅谷)

目录 一、scrapy  1. scrapy的安装  &#xff08;1&#xff09;什么是scrapy  &#xff08;2&#xff09;scrapy的安装 2. scrapy的基本使用  &#xff08;1&#xff09;scrap的使用步骤  &#xff08;2&#xff09;代码的演示 3. scrapy之58同城项目结构和基本方法&…