java AOP环绕切面记录操作日志

一.创建数据库日志表

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;
    }
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Oracle到PostgreSQL的不停机数据库迁移

1970 年&#xff0c;数据库之父 Edgar Frank Codd 发表了“数据的关系模型”论文&#xff0c;该论文为往后的关系型数据库的发展奠定了基础。1979 年&#xff0c;基于关系模型理论的数据库产品 Oracle 2 首次亮相&#xff0c;并在过去的三四十年时间里&#xff0c;横扫全球数据…

Python起风了钢琴曲

写在前面 那年夏天&#xff0c;有《纸短情长》&#xff0c;有《稻香》&#xff0c;有《可不可以》&#xff0c;有《体面》&#xff0c;还有《起风了》……本期小编给大家分享Python弹奏的《起风了》钢琴曲&#xff0c;一起来看看吧&#xff01; 《起风了》 《起风了》是一首深…

解决Android Studio Gradle下载慢的问题

安卓 gradle-7.5-bin.zip 下载慢 https://mirrors.cloud.tencent.com/gradle/7.x.x 找到对应匹配版本 把下载的文件直接复制到 C:\Users\Administrator.gradle\wrapper\dists\gradle-x.x\ 中对应版本目录下&#xff0c;例如需要下载 gradle-2.14.1-all.zip&#xff0c;则下载好…

Linux —— 线程控制

Linux —— 线程控制 创建多个线程线程的优缺点优点缺点 pthread_self进程和线程的关系pthread_exit 线程等待pthread_ join线程的返回值线程分离pthread_detach 线程取消pthread_cancel pthread_t 的理解 我们今天接着来学习线程&#xff1a; 创建多个线程 我们可以结合以前…

【Linux】认识文件(四):文件系统,inode,以及软硬连接

【Linux】认识文件&#xff08;四&#xff09;&#xff1a;文件系统,inode,以及软硬连接 一.磁盘(仅了解)1.组成2.CHS寻址 二.抽象化磁盘(仅了解)三.文件系统1.什么是文件系统2.ext2文件系统的结构i.Date blocksii.Block Bitmapiii.inode1.inode Table2.inode Bitmap iiiii.GDT…

使用 Python 进行图像验证码识别训练及调用

目录 1、验证码识别原理1.1 Tensorflow 介绍1.2 Tensorflow 运行原理1.3 卷积神经网络 CNN&#xff08;Convolutional Neural Networks&#xff09; 2、验证码识别实现步骤2.1 安装第三方模块2.1.1 安装 TensorFlow 模块2.2.2 安装 cuda2.2.3 下载 cudnn 2.2 读取验证码样本形成…

[源码安装]

1 pangolin安装 在Linux上常用的一个3D绘图库是Pangolin&#xff0c;它是基于OpenGL完成的&#xff0c;它不但支持OpenGL的基本操作&#xff0c;还提供了一些GUI的功能。 1.1 版本&#xff1a; pangolin —— v0.6 libpng —— 16 eigen —— 3.4 使用libpng12遇到下面的问…

网络编程套接字(一) 【简单的Udp网络程序】

网络编程套接字<一> 理解源端口号和目的端口号PORT VS PID认识TCP协议和UDP协议网络字节序socket编程接口sockaddr结构简单的UDP网络程序服务端创建套接字服务端绑定运行服务器客户端创建套接字关于客户端的绑定问题启动客户端启动客户端本地测试INADDR_ANY 理解源端口号…

【技术分享】 OPC UA安全策略证书简述

那什么是OPC UA证书&#xff1f;用途是什么&#xff1f; 简单来说它是身份验证和权限识别。 OPC UA使用X.509证书标准&#xff0c;该标准定义了标准的公钥格式。建立UA会话的时候&#xff0c;客户端和服务器应用程序会协商一个安全通信通道。数字证书&#xff08;X.509&#x…

图片压缩工具,这三款软件简单好用!

在数字化时代&#xff0c;图片已成为我们生活和工作中不可或缺的一部分。无论是社交媒体上的分享&#xff0c;还是工作中的文件传输&#xff0c;图片都扮演着重要的角色。然而&#xff0c;随着图片质量的提高&#xff0c;其占用的存储空间也越来越大&#xff0c;这给我们的存储…

英语词汇-Obsoleted

英语词汇&#xff0d;Obsoleted Obsoleted 废弃的&#xff0c;不用的&#xff0c;过时的。 Automation has obsoleted many unskilled workers. 自动化技术&#xff0c;已淘汰了很多低技能工人。 微信公众号&#xff1a; 常青柏 淘宝店铺名&#xff1a; 漫乐之家、梅兰竹菊…

企业管理咨询公司不会选?一文带你避开“坑人”陷阱

近年来&#xff0c;企业管理咨询公司如雨后春笋般涌现&#xff0c;数量之多令人眼花缭乱。所以&#xff0c;面对这么多的企业管理咨询公司&#xff0c;企业该选谁&#xff1f;又该如何选择&#xff1f;本文将从以下几个方面为大家解析。 首先&#xff0c;我们要明确自己的需求和…

代码随想录 打卡day23,24,25

1 二叉搜索树的最小绝对差 注意审题&#xff0c;题目当值说到是一个二叉搜索树&#xff0c;因此我们只需进行中序遍历即可&#xff0c;然后得到一个有序数组之后进行编辑&#xff0c;统计出来最小差。 class solution{ private:vector<int> vec;void traversal(TreeNode…

namenode启动失败 org.apache.hadoop.hdfs.server.common.InconsistentFSStateException:

小白的Hadoop学习笔记 2024/5/14 18:26 文章目录 问题解决报错浅浅分析一下core-ste.xml 问题 namenode启动失败 读日志 安装目录下 vim /usr/local/hadoop/logs/hadoop-tangseng-namenode-hadoop102.log2024-05-14 00:22:46,262 ERROR org.apache.hadoop.hdfs.server.namen…

位图(c++)

文章目录 1.位图概念2.位图的实现3.应用&#xff08;解决整形存在或次数问题&#xff09;3.1存在问题3.2次数问题 5.搜索的方法对比&#xff1a; 1.位图概念 和哈希一样&#xff0c;都是一个表来记录某个元素的个数或者存在与否&#xff1b;不同的是哈希使用的计算机定义的完整…

Qt5 互动地图,实现无人机地面站效果

一、概述 本文主要通过Qt5opmapcontrol实现一个简单的无人机地面站效果。opmapcontrol是一个比较古老的QT开源地面站库&#xff0c;可选择谷歌地图&#xff0c;必应地图&#xff0c; 雅虎地图&#xff0c;GIS等。可直接使用源码&#xff0c;也可以编译生成库进行调用。实现效果…

技艺高超的魔法师:Java运算符

在Java编程的世界里&#xff0c;运算符是连接变量和表达式的关键纽带&#xff0c;它们使得程序能够执行计算、比较、赋值等一系列操作。 一&#xff0c;基本概念 1&#xff0c;运算符是什么&#xff1f; 运算符是操作变量的符号。 2&#xff0c;分类 Java中的主要运算符类…

openlayer实现ImageStatic扩展支持平铺Wrapx

地图平铺&#xff08;Tiling&#xff09;是地图服务中常见的技术&#xff0c;用于将大尺寸的地图数据分割成许多小块&#xff08;瓦片&#xff09;&#xff0c;便于高效加载和展示。这种技术特别适用于网络环境&#xff0c;因为它允许浏览器只加载当前视图窗口内所需的地图瓦片…

摸鱼大数据——Linux搭建大数据环境(集群免密码登录和安装Hadoop)二

集群设置免密登录 克隆node1虚拟机的前置条件&#xff1a;node1虚拟机存在且处于关闭状态 1.克隆出node2虚拟机 1.node1虚拟机: 右键 -> "管理" -> "克隆" 2.图形化弹窗中: "下一页"->"下一页"->选择"创建完整克隆&…

蓝鹏测控:扩大出口,勇拓海外市场

蓝鹏测控自2012年成立以来&#xff0c;始终专注于工业测量仪器的研发、生产与销售。公司坚持经验与创新并存&#xff0c;长期与华北电力大学、河北大学等多所知名院校深度合作&#xff0c;拥有一支技术力量雄厚的研发团队。经过多年的努力&#xff0c;蓝鹏测控已研发出多款具有…