• bizlog通用操作日志组件(使用篇)


    引言

    在这里插入图片描述
    如上图所示,产品的新需求,需要将操作人在系统中具体编辑操作的变更内容记录下来。

    按正常思路来说,无非就是将修改前后的对象字段逐个比较,再拼接为详细的操作描述记录到操作日志表中。如果是一个模块的需求,单独写个方法即可,但各个模块都有这部分需求,在各个模块中单独写就显得冗余了,功能代码与日志记录代码混在一起,代码可读性更差了。

    因此,项目中引入了新的日志组件,统一实现团队项目中各模块记录操作日志的需求,将日志记录代码与功能代码分离,提升代码的可读性。

    概述

    在一个系统中,日志主要分为系统日志和操作日志。

    系统日志主要是为开发排查问题提供依据。操作日志主要是对某条数据进行新增或者修改操作后进行记录,操作日志要求可读性强,因为它是给用户看的,比如订单的物流信息,用户需求知道在什么时候发生了什么事情。

    本篇博客介绍的组件解决的就是该问题:「谁」在「什么时间」对「什么」做了「什么事」

    快速开始

    1. Maven依赖

    <dependency>
       <groupId>io.github.mouzt</groupId>
       <artifactId>bizlog-sdk</artifactId>
       <version>3.0.3<version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2. SpringBoot入口启用日志组件,添加@EnableLogRecord注解

    tenant是代表租户的标识,一般一个服务或者一个业务下的多个服务都写死一个 tenant 就可以。示例如下:

    @SpringBootApplication
    @EnableTransactionManagement
    @EnableLogRecord(tenant = "com.mzt.test")
    public class Main {
    
        public static void main(String[] args) {
            SpringApplication.run(Main.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3. 普通日志记录

    下面给一个示例,在新增方法上加上@LogRecord注解,补上对应的参数,代码如下:

    @LogRecord(
    		operator = "{{#currentUser}}",
            fail = "创建订单失败,失败原因:「{{#_errorMsg}}」",
            subType = "MANAGER_VIEW",
            extra = "{{#order.toString()}}",
            success = "{{#order.purchaseName}}下了一个订单,购买商品「{{#order.productName}}」,测试变量 {{#innerOrder.productName}}",
            type = LogRecordType.ORDER, bizNo = "{{#order.orderNo}}")
    public boolean createOrder(Order order) {
        log.info("【创建订单】orderNo={}", order.getOrderNo());
        // db insert order
        Order order1 = new Order();
        order1.setProductName("内部变量测试");
        LogRecordContext.putVariable("innerOrder", order1);
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    参数说明:

    • SpEL 表达式:其中用双大括号包围起来的(例如:{{#order.purchaseName}})#order.purchaseName 是 SpEL表达式。Spring中支持的它都支持的。比如调用静态方法,三目表达式
    • type:是拼接在 bizNo 上作为 log 的一个标识。避免 bizNo 都为整数 ID 的时候和其他的业务中的 ID 重复。比如订单 ID、用户 ID 等,type可以是订单或者用户
    • bizNo:就是业务的 ID,比如订单ID,我们查询的时候可以根据 bizNo 查询和它相关的操作日志
    • subType:日志子类型,主要是便于对日志做分类,实现不同角色的人看到不同的日志
    • operator:操作人
    • success:方法调用成功后把 success 记录在日志的内容中
    • extra:支持记录操作的详情或者额外信息
    • fail:如果抛出异常则记录fail的日志,其中的 #_errorMsg 是取的方法抛出异常后的异常的 errorMessage

    此时会打印操作日志 “张三下了一个订单,购买商品「XXX」,测试变量「内部变量测试」

    4. 保存日志于存储介质中

    实现ILogRecordService接口即可,在record方法中保存日志记录,查询日志可根据自身业务需求实现。

    @Service
    public class DbLogRecordServiceImpl implements ILogRecordService {
    
        @Resource
        private LogRecordMapper logRecordMapper;
    
        @Override
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void record(LogRecord logRecord) {
            log.info("【logRecord】log={}", logRecord);
            LogRecordPO logRecordPO = LogRecordPO.toPo(logRecord);
            logRecordMapper.insert(logRecordPO);
        }
    
        @Override
        public List<LogRecord> queryLog(String bizKey, Collection<String> types) {
            return Lists.newArrayList();
        }
    
        @Override
        public PageDO<LogRecord> queryLogByBizNo(String bizNo, Collection<String> types, PageRequestDO pageRequestDO) {
            return logRecordMapper.selectByBizNoAndCategory(bizNo, types, pageRequestDO);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    通过上面简单的几步就能实现记录新增操作的日志,不用在业务代码中写一些繁琐的日志代码。下面再简单介绍一些组件的其他使用特性和示例。

    其他使用

    1. 自定义函数

    在日志记录中,可能会存在字段值是ID,如订单号,而看到一堆数字并不知道对应的内容(订单名等)这日志记录的可读性很差。因此可以使用自定义函数,可在函数中实现通过ID查询到对应内容显示。

    使用上只需要实现框架里面的IParseFunction的接口,实现两个方法:

    1)functionName() 方法就返回注解上面的函数名;

    2)apply()函数参数是 "{ORDER{#orderId}}"中SpEL解析的#orderId的值,这里是一个数字1223110,接下来只需要在实现的类中把 ID 转换为可读懂的字符串就可以了, 一般为了方便排查问题需要把名称和ID都展示出来,例如:"订单名称(ID)"的形式。

    示例代码:

    //使用了自定义函数,主要是在 {{#orderId}} 的大括号中间加了 functionName
    @LogRecord(success = "更新了订单{ORDER{#orderId}},更新内容为...",
            type = LogRecordType.ORDER, bizNo = "{{#order.orderNo}}",
            extra = "{{#order.toString()}}")
    public boolean update(Long orderId, Order order) {
        return false;
    }
    
    // 还需要加上函数的实现
    @Slf4j
    @Component
    public class OrderParseFunction implements IParseFunction {
    
        @Override
        public boolean executeBefore() {
            return true;
        }
    
        @Override
        public String functionName() {
            return "ORDER";
        }
    
        @Override
        public String apply(Object value) {
            log.info("@@@@@@@@");
            if (StringUtils.isEmpty(value)) {
                return "";
            }
            log.info("###########,{}", value);
            Order order = new Order();
            order.setProductName("xxxx");
            return order.getProductName().concat("(").concat(value.toString()).concat(")");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    2. 使用方法参数之外的变量

    可以在方法中通过 LogRecordContext.putVariable(variableName, Object) 的方法添加变量,第一个对象为变量名称,后面为变量的对象

    3. diff特性

    用于快速比较并记录两个对象之间不同的字段内容。

    1)使用@DiffLogField标注字段含义

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Order {
        @DiffLogField(name = "订单ID", function = "ORDER")
        private Long orderId;
        @DiffLogField(name = "订单号")
        private String orderNo;
        private String purchaseName;
        private String productName;
        @DiffLogField(name = "创建时间")
        private LocalDateTime createTime;
    
        @DiffLogField(name = "创建人")
        private UserDO creator;
        @DiffLogField(name = "更新人")
        private UserDO updater;
        @DiffLogField(name = "列表项", function = "ORDER")
        private List<String> items;
    
        @DiffLogField(name = "拓展信息", function = "extInfo")
        private String[] extInfo;
    
        @Data
        public static class UserDO {
            @DiffLogField(name = "用户ID")
            private Long userId;
            @DiffLogField(name = "用户姓名")
            private String userName;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    name:是生成的 DIFF 文案中 Field 的中文, function: 自定义函数,例如可以把用户ID映射成用户姓名。

    2)在更新方法上,使用日志注解

    @LogRecord(success = "更新订单。{_DIFF{#oldOrder, #newOrder}}",
            type = LogRecordType.ORDER, bizNo = "{{#newOrder.orderNo}}",
            extra = "{{#newOrder.toString()}}")
    public boolean diff(Order oldOrder, Order newOrder) {
        //....
        return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4. 日志记录与业务逻辑一起回滚

    默认日志记录错误不影响业务的流程,若希望日志记录过程如果出现异常,让业务逻辑也一起回滚,在 @EnableLogRecord 中 joinTransaction 属性设置为 true, 另外 @EnableTransactionManagement order 属性设置为0 (让事务的优先级在@EnableLogRecord之前)

    @EnableLogRecord(tenant = "com.mzt.test", joinTransaction = true)
    @EnableTransactionManagement(order = 0)
    public class Main {
    
        public static void main(String[] args) {
            SpringApplication.run(Main.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    总结

    通过本篇博客对bizlog的简单介绍与使用教程,相信你一定感受到了该组件的魅力所在。如果你的项目中正好有同样的需求,而你还是在业务逻辑代码中实现,不妨试试该日志框架,简单好用。

  • 相关阅读:
    【vue+nestjs】gitee第三方授权登录【超详细】
    利用go制作微信机器人
    一文带你深入浅出 MongoEngine 经典查询【内附详细案例】
    看表情包学Linux:基本指令介绍
    解锁Spring Boot中的设计模式—05.策略模式:探索【策略模式】的奥秘与应用实践!
    【docker部署nacos并实现数据持久化《二》】
    51单片机循迹小车工作原理与程序设计思路
    Go的sync.Mutex互斥锁
    java 版本企业招标投标管理系统源码+功能描述+tbms+及时准确+全程电子化
    【源码课件+教程】看动画,学Python_Python精品课程_Python精选教程_Python入门_Python基础_零基础到精通,只需这一套课程
  • 原文地址:https://blog.csdn.net/u013034223/article/details/127881028