spring AOP实现操作日志记录@OperationLog

news2024/10/22 14:46:59

需要引入的依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

表结构设计 两张表,一张记录操作日志,一张记录错误日志

DROP TABLE IF EXISTS "public"."sys_opera_log";
CREATE TABLE "public"."sys_opera_log" (
  "id" int8 NOT NULL,
  "module" varchar(255) COLLATE "pg_catalog"."default",
  "type" varchar(255) COLLATE "pg_catalog"."default",
  "desc" varchar(255) COLLATE "pg_catalog"."default",
  "method" varchar(255) COLLATE "pg_catalog"."default",
  "req_type" varchar(255) COLLATE "pg_catalog"."default",
  "req_param" text COLLATE "pg_catalog"."default",
  "res_param" text COLLATE "pg_catalog"."default",
  "user_id" varchar(64) COLLATE "pg_catalog"."default",
  "user_name" varchar(255) COLLATE "pg_catalog"."default",
  "ip" varchar(255) COLLATE "pg_catalog"."default",
  "create_time" timestamp(6),
  "uri" varchar(255) COLLATE "pg_catalog"."default",
  "take_up_time" int8
)
;
COMMENT ON COLUMN "public"."sys_opera_log"."module" IS '功能模块';
COMMENT ON COLUMN "public"."sys_opera_log"."type" IS '操作类型';
COMMENT ON COLUMN "public"."sys_opera_log"."desc" IS '操作描述';
COMMENT ON COLUMN "public"."sys_opera_log"."method" IS '请求方法';
COMMENT ON COLUMN "public"."sys_opera_log"."req_type" IS '请求类型';
COMMENT ON COLUMN "public"."sys_opera_log"."req_param" IS '请求参数';
COMMENT ON COLUMN "public"."sys_opera_log"."res_param" IS '响应参数';
COMMENT ON COLUMN "public"."sys_opera_log"."ip" IS 'ip地址';
COMMENT ON COLUMN "public"."sys_opera_log"."create_time" IS '时间';
COMMENT ON COLUMN "public"."sys_opera_log"."take_up_time" IS '耗时';
COMMENT ON TABLE "public"."sys_opera_log" IS '系统操作日志';

-- ----------------------------
-- Primary Key structure for table sys_opera_log
-- ----------------------------
ALTER TABLE "public"."sys_opera_log" ADD CONSTRAINT "sys_opear_log_pkey" PRIMARY KEY ("id");
DROP TABLE IF EXISTS "public"."sys_error_log";
CREATE TABLE "public"."sys_error_log" (
  "id" int8 NOT NULL,
  "req_param" text COLLATE "pg_catalog"."default",
  "name" varchar(255) COLLATE "pg_catalog"."default",
  "message" text COLLATE "pg_catalog"."default",
  "user_id" varchar(64) COLLATE "pg_catalog"."default",
  "user_name" varchar(255) COLLATE "pg_catalog"."default",
  "method" varchar(255) COLLATE "pg_catalog"."default",
  "uri" varchar(255) COLLATE "pg_catalog"."default",
  "ip" varchar(255) COLLATE "pg_catalog"."default",
  "create_time" timestamp(6),
  "type" varchar(255) COLLATE "pg_catalog"."default"
)
;
COMMENT ON COLUMN "public"."sys_error_log"."req_param" IS '请求参数';
COMMENT ON COLUMN "public"."sys_error_log"."name" IS '异常名称';
COMMENT ON COLUMN "public"."sys_error_log"."message" IS '异常信息';
COMMENT ON COLUMN "public"."sys_error_log"."user_id" IS '操作用户id';
COMMENT ON COLUMN "public"."sys_error_log"."user_name" IS '操作用户名字';
COMMENT ON COLUMN "public"."sys_error_log"."method" IS '操作方法';
COMMENT ON COLUMN "public"."sys_error_log"."ip" IS 'ip';
COMMENT ON COLUMN "public"."sys_error_log"."create_time" IS '时间';
COMMENT ON COLUMN "public"."sys_error_log"."type" IS '请求方式';
COMMENT ON TABLE "public"."sys_error_log" IS '系统异常日志';

-- ----------------------------
-- Primary Key structure for table sys_error_log
-- ----------------------------
ALTER TABLE "public"."sys_error_log" ADD CONSTRAINT "sys_error_log_pkey" PRIMARY KEY ("id");

1.创建一个自定义注解接口

package com.common.aop;

import java.lang.annotation.*;

/**
 * @author qy
 * @date 2024-10-22 09:51:09
 */
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented
public @interface OperaLog {

    /**
     * 操作模块
     */
    String operaModule() default "";

    /**
     * 操作类型
     */
    String operaType() default "";

    /**
     * 操作说明
     */
    String operaDesc() default "";
}

2.创建切面处理类,用于操作日志记录

package com.serviceImpl.aop;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import com.alibaba.fastjson2.JSON;
import com.bjcj.kpi.common.aop.OperaLog;
import com.bjcj.kpi.mapper.sys.SysUserMapper;
import com.bjcj.kpi.model.pojo.sys.SysErrorLog;
import com.bjcj.kpi.model.pojo.sys.SysOperaLog;
import com.bjcj.kpi.serviceImpl.sys.SysErrorLogService;
import com.bjcj.kpi.serviceImpl.sys.SysOperaLogService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * 切面处理类,操作日志记录处理
 *
 * @author qy
 * @date 2024-10-22 09:56:02
 */
@Aspect
@Component
@Slf4j
public class OperaLogAspect {


    @Resource
    SysOperaLogService sysOperaLogService;

    @Resource
    SysUserMapper sysUserMapper;

    @Resource
    SysErrorLogService sysErrorLogService;


    /**
     * 统计请求的处理时间
     */
    ThreadLocal<Long> startTime = new ThreadLocal<>();


    /**
     * 设置操作日志切入点 记录操作日志 在注解的位置切入代码
     */
    @Pointcut("@annotation(com.bjcj.kpi.common.aop.OperaLog)")
    public void operaLogPointCut() {

    }

    @Before("operaLogPointCut()")
    public void doBefore() {
        // 接收到请求,记录请求开始时间
        startTime.set(System.currentTimeMillis());
    }

    /**
     * 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
     *
     * @param joinPoint 切入点
     * @param keys      返回结果
     */
    @AfterReturning(value = "operaLogPointCut()", returning = "keys")
    public void saveOperaLog(JoinPoint joinPoint, Object keys) {

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

        try {
            SysOperaLog operaLog = new SysOperaLog();

            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取切入点所在的方法
            Method method = signature.getMethod();
            // 获取操作
            OperaLog opLog = method.getAnnotation(OperaLog.class);
            if (opLog != null) {
                String operaModule = opLog.operaModule();
                String operaType = opLog.operaType();
                String operaDesc = opLog.operaDesc();
                operaLog.setModule(operaModule);
                operaLog.setType(operaType);
                operaLog.setDesc(operaDesc);

            }
            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            // 获取请求的方法名
            String methodName = method.getName();
            methodName = className + "." + methodName;

            // 请求方法
            operaLog.setMethod(methodName);

            operaLog.setReqType(request.getMethod());

            // 请求的参数
            String params = "";
            if (StrUtil.equals(request.getMethod(), "GET")
                    || StrUtil.equals(request.getMethod(), "DELETE")) {

                params = JSON.toJSONString(getRequestParam(request));
            }
            if (StrUtil.equals(request.getMethod(), "POST")) {

                params = JSON.toJSONString(joinPoint.getArgs()[0]);
            }
            operaLog.setReqParam(params);

            operaLog.setUserId(String.valueOf(StpUtil.getLoginId()));
            operaLog.setUserName(this.sysUserMapper.selectById(String.valueOf(StpUtil.getLoginId())).getUsername());
            operaLog.setIp(JakartaServletUtil.getClientIP(request, "Cdn-Src-Ip"));
            operaLog.setUri(request.getRequestURI());
            operaLog.setResParam(JSON.toJSONString(keys));
            operaLog.setTakeUpTime(System.currentTimeMillis() - startTime.get());

            sysOperaLogService.save(operaLog);

        } catch (Exception e) {
            log.error("操作日志生成错误:", e);
        }

    }


    /**
     * <h2>异常返回通知,用于拦截异常日志信息 连接点抛出异常后执行</h2>
     *
     * @param joinPoint:
     * @param e:
     * @return void
     * @author Guow
     * @date 2023/12/8 11:00
     */
    @AfterThrowing(pointcut = "operaLogPointCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();

            // 获取切入点所在的方法
            Method method = signature.getMethod();

            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();

            // 请求的参数
            String params = "";
            if (StrUtil.equals(request.getMethod(), "GET")
                    || StrUtil.equals(request.getMethod(), "DELETE")) {

                params = JSON.toJSONString(getRequestParam(request));
            }
            if (StrUtil.equals(request.getMethod(), "POST")) {

                params = JSON.toJSONString(joinPoint.getArgs()[0]);
            }
            sysErrorLogService.save(
                    SysErrorLog.builder()
                            .reqParam(params)
                            .method(className + "." + method.getName())
                            .name(e.getClass().getName())
                            .message(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()))
                            .userId(String.valueOf(StpUtil.getLoginId()))
                            .userName(this.sysUserMapper.selectById(String.valueOf(StpUtil.getLoginId())).getUsername())
                            .uri(request.getRequestURI())
                            .type(request.getMethod())
                            .ip(JakartaServletUtil.getClientIP(request, "Cdn-Src-Ip"))
                            .build()
            );
        } catch (Exception e2) {
            log.error("异常日志生成错误:", e2);
        }
    }


    /**
     * <h2>获取get请求参数</h2>
     *
     * @return java.util.Map<java.lang.String, java.lang.String>
     * @author Guow
     * @date 2023/12/1 9:18
     */
    private Map<String, String> getRequestParam(HttpServletRequest request) {
        // 返回体
        HashMap<String, String> res = new HashMap<>();

        // 获取 GET 请求参数
        Map<String, String[]> parameterMap = request.getParameterMap();

        // 遍历参数Map
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            String paramName = entry.getKey();
            String[] paramValues = entry.getValue();

            if (paramValues != null && paramValues.length > 0) {
                res.put(paramName, paramValues[0]);
            }
        }
        return res;
    }

    /**
     * <h2>转换异常信息为字符串</h2>
     *
     * @param exceptionName:
     * @param exceptionMessage:
     * @param elements:
     * @return java.lang.String
     * @author Guow
     * @date 2023/12/8 10:56
     */
    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuffer strbuff = new StringBuffer();
        for (StackTraceElement stet : elements) {
            strbuff.append(stet + "<br/>");
        }
        String message = exceptionName + ":" + exceptionMessage + "<br/>" + strbuff.toString();
        return message;
    }


}

3.常量工具类

package com.bjcj.kpi.common.constant;

/**
 * @author qy
 * @date 2024-10-22 09:45
 */
public class OperaLogConstant {

    /**
     * 新增/编辑
     */
    public static final String CREATE_OR_UPDATE = "新增/编辑";

    /**
     * 新增
     */
    public static final String CREATE = "新增";

    /**
     * 更新
     */
    public static final String UPDATE = "更新";

    /**
     * 浏览
     */
    public static final String LOOK = "浏览";

    /**
     * 删除
     */
    public static final String DELETE = "删除";

    /**
     * 下载
     */
    public static final String DOWNLOAD = "下载";

    /**
     * 上传
     */
    public static final String UPLOAD = "上传";

    /**
     * 通知
     */
    public static final String NOTICE = "通知";

}

4.使用示例,在接口上增加注解即可


    @DeleteMapping("/data/{id}")
    @Operation(summary = "删除字典值", description = "删除字典值")
    @ApiOperationSupport(order = 5)
    @Transactional(rollbackFor = Exception.class)
    @OperaLog(operaModule = "删除字典值",operaType = OperaLogConstant.DELETE)
    public JsonResult deleteDictValue(@PathVariable("id") String id) {
        return sysDictService.del(id);
    }

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

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

相关文章

Sigrity Power SI Model Extraction模式如何提取电源网络的S参数和阻抗操作指导(一)

Sigrity Power SI Model Extraction模式如何提取电源网络的S参数和阻抗操作指导(一) Sigrity PowerSI是频域电磁场仿真工具,以下图为例介绍如果用它观测电源的网络的S参数以及阻抗的频域曲线. 观测IC端电源网络的自阻抗 1. 用powerSi.exe打开该SPD文件

工业相机详解及选型

工业相机相对于传统的民用相机而言&#xff0c;具有搞图像稳定性,传输能力和高抗干扰能力等&#xff0c;目前市面上的工业相机大多数是基于CCD&#xff08;Charge Coupled Device)或CMOS(Complementary Metal Oxide Semiconductor)芯片的相机。 一&#xff0c;工业相机的分类 …

sentinel原理源码分析系列(六)-统计指标

调用链和统计节点构建完成&#xff0c;进入统计指标插槽&#xff0c;统计指标在最后执行的&#xff0c;等后面的插槽执行完&#xff0c;资源调用完成了&#xff0c;根据资源调用情况累计。指标统计是最重要的插槽&#xff0c;所有的功能都依靠指标数据&#xff0c;指标的正确与…

你知道什么叫数控加工中心吗?

加工中心是一种高度机电一体化的数控机床&#xff0c;具有刀库&#xff0c;自动换刀功能&#xff0c;对工件一次装夹后进行多工序加工的数控机床。通过计算的控制系统和稳定的机械结构&#xff0c;加工中心能够实现高精度的加工&#xff0c;确保工件的尺寸精度和表面质量。通过…

实用好助手

在现代职场中&#xff0c;拥有高效且适用的工具能够显著提升我们的工作效率与质量。除了常见的办公软件&#xff0c;还有许多小众但非常实用的工具可以大幅度优化工作流程。以下是另外五个推荐的工作软件&#xff0c;它们各自具备独特的功能与优势&#xff0c;值得一试。 1 …

【Docker系列】在 Docker 容器中打印和配置环境变量

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

双十一有哪些值得买的东西?2024年最全双十一好物推荐榜单来了!

双十一能够入手的好东西那肯定是非常多的&#xff0c;不过要想买到性价比高、实用性强的好物&#xff0c;就必须得做些功课了。作为一个智能家居和数码领域的博主&#xff0c;自然知道每年双十一买什么是最划算的。如果有朋友正在为双十一不知道买什么而发愁&#xff0c;那就快…

python+大数据+基于热门视频的数据分析研究【内含源码+文档+部署教程】

博主介绍&#xff1a;✌全网粉丝10W,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业毕业设计项目实战6年之久&#xff0c;选择我们就是选择放心、选择安心毕业✌ &#x1f345;由于篇幅限制&#xff0c;想要获取完整文章或者源码&#xff0c;或者代做&am…

登录后端笔记(一):注册、登录;基于MD5加密

一、注册 一、参数&#xff1a;lombok pom.xml里引入依赖&#xff1b; 二、响应数据&#xff1a;Result 原视频 两个注解对应有参无参生成构造方法&#xff1b; data类型是泛型T&#xff0c;即data在使用时可对应object可对应string字符串可对应bean对象可对应map等&#x…

微信碰一碰支付系统有哪些好的?教程详解抢先看!

支付宝“碰一碰支付”的风刚刚刮起来&#xff0c;它的老对手微信便紧随其后&#xff0c;推出了自己的碰一碰支付设备&#xff0c;再次印证了这个项目市场前景广阔的同时&#xff0c;也让与碰一碰支付系统相关问题的热度又上了一层楼&#xff0c;尤其是微信碰一碰支付系统有哪些…

炒股VS炒游戏装备,哪个更好做

这个项目&#xff0c;赚个10%都是要被嫌弃的 虽然天天都在抒发自己对股市的看法&#xff0c;但自己自始至终也没有买进任何一支股票。之所以对这个话题感兴趣&#xff0c;着实是因为手上的游戏搬砖项目也是国际性买卖&#xff0c;跟国际形势&#xff0c;国际汇率挂钩&#xff0…

RAG总结及前沿之Meta-Chunking切分思路及VisRAG多模态实现机制解读

今天我们来看两个工作&#xff0c;一个是关于RAG的切分策略&#xff0c;Meta-Chunking&#xff0c;里面基于数学常识提到的边际采样分块&#xff08;Margin Sampling Chunking&#xff09;通过LLMs对连续句子是否需要分割进行二元分类&#xff0c;基于边际采样得到的概率差异来…

基于SSM+微信小程序的房屋租赁管理系统(房屋2)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 基于SSM微信小程序的房屋租赁管理系统实现了有管理员、中介和用户。 1、管理员功能有&#xff0c;个人中心&#xff0c;用户管理&#xff0c;中介管理&#xff0c;房屋信息管理&#xff…

Nest.js 实战 (十五):前后端分离项目部署的最佳实践

☘️ 前言 本项目是一个采用现代前端框架 Vue3 与后端 Node.js 框架 Nest.js 实现的前后端分离架构的应用。Vue3 提供了高性能的前端组件化解决方案&#xff0c;而 Nest.js 则利用 TypeScript 带来的类型安全和模块化优势构建了一个健壮的服务端应用。通过这种技术栈组合&…

信雅纳Chimera 100G网络损伤仪助力Parallel Wireless开展5G RAN无线前传网络的损伤模拟

背景介绍 Parallel Wireless 为移动运营商提供唯一全覆盖的(5G/4G/3G/2G&#xff09;软件支持的本地 OpenRAN (ORAN) 解决方案。该公司与全球 50 多家领先运营商合作&#xff0c;并被 Telefonica 和 Vodafone 评为表现最佳的供应商。Parallel Wireless 在多技术、开放式虚拟化…

【多视图聚类】【ICCV 2023】基于交叉视图拓扑一致互补信息的深度多视图聚类

0.论文摘要 多视图聚类旨在从不同的来源或视角提取有价值的信息。多年来&#xff0c;深度神经网络在多视图聚类中展示了其优越的表示学习能力&#xff0c;并取得了令人印象深刻的性能。然而&#xff0c;大多数现有的深度聚类方法致力于合并和探索跨多个视图的一致潜在表示&…

Java网络编程的基础:计算机网络

在学习 Java 网络编程之前&#xff0c;我们先来了解什么是计算机网络。 计算机网络是指两台或更多的计算机组成的网络&#xff0c;在同一个网络中&#xff0c;任意两台计算机都可以直接通信&#xff0c;因为所有计算机都需要遵循同一种网络协议。 下面是一张简化的网络拓扑图…

工业以太网之战:EtherCAT是如何杀出重围的?

前言 EtherCAT 是一种开放的实时工业以太网协议&#xff0c;由德国倍福公司开发并在 2003 年 4 月的汉诺威工业博览会上首次亮相&#xff0c;目前由 EtherCAT 技术协会&#xff08;ETG&#xff09;进行维护和推广。经过 21 年的不断发展&#xff0c;EtherCAT 显示出极强的生命…

2.1_Linux发展与基础

Linux基础知识 Shell 命令执行环境&#xff1a; 命令提示符的组成&#xff1a;(用户名主机名)-[当前路径]权限提示符,例&#xff1a;&#xff08;kali㉿kali)-[~]$ ~ 表示所在目录为家目录:其中root用户的家目录是/root&#xff0c;普通用户的家目录在/home下 # 表示用户的权…

Python酷库之旅-第三方库Pandas(148)

目录 一、用法精讲 671、pandas.Timestamp.day_name方法 671-1、语法 671-2、参数 671-3、功能 671-4、返回值 671-5、说明 671-6、用法 671-6-1、数据准备 671-6-2、代码示例 671-6-3、结果输出 672、pandas.Timestamp.dst方法 672-1、语法 672-2、参数 672-3、…