• 优雅的实现符合开闭原则的流水日志抽取demo


    如何做出一个标准化记录流水日志(Demo)

    昨天晚上 在b站刷到了 极海Channel 海哥的视频 也想去跟着实现一个,作为学习的demo,主要学习思路

    可能存在的问题:

    1. 可能每一个需要收集的类里取参数的字段可能是不一样的如何去处理
    2. 如何可以让他更好的作用于新的业务代码上

    项目代码日志

    问题实现

    以订单为例子 :

    • SaveOrder
    • UpdateOrder

    两个对象的字段名字和类型都可能不同

    SaveOrder

    public class SaveOrder {
        private Long id;
    }
    
    • 1
    • 2
    • 3

    UpdateOrder

    public class UpdateOrder {
        private Long orderId;
    }
    
    • 1
    • 2
    • 3

    定义日志流水数据对象

    OperateLogDo

    public class OperateLogDo {
        private Long orderId;
        private String desc;
        private String result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    配置需要生成注释的类型转换接口

    接口

    这个接口主要是用来对应的不同的类需要生成不同的日志,我们需要拿到他的唯一id 对象就可以操作数据了

    public interface Convert<PARAM> {
        OperateLogDo convert(PARAM param);
    }
    
    
    • 1
    • 2
    • 3
    • 4

    实现类

    public class SaveOrderConvert implements Convert<SaveOrder> {
        @Override
        public OperateLogDo convert(SaveOrder saveOrder) {
            OperateLogDo operateLogDo = new OperateLogDo();
            operateLogDo.setOrderId(saveOrder.getId());
            return operateLogDo;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    public class UpdateOrderConvert implements Convert<UpdateOrder> {
        @Override
        public OperateLogDo convert(UpdateOrder updateOrder) {
            OperateLogDo operateLogDo = new OperateLogDo();
            operateLogDo.setOrderId(updateOrder.getOrderId());
            return operateLogDo;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    定义注解

    RecordOperate

    1、 desc 是用来放日志类型的描述

    2、 convert 用来放日志类型的转变类

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    @Documented
    public @interface  RecordOperate {
        //日志描述
        String desc() default "";
        Class<? extends Convert> convert();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    定义切面

    OperateAspect

    @Component
    @Aspect
    public class OperateAspect {
        //根据需求来定义需要多少线程 编写测试demo需求并不高
        private ThreadPoolExecutor executor = new ThreadPoolExecutor(1,1,1, TimeUnit.SECONDS,new LinkedBlockingDeque<>(100));
    
        @Around("@annotation(com.hyc.annotation.RecordOperate)")
        public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            Object result  = proceedingJoinPoint.proceed();
            executor.execute(()->{
                try {
                    MethodSignature signature = (MethodSignature)proceedingJoinPoint.getSignature();
                    RecordOperate recordOperate = signature.getMethod().getAnnotation(RecordOperate.class);
                    Class<? extends Convert> convert = recordOperate.convert();
                    Convert LogConvert = convert.newInstance();
                    OperateLogDo operateLogDo = LogConvert.convert(proceedingJoinPoint.getArgs()[0]);
                    operateLogDo.setDesc(recordOperate.desc());
                    operateLogDo.setResult(result.toString());
                    System.out.println("insert opreateLog"+ JSON.toJSONString(operateLogDo));
                } catch (InstantiationException e) {
                    throw new RuntimeException(e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            });
            return result;
        }
    }
    
    
    • 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

    查看结果

    启动测试

    @SpringBootApplication
    public class AopPublicLogApplication implements CommandLineRunner  {
    
        @Autowired
        OrderService orderService;
    
        public static void main(String[] args) {
            SpringApplication.run(AopPublicLogApplication.class, args);
        }
    
        @Override
        public void run(String... args) throws Exception {
            SaveOrder saveOrder = new SaveOrder();
            saveOrder.setId(1L);
            orderService.saveOrder(saveOrder);
            UpdateOrder updateOrder = new UpdateOrder();
            updateOrder.setOrderId(2L);
            orderService.updateOrder(updateOrder);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    image-20221119004408533

    ​ 可以看到 我们确实拿到了不同类不同字段的id 而且跟着海哥的思路 我们编写的这个demo符合开闭原则

    在不改动源代码的情况下拓展功能。很简单的一个小demo但是可以拓展我们平时开发中的思路 多多的思考尽可能的优雅的实现业务和一些需要公共抽取的方式

  • 相关阅读:
    【Pindex】我用vue做了个“假终端”
    多维时序 | MATLAB实现TCN时间卷积神经网络多变量时间序列预测
    ubuntu安装golang
    【数据结构】前言概况 - 树
    WPF 控件专题 Ellipse详解
    提高工作效率,让你快速获得Hypermesh二次开发能力!
    Linux系统编程——文件的打开及创建
    adb删除系统应用
    Nginx中关于虚拟主机的一点冷门知识
    POJ 3470 Walls 树上分桶
  • 原文地址:https://blog.csdn.net/doomwatcher/article/details/127931954