springboot项目自定义切面增强方法功能(springboot记录日志)

news2025/1/11 18:44:32

说明

背景:记录系统接口日志入库,包含接口方法、入参、回参、响应时间、操作人、操作时间等信息。

方案:添加自定义切面处理

一、自定义切面注解

package com.gstanzer.supervise.annotation;

import com.gstanzer.supervise.enums.BusinessType;

import java.lang.annotation.*;

/**
 * 系统操作日志
 *
 * @author: tangbingbing
 * @date: 2023/12/15 15:25
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SysOperLog {
    /**
     * 接口名称
     */
    String name();

    /**
     * 接口功能
     */
    BusinessType businessType() default BusinessType.OTHER;

}

二、自定义切面

package com.gstanzer.supervise.aspect;


import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.gstanzer.supervise.annotation.SysOperLog;
import com.gstanzer.supervise.mapper.SysOperLogMapper;
import com.gstanzer.supervise.po.SysOperLogPO;
import com.gstanzer.supervise.utils.IpUtils;
import com.gstanzer.supervise.utils.IpUtilsNew;
import com.gstanzer.supervise.utils.ServletUtils;
import com.gstanzer.supervise.utils.TokenUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.Date;
import java.util.Map;

/**
 * 系统操作日志记录处理
 *
 * @author: tangbingbing
 * @date: 2023/12/15 15:25
 */
@Aspect
@Component
public class SysOperLogAspect {
    private static final Logger log = LoggerFactory.getLogger(SysOperLogAspect.class);

    ThreadLocal<Long> startTime = new ThreadLocal<>();

    @Resource
    private SysOperLogMapper sysOperLogMapper;

    @Before("@annotation(controllerLog)")
    public void doBefore(JoinPoint joinPoint, SysOperLog controllerLog) {
        //开始计时
        startTime.set(System.currentTimeMillis());
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, SysOperLog controllerLog, Object jsonResult) {
        HttpServletRequest request = IpUtils.getHttpServletRequest();
        handleLog(joinPoint, controllerLog, null, jsonResult, request);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, SysOperLog controllerLog, Exception e) {
        HttpServletRequest request = IpUtils.getHttpServletRequest();
        handleLog(joinPoint, controllerLog, e, null, request);
    }

    protected void handleLog(final JoinPoint joinPoint, SysOperLog controllerLog, final Exception e, Object jsonResult, HttpServletRequest request) {
        try {
            long totalRuntime = System.currentTimeMillis() - startTime.get();
            String userId = "";
            String loginName = "";
            Date date = new Date();
            String token = request.getHeader("gst-token");
            Map<String, String> userMap = TokenUtils.getUserInfoByToken(token);
            if (userMap != null) {
                userId = userMap.get("userId");
                loginName = userMap.get("loginName");
            }
            SysOperLogPO operLog = new SysOperLogPO();
            String ip = IpUtilsNew.getIpAddr(ServletUtils.getRequest());
            operLog.setId(String.valueOf(IdUtil.getSnowflake(1,1).nextId()));
            operLog.setOperIp(ip);
            operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
            operLog.setUserId(userId);
            operLog.setUserName(loginName);
            if (e != null) {
                operLog.setErrorMsg(e.getMessage());
            }
            //String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            operLog.setMethodName(methodName);
            operLog.setOperTime(date);
            operLog.setResponseTime(totalRuntime + "ms");
            // 设置请求方式
            operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
            // 保存数据库
            log.info("操作日志入库:{}", operLog);
            sysOperLogMapper.insert(operLog);
        } catch (Exception exp) {
            // 记录本地异常日志
            log.error("==前置通知异常==");
            log.error("异常信息:{}", exp.getMessage());
            exp.printStackTrace();
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param log     日志
     * @param operLog 操作日志
     * @throws Exception
     */
    public void getControllerMethodDescription(JoinPoint joinPoint, SysOperLog log, SysOperLogPO operLog, Object jsonResult) throws Exception {
        operLog.setMethodName(log.name());
        operLog.setBusinessType(String.valueOf(log.businessType()));
        setRequestValue(joinPoint, operLog);
        operLog.setResponseParam(JSON.toJSONString(jsonResult));
    }

    /**
     * 获取请求的参数,放到log中
     *
     * @param operLog 操作日志
     * @throws Exception 异常
     */
    private void setRequestValue(JoinPoint joinPoint, SysOperLogPO operLog) throws Exception {
        String requestMethod = operLog.getRequestMethod();
        if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
            String params = argsArrayToString(joinPoint.getArgs());
            operLog.setRequestParam(params);
        }
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        String params = "";
        if (paramsArray != null && paramsArray.length > 0) {
            for (Object o : paramsArray) {
                if (o != null && !isFilterObject(o)) {
                    try {
                        String jsonObj = JSON.toJSONString(o);
                        params += jsonObj + " ";
                    } catch (Exception e) {
                    }
                }
            }
        }
        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 (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }
}

三、在需要记录日志的接口上加上该注解即可

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

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

相关文章

【BFS】走迷宫问题——acwing844

问题描述 给定一个 nm 的二维整数数组&#xff0c;用来表示一个迷宫&#xff0c;数组中只包含 0 或 1&#xff0c;其中 0表示可以走的路&#xff0c;1 表示不可通过的墙壁。 最初&#xff0c;有一个人位于左上角 (1,1) 处&#xff0c;已知该人每次可以向上、下、左、右任意一…

Ubuntu20下C/C++编程开启TCP KeepAlive

1、在linux下&#xff0c;测试tcp保活&#xff0c;可以使用tcp自带keepalive功能。 2、几个重要参数&#xff1a; tcp_keepalive_time&#xff1a;对端在指定时间内没有数据传输&#xff0c;则向对端发送一个keepalive packet&#xff0c;单位&#xff1a;秒 tcp_keep…

学习开发小程序的起航日记

2024年3月16日 不知不觉中三月份还只剩了一半的光景&#xff0c;我想写的内容还很多没有写&#xff0c;或者更应该说&#xff0c;是想积累的还有很多。现在最应该去完善Java的内容&#xff0c;可还是想先等等。想等搞清楚小程序部分&#xff0c;想等积累完小程序的内容。 这几…

unraid docker.img扩容

unraid 弹Docker image disk utilization of 99%&#xff0c;容器下载/更新失败 我的版本是6.11.5&#xff0c;docker.img满了导致容器不能更新&#xff0c;遇到同样问题的可以先用docker命令清除一下仓库(当然不一定能清理出来&#xff0c;我已经清理过只清理出来1G多点&…

黑马微服务p30踩坑

报错详情 : orderservice开不起来 : 发生报错 : 然后检查了以下端口啥的 &#xff0c;配置啥的都是没有问题的 ; 解决办法 : 1 . 修改nacos1,2,3中的端口&#xff0c;将conf 中 cluster.conf中 的 127.0.0.1 全部改成自己本机的真实ipv4地址; 本机真实ipv4地址查看 :…

Java学习笔记(14)

常用API Java已经写好的各种功能的java类 Math Final修饰&#xff0c;不能被继承 因为是静态static的&#xff0c;所以使用方法不用创建对象&#xff0c;使用里面的方法直接 math.方法名 就行 常用方法 Abs,ceil,floor,round,max,minm,pow,sqrt,cbrt,random Abs要注意参数的…

数据结构:树和二叉树

树的概念 1.树是一种非线性的数据结构。它是由n个有限节点的集合。 2.树分为根节点和子树。根节点没有前驱节点。 3.树的子树是由一个个子树组成&#xff0c;它们可以看作一个个集合。每个集合下面又有集合。 因此&#xff0c;树是递归定义的。 树形结构中&#xff0c;子树…

Leetcode刷题-(21~25)-Java

算法是码农的基本功&#xff0c;也是各个大厂必考察的重点&#xff0c;让我们一起坚持写算法题吧。 遇事不决&#xff0c;可问春风&#xff0c;春风不语&#xff0c;即是本心。 我们在我们能力范围内&#xff0c;做好我们该做的事&#xff0c;然后相信一切都事最好的安排就可…

软件应用,物流运单填写模板,物流打印快递单教程,货运单怎么打印视频教程

软件应用&#xff0c;物流运单填写模板&#xff0c;物流打印快递单教程&#xff0c;货运单怎么打印视频教程 一、前言 以下软件操作教程以 佳易王物流货运管理系统软件V17.2为例说明 件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 1、物流管理系统打印&a…

【webrtc】m122:PacingController 源码阅读

PacingController 关系与BitrateProber 关系更为密切PacingController 内置BitrateProber G:\CDN\signalapp_webrtc\modules\pacing\pacing_controller.hPacingControllerBitrateProber prober_;可配置BitrateProber 是否激活 PacingController 关系与BitrateProber 关系更为密…

springboot基于spring boot的在线答题微信小程序

摘 要 在线答题微信小程序是考试中重要的一环&#xff0c;在线答题是学生获取任务信息的主要渠道。为了方便学生能够在网站上查看任务信息、考试&#xff0c;于是开发了基于 springboot框架设计与实现了一款简洁、轻便的在线答题微信小程序。本微信小程序解决了在线答题事务中的…

Android Kotlin知识汇总(三)Kotlin 协程

Kotlin的重要优势及特点之——结构化并发 Kotlin 协程让异步代码像阻塞代码一样易于使用。协程可大幅简化后台任务管理&#xff0c;例如网络调用、本地数据访问等任务的管理。本主题介绍如何使用 Kotlin 协程解决以下问题&#xff0c;从而让您能够编写出更清晰、更简洁的应用代…

AI毕业论文降重GPTS,避免AI检测,高效完成论文

视频演示 AI毕业论文降重GPTS&#xff0c;避免AI检测&#xff0c;高效完成论文&#xff01; 开发目的 “毕业论文降重”GPTS应用&#xff0c;作用为&#xff1a;重新表述学术论文&#xff0c;降低相似性评分&#xff0c;避免AI检测。 使用地址 地址&#xff1a;毕业论文降重…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:ColumnSplit)

将子组件纵向布局&#xff0c;并在每个子组件之间插入一根横向的分割线。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 可以包含子组件。 ColumnSplit通过分割线限制子组件的高度。初始…

跨境电商选品实战——Ownips公开数据信息安全采集+Python爬虫轻松搞定Lazada电商选品

文章目录 一、引言二、Lazada电商平台选品实战2.1、分析Lazada电商平台的商品列表接口2.2、定位商品列表计算逻辑2.3、封装高质量住宅IP2.4、运行爬虫 三、数据处理及选品分析四、Ownips——企业级全球静态住宅IP&#xff0c;高效采集公开数据 一、引言 互联网与外贸的结合&am…

考察c语言关键字

C语言——关键字 1.问题&#xff1a;简述goto语句的作用 答&#xff1a;无条件跳转 具体来说&#xff0c;其作用在于允许程序在执行时无条件地跳转到指定的标签位置&#xff0c;并从该标签位置继续执行。通过goto语句&#xff0c;可以实现程序流程的无条件转移&#xff0c;使得…

【python】爬取杭州市二手房销售数据做数据分析【附源码】

一、背景 在数据分析和市场调研中&#xff0c;获取房地产数据是至关重要的一环。本文介绍了如何利用 Python 中的 requests、lxml 库以及 pandas 库&#xff0c;结合 XPath 解析网页信息&#xff0c;实现对链家网二手房销售数据的爬取&#xff0c;并将数据导出为 Excel 文件的过…

Visual Studio项目模板的创建与使用

Visual Studio项目模板的创建、使用、删除 创建模板项目模板的使用模板的删除 创建模板 点击项目&#xff0c;点击导出模板 选择你要创建哪个项目的项目模板&#xff0c;点击下一步 输入你的模板名称并添加模板说明&#xff0c;方便记忆 项目模板的使用 点击创建新项目 输入刚刚…

什么是VR应急预案演练虚拟化|VR体验馆加盟|元宇宙文旅

VR 应急预案演练虚拟化指的是利用虚拟现实&#xff08;Virtual Reality&#xff0c;VR&#xff09;技术进行应急预案演练的过程。在传统的应急预案演练中&#xff0c;人们通常需要在实际场地或模拟环境中进行演练&#xff0c;这可能存在一些限制&#xff0c;如成本高昂、场地受…

Cesium 获取 3dtileset的包围盒各顶点坐标

Cesium 获取 3dtileset的包围盒各顶点坐标 /*** 获取 3dtileset的包围盒各顶点坐标, z 方向取高度最低的位置* param {*} tileset* param {*} options* returns* ref https://blog.csdn.net/STANDBYF/article/details/135012273* ref https://community.cesium.com/t/accurate-…