java AOP环绕通知记录操作日志

news2024/11/15 9:12:44

一.创建数据库日志表

CREATE TABLE `uc_system_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `user_code` varchar(64) DEFAULT NULL COMMENT '用户编码',
  `user_name` varchar(128) DEFAULT NULL COMMENT '用户名称',
  `is_login` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否登录',
  `client` varchar(32) DEFAULT NULL COMMENT '客户端(PC-网页、APP-移动应用、API-开放接口)',
  `module_code` varchar(32) DEFAULT NULL COMMENT '操作模块',
  `action_code` varchar(32) DEFAULT NULL COMMENT '操作类型/行为',
  `remark` text COMMENT '操作描述(失败原因)',
  `is_success` tinyint(4) NOT NULL DEFAULT '1' COMMENT '是否操作成功',
  `ip` varchar(32) DEFAULT NULL COMMENT 'IP地址',
  `user_dept_code` varchar(64) DEFAULT NULL COMMENT '用户操作时所在组织编码',
  `user_dept_name` varchar(128) DEFAULT NULL COMMENT '用户操作时所在组织名称',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `app_code` varchar(64) DEFAULT NULL,
  `app_name` varchar(128) DEFAULT NULL,
  `refer_app_code` varchar(64) DEFAULT NULL COMMENT '来源应用编码',
  `refer_app_name` varchar(128) DEFAULT NULL COMMENT '来源应用名称',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_user_code` (`user_code`) USING BTREE,
  KEY `idx_app_code` (`app_code`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=969269 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='操作日志表';

java对象


import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import lombok.experimental.Accessors;

import java.time.LocalDateTime;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("uc_system_log")
public class SystemLog{

    /**
     * 主键ID
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 用户编码
     */
    @TableField("user_code")
    private String userCode;

    /**
     * 用户编码
     */
    @TableField("user_name")
    private String userName;

    /**
     * 是否登录
     * 0-失败,1-成功
     */
    @TableField("is_login")
    private Boolean login;

    /**
     * 客户端
     * PC-网页、APP-移动应用、API-开放接口
     */
    @TableField("client")
    private SystemLogClientEnum client;

    /**
     * 操作模块
     */
    @TableField("module_code")
    private SystemLogModuleEnum module;

    /**
     * 操作类型(行为)
     */
    @TableField("action_code")
    private SystemLogActionEnum action;

    /**
     * 操作描述
     * 失败时有值
     */
    @TableField("remark")
    private String remark;

    /**
     * 操作结果
     * 0-失败,1-成功
     */
    @TableField("is_success")
    private Boolean success;

    /**
     * IP地址
     */
    @TableField("ip")
    private String ip;

    /**
     * 用户操作时所在组织编码
     */
    @TableField("user_dept_code")
    private String userDeptCode;

    /**
     * 用户操作时所在组织名称
     */
    @TableField("user_dept_name")
    private String userDeptName;

    /**
     * 创建时间
     */
    @TableField("gmt_create")
    private LocalDateTime gmtCreate;

    /**
     * 应用编码
     */
    @TableField("app_code")
    private String appCode;

    /**
     * 应用名称
     */
    @TableField("app_name")
    private String appName;

    /**
     * 来源应用编码
     */
    @TableField("refer_app_code")
    private String referAppCode;

    /**
     * 来源应用名称
     */
    @TableField("refer_app_name")
    private String referAppName;

}

 2.定义枚举


public interface BaseStringEnum {

    /**
     * 获取枚举值
     *
     * @return
     */
    String getCode();

    /**
     * 获取枚举文本
     *
     * @return
     */
    String getName();

    /**
     * 根据枚举值和type获取枚举
     */
    static <T extends BaseStringEnum> T getEnum(Class<T> type, String value) {
        T[] objs = type.getEnumConstants();
        for (T em : objs) {
            if (em.getCode().equals(value)) {
                return em;
            }
        }
        return null;
    }
}

系统模块枚举 

import com.dcqc.summarize.service.BaseStringEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Arrays;
/**
 * 系统模块枚举
 *
 * @author ws
 * Create on 2023-06-09
 */
@Getter
@AllArgsConstructor
public enum SystemLogModuleEnum  implements BaseStringEnum {

    LOGIN("登录/登出"),
    REGULAR_CONFIG("规则配置"),
    USER_MANAGE("用户管理"),
    MENU_MANAGE("菜单管理"),
    ROLE_MANAGE("角色管理"),
    RESOURCE_MANAGE("资源管理"),
    DEPT_MANAGE("组织管理"),
    APP_MANAGE("应用管理"),
    ATTACH_MANAGE("附件管理"),
    TASK_MANAGE("任务管理"),
    OTHER("其他"),
    AUTH_MANAGE("授权管理"),
    ORDER_MANAGE("工单管理"),
    ;

    private String desc;

    @Override
    public String getCode() {
        return name();
    }

    @Override
    public String getName() {
        return desc;
    }

    public static SystemLogModuleEnum valueByCode(String code) {
        return Arrays.stream(SystemLogModuleEnum.values())
                .filter(item -> item.getCode().equals(code))
                .findFirst()
                .orElse(null);
    }
}

 日志行为枚举类

import com.dcqc.summarize.service.BaseStringEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;

/**
 * 日志行为枚举类
 *
 * @author ws
 * 2023-06-19
 */
@Getter
@AllArgsConstructor
public enum SystemLogActionEnum implements BaseStringEnum {

    QR_LOGIN("扫码登录"),
    ENABLE("启用"),
    REGISTER("注册"),
    DISABLE("禁用"),
    ADD("新增"),
    UPDATE("更新"),
    DELETE("删除"),
    OTHER("其他"),
    AUTH_CODE_LOGIN("授权码免登"),
    USER_CODE_LOGIN("用户编码免登"),
    IRS_TOKEN_LOGIN("IRS免登"),
    REFRESH_COOKIE("刷新COOKIE"),
    LOGOUT("退出登录"),
    INTER_LOGIN("聚合登录"),
    CHANGE_USER_DEPT("变更用户部门"),
    CHANGE_USER_ROLE("变更用户角色"),
    UPLOAD("上传"),
    DOWNLOAD("下载"),
    EXECUTE("执行"),
    // 免登包括了认证和授权
    SSO_LOGIN("SSO免登"),
    // 单纯的认证,不包括授权,只返回简单的用户信息
    SSO_AUTHENTICATION("SSO认证"),
    AGREE("同意"),
    REJECT("拒绝"),
    DELETE_USER("删除用户"),
    ;

    private String desc;

    @Override
    public String getCode() {
        return name();
    }

    @Override
    public String getName() {
        return desc;
    }

    public static SystemLogActionEnum valueByCode(String code) {
        return Arrays.stream(SystemLogActionEnum.values())
                .filter(item -> item.getCode().equals(code))
                .findFirst()
                .orElse(null);
    }

}

日志客户端枚举 


import com.dcqc.summarize.service.BaseStringEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Arrays;

/**
 * 日志客户端枚举类
 *
 * @author ws
 * Create on 2023-06-09
 */
@Getter
@AllArgsConstructor
public enum SystemLogClientEnum implements BaseStringEnum {

    PC("网页"),
    APP("移动应用"),
    OPEN_API("开放接口"),
    API("接口"),
    OTHER("其他"),
    ;

    private String desc;

    public static SystemLogClientEnum valueByCode(String code) {
        return Arrays.stream(SystemLogClientEnum.values())
                .filter(systemLogClientEnum -> systemLogClientEnum.name().equals(code))
                .findFirst()
                .orElse(null);
    }

    @Override
    public String getCode() {
        return name();
    }

    @Override
    public String getName() {
        return desc;
    }


}

三.记录日志注解

import com.dcqc.summarize.entity.SystemLogClientEnum;
import com.dcqc.summarize.entity.SystemLogModuleEnum;
import com.dcqc.summarize.entity.SystemLogActionEnum;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


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

    SystemLogModuleEnum module() default SystemLogModuleEnum.LOGIN;

    SystemLogActionEnum action() default SystemLogActionEnum.QR_LOGIN;

    SystemLogClientEnum client() default SystemLogClientEnum.OTHER;

    String appCode() default "";

}

四.Controller方法添加@RecordLog注解

  @ApiOperation("用户登陆")
    @PostMapping("/login")
    @RecordLog(module = SystemLogModuleEnum.LOGIN, action = SystemLogActionEnum.QR_LOGIN, client = SystemLogClientEnum.PC)
    public Result login(@RequestBody LoginRO entity) {
        try {
           //登陆业务
        }catch (Exception e) {
            throw new UserLoginErrorException("userCode", "userName", e);
        }
        return Result.success();
    }

用户登陆异常类 

import lombok.Data;

@Data
public class UserLoginErrorException extends RuntimeException {

    private String userCode;

    private String userName;

    public UserLoginErrorException(String userCode, String userName, Throwable cause) {
        super(userCode + ":" + userName, cause);
        this.userCode = userCode;
        this.userName = userName;
    }

}

五.AOP切面

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

public class BaseAspect {
    public BaseAspect() {
    }

    protected Class<?> getClass(JoinPoint joinPoint) {
        return joinPoint.getTarget().getClass();
    }

    protected Method getMethod(JoinPoint joinPoint) {
        return ((MethodSignature)joinPoint.getSignature()).getMethod();
    }

    protected <T extends Annotation> T getAnn(JoinPoint joinPoint, Class<T> annClazz) {
        return this.getAnn(this.getMethod(joinPoint), annClazz);
    }

    protected <T extends Annotation> T getAnn(Method method, Class<T> annClazz) {
        return AnnotationUtils.findAnnotation(method, annClazz);
    }

    protected String[] getParamNames(Method method) {
        return (new LocalVariableTableParameterNameDiscoverer()).getParameterNames(method);
    }



    protected HttpServletRequest getRequest() {
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    }
}

import cn.hutool.extra.servlet.ServletUtil;
import com.dcqc.summarize.entity.SystemLog;
import com.dcqc.summarize.entity.SystemLogClientEnum;
import com.dcqc.summarize.exception.UserLoginErrorException;
import com.dcqc.summarize.mapper.SystemLogMapper;
import com.dcqc.summarize.service.RecordLog;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;

import java.util.Objects;
@Slf4j
@Aspect
@Component
public class RecordLogAspect extends BaseAspect {
    @Autowired
    private SystemLogMapper systemLogMapper;
    /**
     * 处理日志注解
     *
     * @param point 切点
     * @return 处理结果
     * @throws Throwable 处理异常
     */
    @Around("@annotation(com.dcqc.summarize.service.RecordLog)")
    public Object recordLogAround(ProceedingJoinPoint point) throws Throwable {
        SystemLog systemLogDTO = new SystemLog();
        try {
            // 1,获取操作日志切面注解
            RecordLog recordLog = getAnn(point, RecordLog.class);
            if (Objects.isNull(recordLog)) {
                return point.proceed();
            }

            // 2,获取注解对应的请求的客户端IP
            HttpServletRequest httpRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String clientIP = ServletUtil.getClientIP(httpRequest);

            // 3,从header里取值
            String appCode = httpRequest.getHeader("appCode");
            if (StringUtils.isNotBlank(appCode)) {
                systemLogDTO.setAppCode(appCode);
            }
            // 获取客户端类型
            SystemLogClientEnum client = recordLog.client();
            if (SystemLogClientEnum.OTHER.equals(client)) {
                if (httpRequest.getRequestURI().contains("openapi")) {
                    client = SystemLogClientEnum.OPEN_API;
                } else if (httpRequest.getRequestURI().contains("api")) {
                    client = SystemLogClientEnum.API;
                } else {
                    client = SystemLogClientEnum.PC;
                }
            }

            // 4,封装操作日志业务信息
            systemLogDTO.setIp(clientIP);
            systemLogDTO.setModule(recordLog.module());
            systemLogDTO.setAction(recordLog.action());
            systemLogDTO.setClient(client);
        } catch (Exception e) {
            log.error("操作日志解析异常", e);
        }
        // 6,执行切点并记录操作日志
        Object result = null;
        try {
            result = point.proceed();
        } catch (Exception e) {
            if (e instanceof UserLoginErrorException) {
                systemLogDTO.setUserCode(((UserLoginErrorException) e).getUserCode());
                systemLogDTO.setUserName(((UserLoginErrorException) e).getUserName());
            }
            // 6.1,执行失败,记录失败日志
            systemLogDTO.setRemark(e.getMessage());
            systemLogMapper.insert(systemLogDTO);
            throw e;
        }
        // 6.1,执行成功,记录成功日志
        systemLogMapper.insert(systemLogDTO);
        return result;
    }
}

调用方法,可以看到日志被记录 

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

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

相关文章

吴恩达深度学习笔记:优化算法 (Optimization algorithms)2.3-2.5

目录 第二门课: 改善深层神经网络&#xff1a;超参数调试、正 则 化 以 及 优 化 (Improving Deep Neural Networks:Hyperparameter tuning, Regularization and Optimization)第二周&#xff1a;优化算法 (Optimization algorithms)2.3 指数加权平均数&#xff08;Exponential…

Java-数据库连接(JDBC小白教学)

&#xff01;文章最后附有完整代码&#xff01; 目录 &#x1f516;JDBC概述 &#x1f516;JDBC连接数据库 &#x1f516;添加数据&#xff08;insert&#xff09; &#x1f516;修改数据&#xff08;Update&#xff09; &#x1f516;删除数据&#xff08;delete&#x…

边缘计算教学实训解决方案

一、引言 随着物联网、5G通信技术的快速发展&#xff0c;边缘计算作为云计算的延伸和补充&#xff0c;正逐渐成为支撑智能物联网、实时数据分析等领域的重要技术。唯众旨在为职业院校设计一套全面的边缘计算教学实训体系&#xff0c;通过理论与实践相结合的方式&#xff0c;培…

深度剖析MyBatis的二级缓存

二级缓存的原理 MyBatis 二级缓存的原理是什么&#xff1f; 二级缓存的原理和一级缓存一样&#xff0c;第一次查询会将数据放到 缓存 中&#xff0c;然后第二次查询直接去缓存读取。但是一级缓存是基于 SqlSession 的&#xff0c;二级缓存是基于 mapper 的 namespace 的。也就是…

webhook 和 API:你了解吗

Webhooks 是许多 API 的补充。通过设置 webhook 系统&#xff0c;系统 B 可以注册接收有关系统 A 某些更改的通知。当更改发生时&#xff0c;系统 A 推送 更改到系统 B&#xff0c;通常是以发出 HTTP POST 请求的形式。 Webhooks 旨在消除或减少不断轮询数据的需要。但根据我的…

如何购买RAKsmart的国外高防服务器?

随着互联网的快速发展&#xff0c;网络安全问题日益凸显&#xff0c;特别是对于拥有在线业务的企业或个人来说&#xff0c;选择一款高防服务器显得尤为关键。美国RAKsmart作为知名的服务器提供商&#xff0c;其高防服务器因其卓越的性能和安全性&#xff0c;受到了广大用户的青…

一步一步带你做网络工程

网络工程怎么做 一、网络设备交换机的应用&#xff1a; 要求&#xff1a;在此接入交换机S3700&#xff0c;上划分两个vlan&#xff0c;vlan10和vlan20分别有两个PC&#xff0c;按拓扑图完成要求&#xff1a; 划分vlan添加端口 sys [Huawei]sys S1 [S1]undo in e [S1]undo t…

【全开源】JAVA同城圈子达人交友系统源码支持微信小程序+微信公众号+H5+APP

同城达人 精准匹配&#xff1a;系统通过用户填写的个人信息和兴趣爱好&#xff0c;运用智能算法进行精准匹配&#xff0c;推荐合适的同城朋友。多种互动方式&#xff1a;提供在线聊天、语音通话、视频交流等多种互动方式&#xff0c;让用户能够随时随地与朋友保持联系。本地生…

人工智能AI聊天chatgpt系统openai对话创作文言一心源码APP小程序功能介绍

你提到的是一个集成了多种智能AI创作能力的系统&#xff0c;它结合了OpenAI的ChatGPT、百度的文言一心&#xff08;ERNIE Bot&#xff09;以及可能的微信WeLM&#xff08;或其他类似接口&#xff09;等。这样的系统确实能够极大地提高创作效率&#xff0c;并且在各种场景下为用…

SpringBoot接收参数的19种方式

https://juejin.cn/post/7343243744479625267?share_token6D3AD82C-0404-47A7-949C-CA71F9BC9583

秋招算法——AcWing101——拦截导弹

文章目录 题目描述思路分析实现源码分析总结 题目描述 思路分析 目前是有一个笨办法&#xff0c;就是创建链表记录每一个最长下降子序列所对应的节点的链接&#xff0c;然后逐个记录所有结点的访问情况&#xff0c;直接所有节点都被访问过。这个方法不是很好&#xff0c;因为需…

【Qt】Qt组件设置背景图片

1. 方法1&#xff08;paintEvent方式&#xff09; 使用paintEvent()实现 1. .h文件中添加虚函数 protected:void paintEvent(QPaintEvent *event) override;添加虚函数方法&#xff1a; 选中父类&#xff0c;点击鼠标右键点击重构点击 Insert Virtual Funtion of Base Class…

小朋友台灯什么品牌好,分享最好的台灯品牌排行榜

小朋友台灯什么品牌好&#xff1f;台灯作为我们日常生活中重要的桌面照明工具&#xff0c;对于办公族的工作和学生的学习都扮演着关键角色。长期使用质量不佳的台灯可能会对我们的视力健康造成不利影响&#xff0c;尤其是对于眼睛尚在发育阶段的青少年来说&#xff0c;这种影响…

5G技术相关部分图解

1、面向5G商用网络的全系列解决方案 面向5G商用网络的全系列解决方案涵盖了从核心网到接入网的各个方面&#xff0c;确保网络的高性能、高可靠性和高安全性 2、2\3\4\5G带宽图解 G带宽的提升将推动许多新型应用的发展&#xff0c;并提供更快速、更可靠的移动通信体验。然而…

一个panic问题引起对percpu变量的思考

1 问题引入 最近在分析一个panic问题时&#xff0c;发现panic现场无法与log对应起来。 先贴log: <1>[ 180.089084] Unable to handle kernel NULL pointer dereference at virtual address 00000001 <1>[ 180.099551] pgd 8bbde651 <1>[ 180.107775] …

AXI UART 16550 IP核简介

AXI UART 16550 IP核实现了PC16550D UART的硬件和软件功能&#xff0c;该UART可以在16450和16550 UART模式下工作。 一、 功能 AXI UART 16550 IP核执行从AXI主设备接收的字符的并行到串行转换&#xff0c;以及从调制解调器或串行外设接收的字符的串行到并行转换。它支持发送…

GPT3.5、GPT4、GPT4o的性能对比

理论总结 随着版本的升级,模型在参数数量、语言理解能力、生成文本质量、多模态能力、推理能力等方面均有显著提升。GPT-4.0作为最新改进版,提供了最先进的功能和性能。 实际对比 1.1.GPT3.5 1.2.GPT4 1.3.GPT4o 在语义理解上,无差别。 下面测试下代码能力。 测试问题 我…

uni-app:音频播放 uni.createInnerAudioContext()

uni.createInnerAudioContext() 创建并返回内部 audio 上下文 innerAudioContext 对象 简单实现音频播放&#xff1a; let innerAudioContext uni.createInnerAudioContext(); innerAudioContext.src ../../../../static/ok.MP3;//音频地址 innerAudioContext.play(); inn…

Pikachu 靶场 SQL 注入通关解析

前言 Pikachu靶场是一种常见的网络安全训练平台&#xff0c;用于模拟真实世界中的网络攻击和防御场景。它提供了一系列的实验室环境&#xff0c;供安全专业人士、学生和爱好者练习和测试他们的技能。 Pikachu靶场的目的是帮助用户了解和掌握网络攻击的原理和技术&#xff0c;…