利用springboot的aop实现行为日志管理

svbadmin学习日志

本学习日志是使用Springboot和Vue来搭建的后台管理系统:
演示地址:http://118.31.68.110:8081/index.html
账号:root
密码:123
所有代码可以在gitbub上找到,切换到相应分支即可。【代码传送门】

正篇

第一节 spring boot 模块化构建项目
第二节 整合mybatisplus完成用户增删改查
第三节 整合springsecurity实现基于RBAC的用户登录
第四节 springsecurity结合jwt实现前后端分离开发
第五节 使用ResponseBodyAdvice格式化接口输出
第六节 springboot结合redis实现缓存策略
第七节 springboot结合rabbitmq实现队列消息
第八节 springboot结合rabbitmq实现异步邮件发送
第九节 利用springboot的aop实现行为日志管理
第十节 利用Quartz实现数据库定时备份
第十一节 springboot配置log输出到本地文件
第十二节 使用flyway对数据库进行版本管理
第十三节 springboot配合VbenAdmin实现前端登录
第十四节 springboot配合VbenAdmin实现用户CURD
第十五节 基于RBAC的权限管理VbenAdmin前端实现
第十六节 springboot 打包vue代码实现前后端统一部署

番外

2.1 数据库设计原则
3.1 配置apifox自动获取登录的token
13.1 springboot 全局捕捉filter中的异常
14.1 springsecurity整合mybatisplus出现isEnable的问题和解决方案


前言

开发的时候我们习惯了sout来打印输出排查问题,而正式上线的时候我们则需要借助于日志来定位问题。从实战角度出发,有两类日志需要记录
1.行为日志,主要是记录对数据表的修改,或者一些特殊的操作,比如登录等
2.错误日志,一般就是错误反馈,这个比较容易理解了
当然对于物联网项目,我们也需要记录一些数据上传的日志,协助我们排查硬件是否工作正常。
那网上大部分的教程都用了springboot的AOP思想来解决这一问题。接下来我们也来实现下。


一、切面实现原理

切面可以直接指定到控制器里面的方法,当然这样不够灵活,我们更多会借用注解来实现。

1. 添加依赖

<!--aop相关的依赖引入-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.编写自定义注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SbvLog {
    String desc() default " ";
}

3. 编写切面类


/**
 * Notes: 日志切面类
 * Author: 涛声依旧 [email protected]
 * Time: 2022/8/1 22:00
 */
@Aspect
@Component
public class SbvLogAspect {

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

    @Autowired
    LogServiceImpl logService;
    // 被增强类中的被增强的方法
    @Pointcut(value = "@annotation(com.shenfangtao.utils.SbvLog)")
    public void logAnnotation(){
        System.out.println("pointCut签名");
    }

    // 前置通知
    @Before("logAnnotation()")
    public void doBefore(JoinPoint joinPoint) throws Throwable{
        System.out.println("doBefore");
        takeUpTime.set(System.currentTimeMillis());
    }

    // 返回通知
    @AfterReturning(returning = "ret", pointcut = "logAnnotation()")
    public void doAfterReturning(JoinPoint joinPoint, Object ret) throws Throwable{
        System.out.println("doAfterReturning");
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);

        Log log = Log.builder().build();
        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取切入点所在的方法
        Method method = signature.getMethod();
        // 获取请求的类名
        String className = joinPoint.getTarget().getClass().getName();
        // 获取操作描述
        SbvLog sbvLog = method.getAnnotation(SbvLog.class);
        if (Objects.nonNull(sbvLog)){
            log.setDescription(sbvLog.desc());
        }

        // 请求信息
        log.setMethod(className + "." + method.getName());
        log.setReqParam(JSONUtils.toJSONString(converMap(request.getParameterMap())));
        log.setUri(request.getRequestURI());
        log.setIp(IpUtil.getIpRequest(request));

        // 用户信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        BigInteger uid = (BigInteger) authentication.getDetails();
        log.setUid(uid);
        log.setUsername(authentication.getName());

        // 时间信息
        log.setCreatedAt(LocalDateTime.now());
        log.setUpdatedAt(LocalDateTime.now());
        log.setVersion("1.0");
        log.setTakeUpTime(System.currentTimeMillis() - takeUpTime.get());

        log.setLevel(Log.ACTION_LEVEL);

        logService.save(log);
    }

    // 异常通知
    @AfterThrowing(throwing = "ex", pointcut = "logAnnotation()")
    public void doAfterThrowing(JoinPoint joinPoint, Exception ex){
        System.out.println("doAfterThrowing");
    }

    // 后置通知
    @After("logAnnotation()")
    public void doAfter(JoinPoint joinPoint){
        System.out.println("doAfter");
    }


    /**
     * Notes:  转换request 请求参数
     * @param: [paramMap]
     * @return: java.util.Map
     * Author: 涛声依旧 [email protected]
     * Time: 2022/8/3 21:08
     **/
    public Map<String, String> converMap(Map<String, String[]> paramMap) {
        Map<String, String> rtnMap = new HashMap<String, String>();
        for (String key : paramMap.keySet()) {
            rtnMap.put(key, paramMap.get(key)[0]);
        }
        return rtnMap;
    }
}

二、行为日志设计

1.数据库设计

DROP TABLE IF EXISTS `log`;

CREATE TABLE `log`
(
    `id`            bigint UNSIGNED NOT NULL AUTO_INCREMENT,
    `uid`           bigint UNSIGNED NOT NULL COMMENT '操作用户id',
    `username`      varchar(255)  DEFAULT NULL COMMENT '用户名',
    `level`         tinyint(1) DEFAULT '1' COMMENT '日志等级:1为行为日志,2为错误日志',
    `description`          varchar(255)  NULL DEFAULT NULL COMMENT '操作描述',
    `req_param`     text  NULL COMMENT '请求参数',
    `take_up_time`  int(64) NULL DEFAULT NULL COMMENT '耗时',
    `method`        varchar(255)  NULL DEFAULT NULL COMMENT '操作方法',
    `uri`           varchar(255)  NULL DEFAULT NULL COMMENT '请求url',
    `ip`            varchar(50)  NULL DEFAULT NULL COMMENT '请求IP',
    `version`       varchar(50)  NULL DEFAULT NULL COMMENT '版本号',
    `created_at`    datetime NULL DEFAULT NULL,
    `updated_at`    datetime NULL DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

2. 编写log,logservice类等

@Data
@Builder
public class Log {
    public static final int ACTION_LEVEL = 1;
    public static final int ERROR_LEVEL = 2;

    @TableId(type = IdType.AUTO)
    private BigInteger id;
    private BigInteger uid;
    private String username;
    private Integer level;
    private String description;
    private String reqParam;
    private Long takeUpTime;
    private String method;
    private String uri;
    private String ip;
    private String version;
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createdAt;
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updatedAt;
}

测试

在需要加入日志的操作上面,使用@SbvLog日志

    @GetMapping("")
    @SbvLog(desc = "users")
    public List<User> getUsers(){
        List<User> data = userService.getUsersWithRoles();
        return data;
    }

调用users接口,然后查看数据库中log表的记录

在这里插入图片描述


总结

  1. 利用AOP可以很好的实现日志记录
  2. 注意利用spring security 的detail属性,记录用户信息

问题

  1. Error attempting to get column ‘xx‘ from result set. Cause: java.sql.SQLFeatureNotSupportedException

代码地址

代码


参考文档:
SpringBoot中的AOP使用

SpringBoot项目如何优雅的实现操作日志记录
SpringBoot AOP 记录操作日志、异常日志
史上最全ThreadLocal 详解(一)

你可能感兴趣的:(svbadmin,spring,boot,java,java-rabbitmq)