复杂业务场景下,如何优雅的使用设计模式来优化代码?

1、引言

本文以一个实际案例来介绍在解决业务需求的路上,如何通过常用的设计模式来逐级优化我们的代码,以把我们所了解的到设计模式真实的应用于实战。

2、背景

假定我们现在有一个订单流程管理系统,这个系统对于用户发起的一笔订单,需要你编写代码按照以下环节进行依次处理

图片

注:本文不会对每个环节的实现细节进行描述,读者也不必了解这每个环节的实现,我们只需要关注代码架构设计

3、第一次迭代

按照背景,我们如果不是打算if-else一撸到底的话,我们最合适使用的设计模式应该是责任链模式,于是我们先打算用责任链模式来做我们的第一次迭代。

图片

先整体看下类图:

图片

我们定义一个抽象类,抽象类中定义了下一个处理器,方便后期我们读取配置直接构建责任链处理顺序:

@Data
public abstract class BizOrderHandler {

    /**
     * 下一个处理器
     */
    private BizOrderHandler nextBizOrderHandler;

    /**
     * 处理器执行方法
     * @param param 责任链参数
     * @return 执行结果
     */
    public abstract Result handle(ProductVO param);


    /**
     * 链路传递
     * @param param
     * @return
     */
    protected Result next(ProductVO param) {
        //下一个链路没有处理器了,直接返回
        if (Objects.isNull(nextBizOrderHandler)) {
            return Result.success();
        }
        //执行下一个处理器
        return nextBizOrderHandler.handle(param);
    }

}

然后我们将需要实现的流程都来实现这个接口 (为了简单只列举一个)

public class StorageCheckBizOrderHandler extends BizOrderHandler {
    @Override
    public Result handle(ProductVO param) {
        // 这里写上仓储管理的业务逻辑
        System.out.println("StorageCheckBizOrderHandler doing business!");
        return super.next(param);
    }
}

通过调用父类的next方法实现了链式传递,接下来我们就可以使用责任链来实现业务了

public class OrderHandleCases {

    static Map<String, BizOrderHandler> handlerMap = new HashMap<>();

    static {
        handlerMap.put("Storage", new StorageCheckBizOrderHandler());
        handlerMap.put("Payment", new PaymentBizOrderHandler());
        handlerMap.put("RightCenter", new RightCenterBizOrderHandler());
        handlerMap.put("PointDeduction", new PointDeductBizOrderHandler());
    }

    public static void main(String[] args) { 
        // 这里可以从nacos配置中心读取责任链配置
        BizOrderHandler handler1 = initHandler(Lists.newArrayList("Storage", "RightCenter", "PointDeduction","Payment"));
        // 虚拟化一个产品订单
        ProductVO productVO = ProductVO.builder().build();
        Result result = handler1.handle(productVO);
        System.out.println("订单处理 成功");
    }


    /**
     * 根据责任链配置构建责任链
     * @param handlerNameChain 责任链执行顺序
     * @return 首个处理器
     */
    private static BizOrderHandler initHandler(List<String> handlerNameChain) {
        List<BizOrderHandler> handlers = new ArrayList<>();
        for (int i = 0; i < handlerNameChain.size(); i++) {
            String cur = handlerNameChain.get(i);
            String next = i + 1 < handlerNameChain.size() ? handlerNameChain.get((i + 1)) : null;
            BizOrderHandler handler = handlerMap.get(cur);
            if (next != null) {
                handler.setNextBizOrderHandler(handlerMap.get(next));
            }
            handlers.add(handler);
        }
        return handlers.get(0);
    }

}

上面的代码中通过initHandler这个方法来组建整个责任链条,还能实现动态配置,比如后期需要撤掉积分模块的商品处理,改个配置就行,感觉责任链完美搞定了这个问题,第一版就这样开心上线。

图片

4、第二次迭代

产品又来了,提了一个新的需求

  • 产品说,我们需要支持多租户,每种租户的订单流程都是不一样的

  • 租户A:仓储检查->权益扣减->积分扣减->剩余金额支付

  • 租户B:仓储检查->积分扣减->权益扣减

也就是说现在流程变成这样:

图片

来了多租户,这有何难,再加一条责任链配置不就好了,直接写代码如下:

public class OrderHandleCases {

    static Map<String, BizOrderHandler> handlerMap = new HashMap<>();

    static {
        handlerMap.put("Storage", new StorageCheckBizOrderHandler());
        handlerMap.put("Payment", new PaymentBizOrderHandler());
        handlerMap.put("RightCenter", new RightCenterBizOrderHandler());
        handlerMap.put("PointDeduction", new PointDeductBizOrderHandler());
    }

    public static void main(String[] args) {
        // 租户A的责任链
        BizOrderHandler handler1 = initHandler(Lists.newArrayList("Storage", "RightCenter", "PointDeduction", "Payment"));
        ProductVO productVO = ProductVO.builder().build();
        Result result1 = handler1.handle(productVO);
       // 租户B的责任链
        BizOrderHandler handler2 = initHandler(Lists.newArrayList("Storage", "PointDeduction", "RightCenter"));
        Result result2 = handler2.handle(productVO);
        System.out.println("订单处理 成功");

    }

    /**
     * 根据责任链配置构建责任链
     * @param handlerNameChain 责任链执行顺序
     * @return 首个处理器
     */
    private static BizOrderHandler initHandler(List<String> handlerNameChain) {
        List<BizOrderHandler> handlers = new ArrayList<>();
        for (int i = 0; i < handlerNameChain.size(); i++) {
            String cur = handlerNameChain.get(i);
            String next = i + 1 < handlerNameChain.size() ? handlerNameChain.get((i + 1)) : null;
            BizOrderHandler handler = handlerMap.get(cur);
            if (next != null) {
                handler.setNextBizOrderHandler(handlerMap.get(next));
            }
            handlers.add(handler);
        }
        return handlers.get(0);
    }

}

上面的代码相比之前的就是多加了一条新的租户B的责任链配置。感觉这个功能不就实现了嘛 结果一运行:堆栈溢出!

图片

咋回事 怎么堆栈溢出了,咱们仔细看一下 发现咱们的Map里面存放的实例全部是单例,搞出来了环形链表了....

图片

上图中黑色箭头代表第一个租户的流程,绿色箭头代表第二个租户,第二个租户的流程在执行到权益扣减环节,后面由于第一个租户配置的下一个环节是积分扣减,于是在这里形成了环。

看来单例不行,咱们得搞多例 既然需要多次构建对象,于是咱们搬出来下一个设计模式“简单工厂模式”:

public class BizOrderHandlerFactory {

    public static BizOrderHandler buildBizOrderHandler(String bizType) {
        switch (bizType) {
            case "Storage":
                return new StorageCheckBizOrderHandler();
            case "Payment":
                return new PaymentBizOrderHandler();
            case "RightCenter":
                return new RightCenterBizOrderHandler();
            case "PointDeduction":
                return new PointDeductBizOrderHandler();
            default:
                return null;
        }
    }

}

然后我们改写initHandler方法,不再从map中取实例,转为从工厂方法里面获取实例:

private static BizOrderHandler initHandlerPro(List<String> handlerNameChain) {
    List<BizOrderHandler> handlers = new ArrayList<>();
    for (String s : handlerNameChain) {
        BizOrderHandler handler = BizOrderHandlerFactory.buildBizOrderHandler(s);
        handlers.add(handler);
    }
    for (int i = 0; i < handlers.size(); i++) {
        BizOrderHandler handler = handlers.get(i);
        BizOrderHandler nextHandler = i + 1 < handlerNameChain.size() ? handlers.get(i + 1) : null;
        handler.setNextBizOrderHandler(nextHandler);
    }
    return handlers.get(0);
}

public static void main(String[] args) {
    BizOrderHandler handler1 = initHandlerPro(Lists.newArrayList("Storage", "Payment", "RightCenter", "PointDeduction"));
    BizOrderHandler handler2 = initHandlerPro(Lists.newArrayList("Storage", "RightCenter", "PointDeduction"));
    ProductVO productVO = ProductVO.builder().build();
    Result result = handler1.handle(productVO);
    System.out.println("订单处理 成功--租户1");
    result = handler2.handle(productVO);
    System.out.println("订单处理 成功--租户2");
}

执行代码:

图片

好了问题完美解决,现在多租户也支持了。上线搞定这次需求

5、第三次迭代

产品又又来了,提了一个新的需求

产品说,我们需要支持条件判断,租户A要求,权益扣减和积分扣减必须全部成功完成一个就可以进入支付环节,不必都要把权益扣减和积分扣减流程走一遍

分析一下这个需求权益扣减和积分扣减都要完成才可以进入支付环节 当然最简单的改法是在权益和积分环节做个判断,要是失败了就跳出责任链,但是假如产品经理下次又说权益扣减和积分扣减完成一个就能进入支付,我们还得修改这个权益和积分实现里面的判断,频繁修改实现可并不是好事。

那咱们可以考虑代理模式,熟悉网关的都知道网关其实就是一个大代理,咱们按照这种思想可以搞一个网关代理权益扣减和积分扣减环节。于是咱们搞出来一个“网关”组件

@Data
public class BizOrderHandlerUnionGateway extends BizOrderHandler {
    List<BizOrderHandler> proxyHandlers;

    @Override
    public Result handle(ProductVO param) {
        boolean isAllSuccess = true;
        if (proxyHandlers != null) {
            for (BizOrderHandler handler : proxyHandlers) {
                Result result = handler.handle(param);
                if (result.isSuccess()) {
                    // 一个代理执行器 执行成功 则继续执行
                    continue;
                } else {
                    isAllSuccess = false;
                    break;
                }
            }
        }
        if (isAllSuccess) {
            return super.next(param);
        }else{
            throw new RuntimeException("execute Failed");
        }
    }
}

上面的网关叫做union网关也就是并集网关,也就是说代理的处理器全部都执行成功才继续传递责任链,需要注意的是这个类也是BizOrderHandler的一个实现,只不过它的内部没有逻辑,只是对proxyHandlers中的组件进行代理。

然后简单修改下工厂 加一个分支:

public static BizOrderHandler buildBizOrderHandler(String bizType) {
    switch (bizType) {
        case "Storage":
            return new StorageCheckBizOrderHandler();
        case "Payment":
            return new PaymentBizOrderHandler();
        case "RightCenter":
            return new RightCenterBizOrderHandler();
        case "PointDeduction":
            return new PointDeductBizOrderHandler();
        case "UnionGateway":
            return new BizOrderHandlerUnionGateway();
        default:
            return null;
    }
}

然后我们用下面的方法获取首个执行节点,就可以执行整个责任链了:

private static BizOrderHandler initHandlerWithGateway() {
    BizOrderHandler storage = BizOrderHandlerFactory.buildBizOrderHandler("Storage");
    BizOrderHandler payment = BizOrderHandlerFactory.buildBizOrderHandler("Payment");
    BizOrderHandler rightCenter = BizOrderHandlerFactory.buildBizOrderHandler("RightCenter");
    BizOrderHandler pointDeduction = BizOrderHandlerFactory.buildBizOrderHandler("PointDeduction");
    BizOrderHandlerUnionGateway unionGateway = (BizOrderHandlerUnionGateway) BizOrderHandlerFactory.buildBizOrderHandler("UnionGateway");
    storage.setNextBizOrderHandler(unionGateway);
    unionGateway.setNextBizOrderHandler(payment);
    // unionGateway 加入责任链,权益和积分交给这个uniongateway进行代理控制
    unionGateway.setProxyHandlers(Lists.newArrayList(rightCenter, pointDeduction));
    return storage;
}

6、第四次迭代

产品又又又来了,这次提了一个技术需求

用户反馈生产订单流接口响应过慢,页面卡顿,观察接口发现目前的订单流程需要走的链路比较冗长,虽然用了责任链模式但本质上代码执行仍然是同步的,导致一个订单流完成耗费的时间过长,现在希望订单流接口异步化,然后需要发挥分布式部署的优势,每一个环节可以单独分散到每个单个部署节点上执行。

这次我们发现问题需要异步化还要分布式,这怎么办,显然简单的内存责任链不行了,咱们得上升到分布式责任链模式的方式,那怎么实现分布式责任链呢,咱们可以借助MQ来实现消息触发,于是观察者模式上线,这次咱们借助观察者模式的思想彻底完成分布式重构。

ps:果然需求演进的最后就是重构,不重构没有KPI。

咱们首先定义一个事件,这个就是订单流事件:

@Data
public class OrderFlowEvent implements Serializable {

    private String orderNo;

    private String currentFlow;

    private String nextFlow;

}

这个事件可以在订单流发起的时候丢到消息队列里面,然后就可以进行订单流的流转了,下面我们来看消息处理逻辑,咱们使用模板方法再次进行一次代码优化,这里还是一个抽象类,然后我们的,支付、权益、积分只需要实现这个抽象类实现handleEvent逻辑就可以了,此外我们只用一个Topic,当前环节处理完成之后如果还有后续流程则再次发送消息到消息队列,进行下一步处理,此外handlerMap 代表责任链名称和责任链处理器的对应关系,handlerChain则是责任链的环节配置。

@Data
public abstract class BizHandler {

    String topicName = "biz_handle_topic";

    Map<String, BizHandler> handlerMap = new HashMap<>();

    Map<String, String> handlerChain = new LinkedHashMap<>();

    /**
     * 模板方法:在收到订单流的消息之后将进到这里进行业务逻辑处理
     *
     * @param msg 订单流消息
     */
    public void handle(String msg) {
        if (CollectionUtils.isEmpty(handlerMap) || CollectionUtils.isEmpty(handlerChain)) {
            //log.warn("handlerMap or handlerChain is empty");
            return;
        }
        OrderFlowEvent orderFlowEvent = JSON.parseObject(msg, OrderFlowEvent.class);
        String currentFlow = orderFlowEvent.getCurrentFlow();
        String nextFlow = handlerChain.get(currentFlow);
        // 当前环节的处理器进行业务处理
        Result result = handlerMap.get(currentFlow).handleEvent(orderFlowEvent);
        if (!result.isSuccess()) {
            throw new RuntimeException("handleException");
        }
        if (nextFlow == null) {
            return;
        }
        if (result.isSuccess()) {
            // 处理成功并且还有后续流程则再次向订单流Topic中发送消息
            sendFlowMsg(result.getData(), nextFlow, handlerChain.get(nextFlow));
        }
    }

    public abstract Result handleEvent(OrderFlowEvent orderFlowEvent);

    public void sendFlowMsg(Object data, String currentFlow, String nextFlow) {
        OrderFlowEvent orderFlowEvent = new OrderFlowEvent();
        orderFlowEvent.setCurrentFlow(currentFlow);
        orderFlowEvent.setNextFlow(nextFlow);
        MqUtils.sendMsg(topicName, JSON.toJSONString(orderFlowEvent));
    }


}

例如仓储环节可以这样实现:

public class StorageBizHandler extends BizHandler {

    @Override
    public Result handleEvent(OrderFlowEvent orderFlowEvent) {
        System.out.println("StorageBizHandler handle orderFlowEvent=" + JSON.toJSONString(orderFlowEvent));
        return Result.success();
    }
}

使用的时候则可以这样:

public class OrderFlowClient {

    void handleOrder() {
        // 定义一下流程名称和流程实例的对应关系
        Map<String, BizHandler> handlerMap = new HashMap<>();
        handlerMap.put("Storage", new StorageBizHandler());
        handlerMap.put("PointDeduction", new PointDeductionBizHandler());
        handlerMap.put("Payment", new PaymentBizHandler());

        //注意这里用LinkedHashMap 保证顺序 key标识当前流程 value标识下一个流程
        Map<String, String> handlerChain = new LinkedHashMap<>();
        handlerChain.put("Storage", "PointDeduction");
        handlerChain.put("PointDeduction", "Payment");
        handlerChain.put("Payment", null);

        // 开启分布式订单流转
        Map.Entry<String, String> first = handlerChain.entrySet().iterator().next();
        String key = first.getKey();
        OrderFlowEvent orderFlowEvent = new OrderFlowEvent();
        orderFlowEvent.setCurrentFlow("Storage");
        orderFlowEvent.setOrderNo("order001123124123");
        handlerMap.get(key).handle(JSON.toJSONString(orderFlowEvent));
    }

}

最后咱们完成了这次大的技术演进需求,但是就到此结束了吗?按照这种设计思路改动之后你发现分布式环境下各种并发问题又出现了,于是你还需要分布式锁来控制,有了分布式锁你发现环节失败了还得引入重试逻辑,重试应该怎么设计,所以发现到了分布式系统下问题变得复杂了,还得继续想办法一个个攻克。

6、总结

本文通过一次简单的需求演进分别讲述了责任链、模板方法、策略模式、工厂模式、代理模式、观察者模式的使用,通过实际场景介绍下不同需求下如何通过适合的设计模式来解决问题。

最后说一句(求关注!别白嫖!)

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记  就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!

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

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

相关文章

MyBatis-Plus如何娴熟运用乐观锁

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 MyBatis-Plus如何娴熟运用乐观锁 前言乐观锁的基本概念基本概念和原理&#xff1a;为何乐观锁是解决并发问题的有效手段&#xff1a; MyBatis-Plus中乐观锁的支持1. Version 注解&#xff1a;2. 配置乐…

穿越牛熊,股市的春天还有多远?

2023年&#xff0c;资本市场的严冬令无数投资者和机构投资者都感受到了前所未有的压力。VC/PE、公募基金、股权投资类公司等机构&#xff0c;在这一年里业绩普遍不佳&#xff0c;寒意弥漫。VC/PE机构的营业收入普遍呈现负增长&#xff0c;公募基金更是历史上首次连续两年亏损&a…

包含字母数字及特殊字符 三种组合的正则两种写法

//长度8~16位&#xff1b;包含字母、数字及特殊字符 #$%^&*_-//正则1 写法&#xff1a;let reg_1 /^(?![A-Za-z0-9]$)^(?![A-Za-z#$\%^&*_\-]$)^(?![0-9#$\%^&_*\-]$)([A-Za-z0-9#$\%^&*_\-]{8,16})$///正则2 写法&#xff1a;let reg_2 /^(?![A-Za-z#$%…

在Vue中处理接口返回的二进制图片数据

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

产品经理必看,教你写一份简单的产品说明书

作为一名产品经理&#xff0c;你可能会为如何写一份能够有效传达产品特性和使用步骤的说明书而困扰。确实&#xff0c;写作产品说明书的过程中&#xff0c;需要详细并准确展示产品的所有功能&#xff0c;同时保持文本清晰、简洁和易于理解。以下是一些步骤和技巧&#xff0c;可…

AntV L7的pointLayer点图层

本案例使用L7库和Mapbox GL JS创建点数据并加载进地图。 文章目录 1. 引入 CDN 链接2. 引入组件3. 创建地图4. 创建场景5. 创建点数据5.1. 普通 json 数据5.2. geojson 数据 6. 创建点图层6.1. 普通 json 数据6.2. geojson 数据 7. 演示效果8. 代码实现 1. 引入 CDN 链接 <s…

不注册访问 Claude3 大模型

随着Claude3大模型的出世&#xff0c;大模型霸主地位已经发生易位&#xff0c;但是国内使用Claude3官网 无论是注册都不容易&#xff0c;本篇文章主要介绍如何不通过Claude3 官网实现Claude3 大模型的使用&#xff0c;这里优先推荐Chatbot Arena 一、直接通过第三方代理 Chatb…

CorelDRAW Graphics Suite2024免费试用体验15天版下载

使用基于全球知名的 Corel Painter 画笔技术构建的 100 款逼真像素画笔&#xff0c;以全新的方式将您独特的想法变为现实&#xff01;试用 CorelDRAW 的全新美术画笔&#xff0c;探索您的创意想法。 使用 CorelDRAW 中现在可用的远程字体&#xff0c;畅享更多创作自由&#xf…

Humanoid-Gym 开源人形机器人端到端强化学习训练框架!星动纪元联合清华大学、上海期智研究院发布!

系列文章目录 前言 Humanoid-Gym: Reinforcement Learning for Humanoid Robot with Zero-Shot Sim2Real Transfer GitHub Repository: GitHub - roboterax/humanoid-gym: Humanoid-Gym: Reinforcement Learning for Humanoid Robot with Zero-Shot Sim2Real Transfer 一、介…

一键查看:大厂网站都用了啥技术栈,有图有真相。

本次我们采用Wappalyzer插件来看下国内大厂的网站都采用了什么技术架构&#xff0c;文章最后由Wappalyzer的安装方法。 今日头条网站 淘宝网站 哔哩哔哩 京东商城 花瓣网 CSDN 国务院 网易 58同城 腾讯网 如何安装Wappalyzer 用Edge浏览器即可

软考70-上午题-【面向对象技术2-UML】-UML中的图1

一、图的定义 图是一组元素的图形表示&#xff0c;大多数情况下把图画成顶点、弧的联通图。 顶点&#xff1a;代表事物&#xff1b; 弧&#xff1a;代表关系。 可以从不同的角度画图&#xff0c;UML提供了13种图&#xff1a;&#xff08;只看9种&#xff09; 类图&#xff…

内联函数|auto关键字|范围for的语法|指针空值

文章目录 一、内联函数1.1概念1.2特性 二、auto关键字2.2类型别名思考2.3auto简介2.4auto使用细则2.4 auto不能推导的场景 三、基于范围的for循环(C11)3.1 范围for的语法 四、指针空值nullptr(C11)4.1 C98中的指针空值 所属专栏:C初阶 一、内联函数 1.1概念 以inline修饰的函…

内部订单预算管理 在建工程项目结转流程以及用户操作步骤

业务流程概述: 所有的内部定单(项目)在月底时都必须将当月发生的费用按结算规则结转到在建工程卡片中。为保证结转的正确性,在系统中可以先查看项目费用的明细表后再由系统自动结转到在建工程卡片中。 对于已竣工的项目,还需要到系统中查看项目报表,根据报表将工程实际发生…

【人工智能入门必看的最全Python编程实战(2)】

4.4.2 复合语句&#xff1a; if语句&#xff0c;当条件成立时执行语句包。它经常包含elif、else子句。while语句&#xff0c;当条件为真时&#xff0c;重复执行语句包。for语句&#xff0c;遍历列表、字符串、字典、集合等迭代器&#xff0c;依次处理迭代器中的每个元素。matc…

设计模式——2_4 中介者(Mediator)

我寄愁心与明月&#xff0c;随风直到夜郎西 ——李白《闻王昌龄左迁龙标遥有此寄》 文章目录 定义图纸一个例子&#xff1a;怎么调度一组地铁站台和地铁开车指挥中心 碎碎念中介者和表单平台思想但是这种平台便利性是要付出代价的变化隔离原则 姑妄言之 定义 用一个中介者对象…

吴恩达机器学习-可选实验室:特征工程和多项式回归(Feature Engineering and Polynomial Regression)

文章目录 目标工具特征工程和多项式回归概述多项式特征选择功能备用视图扩展功能复杂的功能 恭喜! 目标 在本实验中&#xff0c;你将:探索特征工程和多项式回归&#xff0c;它们允许您使用线性回归的机制来拟合非常复杂&#xff0c;甚至非常非线性的函数。 工具 您将利用在以…

PageHelper 又给我上了一课!

多年不用PageHelper了&#xff0c;最近新入职的公司&#xff0c;采用了此工具集成的框架&#xff0c;作为一个独立紧急项目开发的基础。项目开发起来&#xff0c;还是手到擒来的&#xff0c;但是没想到&#xff0c;最终测试的时候&#xff0c;深深的给我上了一课。 # 我的项目发…

SpringCloud-SpringBoot读取Nacos上的配置文件

在 Spring Boot 应用程序中&#xff0c;可以使用 Spring Cloud Nacos 来实现从 Nacos 服务注册中心和配置中心读取配置信息。以下是如何在 Spring Boot 中读取 Nacos 上的配置文件的步骤&#xff1a; 1. 引入依赖 首先&#xff0c;在 Spring Boot 项目的 pom.xml 文件中添加 …

2007-2022年上市公司迪博内部控制评价缺陷数量数据

2007-2022年上市公司迪博内部控制评价缺陷数量数据 1、时间&#xff1a;2007-2022年 2、范围&#xff1a;上市公司 3、指标&#xff1a;证券代码、证券简称、辖区、证监会行业、申万行业、是否存在财报内控重大缺陷、财报内控重大缺陷数量、是否存在财报内控重要缺陷、财报内…