基于springboot1.x AOP的操作日志

基于springboot1.x AOP的操作日志

[TOC]

前言

由于一些历史原因,为了方便运营部门登录后台系统,大家使用了一个权限非常大的账号。近期发现很多操作失误,找不到凶手的情况。 
所以做两个事:
  • 修改原有账号的密码,一人一号操作
  • 编写操作日志记录的功能,让关键增删改操作,能找到人。好抓凶手,故引出本文。

正文

一、整体思路

  1. 基于AOP切面编程
  2. 切点第一类:拦截指定包及子包下的业务代码,对以特殊开头方法,进行拦截
  3. 切点第二类:通过自定义注解,对特殊开头方法进行拦截
  4. 拦截后将操作人,入库进行持久化
  5. 操作日志数据的处理

(1)存储媒介暂时是mysql

(2)定期数据移入历史库或是删除,控制数据库文件大小

(3)为了方便查询,后期考虑ES

二、数据库及基础服务

增删改查的基础服务,此处不贴代码凑字
数据库表如下:

CREATE TABLE `action_log` (
  `id` bigint(32) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(50) DEFAULT NULL COMMENT 'open_id',
  `user_name` varchar(100) DEFAULT NULL COMMENT '用户名',
  `operation` varchar(100) DEFAULT NULL COMMENT '操作,注解value',
  `method_name` text COMMENT '方法名',
  `params` text COMMENT '参数json',
  `ip` varchar(100) DEFAULT NULL COMMENT 'ip地址',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1148538203664441345 DEFAULT CHARSET=utf8

三、AOP切面实现

0.maven依赖

<!-- springboot1.x 的aop依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
 <!-- web程序的启动项依赖,通过此依赖可引入内嵌的tomcat等web必须的jars -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

1.文件路径过滤式

  • 定义切点: com.fatsnake.projects包及其子包下的***的方法
    /**
     * 添加业务逻辑方法切入点
     */
    @Pointcut("execution(* com.fatsnake.projects..*.add*(..)) "
        + "|| execution(* com.fatsnake.projects..*.create*(..)) "
        + "|| execution(* com.fatsnake.projects..*.insert*(..))")
    public void insertCell() {
    }

    /**
     * 删除业务逻辑方法切入点
     */
    @Pointcut("execution(* com.fatsnake.projects..*.delete*(..)) "
        + "execution(* com.fatsnake.projects..*.remove*(..))")
    public void deleteCell() {
    }

    /**
     * 修改业务逻辑方法切入点
     */
    @Pointcut("execution(* com.fatsnake.projects..*.update*(..))")
    public void updateCell() {
    }

2.自定义注解式

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

/**
 * 自定义注解类
 */
@Target( {ElementType.TYPE, ElementType.METHOD}) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented //生成文档
public @interface FatsnakeActionLog {
    /**
     * 默认值
     *
     * @return String
     */
    String value() default "";
}
  • 定义注解式切点
/**
     * 在注解FatsnakeActionLog的位置切入代码
     * 定义切点 @Pointcut
     */
    @Pointcut("@annotation( com.fatsnake.projects.springboot.common.annotation.FatsnakeActionLog)")
    public void fatsnakeActionLogPoinCut() {
    }

3.AOP方法

/**
     * 在切入点(业务方法)方法执行之前执行
     *
     * @param joinPoint joinPoint
     */
    @Before(" fatsnakeActionLogPoinCut() || insertCell() || deleteCell()|| updateCell() ")
    public void logBeforeController(JoinPoint joinPoint) {

        // 记录日志不能影响原有业务代码,所以所以异常均catch捕获,LOGGER.error打印出,ELK服务收集
        try {

            // 登录判断
            // 全局缓存服务中获取当前登录的用户
            User user = userService.getLogonUser(); 
            // 操作人, 用户不能录,记录没有意义
            if (user != null) {
                FatsnakeActionLog fatsnakeActionLog = new FatsnakeActionLog();

                fatsnakeActionLog.setOpenId(user.getOpenId());
                fatsnakeActionLog.setUserName(user.getUserName());

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

                //获取请求的类名
                String className = method.getDeclaringClass().getName();

                //获取请求的方法名
                String methodName = method.getName();
                fatsnakeActionLog.setMethodName(className + "." + methodName);

                //获取注解操作的value
                com.fatsnake.projects.springboot.common.annotation.FatsnakeActionLog logAnnotation
                    = method.getAnnotation(com.fatsnake.projects.springboot.common.annotation.FatsnakeActionLog.class);
                if (logAnnotation != null) {
                    String value = logAnnotation.value();
                    fatsnakeActionLog.setOperation(value); // 保存获取的操作
                }

                //请求的参数
                Object[] args = joinPoint.getArgs();
                //将参数所在的数组转换成json
                String params = JSON.toJSONString(args);
                fatsnakeActionLog.setParams(params);

                // 操作时间
                fatsnakeActionLog.setCreateTime(new Date());

                // 获取用户ip地址
                // 这个RequestContextHolder是Springmvc提供来获得请求的东西
                RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
                if (requestAttributes != null) {
                    HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
                    fatsnakeActionLog.setIp(request.getRemoteAddr());
                }
                // 持久化日志
                // 此处持久化业务方法名,不能满足切点,否则将被拦截,进入死循环,导致jvm堆内存溢出
                fatsnakeActionLogService.produceLog(fatsnakeActionLog);
            }
        } catch (Exception e) {
            LOGGER.error(e.getMessage());
        }
    }

四、使用与测试

  • 以上aop相关代码,以sdk的形式,通过maven引入项目中
  • 在application.properties文件里加这样一条配置
    spring.aop.auto=true // 此配置,在功能中加与不加,功能都能正常运行
  • 注解式使用如下:
 /**
     * 套餐分页
     *
     * @param saleActivity saleActivity
     * @param pageNo       pageNo
     * @param pageSize     pageSize
     * @return ResultData
     */
    @PostMapping("/getSmartData")
    @FatsnakeActionLog(value = "优惠活动查询")
    public ResultData getList(@RequestBody SaleActivity saleActivity,
                              @NotBlank @RequestParam("pageNo") String pageNo,
                              @NotBlank @RequestParam("pageSize") String pageSize) {

        Paging page = new Paging();
        page.setPageNo(Integer.parseInt(pageNo));
        page.setPageSize(Integer.parseInt(pageSize));
        return renderSuccess(prepareModel(saleActivity, page));
    }

五、扩展阅读

在SpringBoot中用SpringAOP实现日志记录功能

springboot—spring aop 实现系统操作日志记录存储到数据库

Spring boot学习(六)Spring boot实现AOP记录操作日志

在aop中execution的写法规则

spring aop expression 匹配多个条件 多个表达式

结语

功能以上正式站,目前仅是用在公司的业务管理系统中。还是有很多问题,有待以后优化。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容