流程编排是如何实现解耦代码

news2024/10/6 18:30:20

为什么要使用流程流程编排

问题原因

在我们日常开发中,当我们接到一个复杂业务需求时,大部分开发同学都是通过面向过程编程代码,瀑布式编写代码风格,当新增新的需求时我们需要修改当前的方法,当需求变更很频繁代码的维护成本和测试复杂度成指数级上升,当开发同学离职,下一个新来同学维护代码就很头痛😣,新的需求不知道如何下手。

解决办法

而我们可以通过流程编排来实现编写代码,流程编排就是将一段复杂的业务代码拆分成一个个节点/步骤然后聚合成一条执行链,实现了代码解耦,提高了代码复用性,也遵循了设计模式SOLID设计思想。

流程编排概念

概念

流程编排就是将接口或者一段复杂的业务代码拆分成一个个节点/步骤然后聚合成一条执行链,流程编排适合复杂的业务系统,列如电商交易系统。

名词

  • 流程: 执行处理的整个过程(通常可以理解为一个接口,如下单接口)
    
  • 节点/步骤:一个完整的流程可以分为多个节点,每个节点可以完成单一的业务行为,比如下单流程中的限流、限购、拆单等。一个节点通常是一个类或者spring bean。
    
  • 上下文:将节点返回的数据设置到上下文context中,让后续节点能够获取到相关数据(如订单结算节点需要获取到初始化购物车节点中的信息)
    

生活中的案例

图片.png

流程图

流程图

时序图

ProcessController_process.png

执行器

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

流程节点

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

代码是如何实现

流程上下文

功能:流程上下文是用来处理流程节点请求参数和返回参数传递

/**
 * @Classname Context
 * @Description 流程引擎上下文
 * @Date 2023/7/20 17:53
 * @Created by ZouLiPing
 */
public class Context<T> {

    /**
     * 请求参数上下文
     */
    private RequestContext request = new RequestContext();
    /**
     * 返回参数上下文
     */
    private ResponseContext response = new ResponseContext();
    /**
     * 流程标签 {@link com.zlp.process.enums.ProcessTypeEnum}
     */
    private String processTag;

    /**
     * 业务类型
     */
    private Integer billType;


    public RequestContext getRequest() {
        return request;
    }

    public <T> void createRequestBody(T body) {
        request.setBody(body);
    }


    public ResponseContext getResponse() {
        return response;
    }


    public <T> void createResponseBody(T body) {
        response.setBody(body);
    }

    public void setProcessTag(String processTag) {
        this.processTag = processTag;
    }

    public String getProcessTag() {
        return processTag;
    }

    public Integer getBillType() {
        return billType;
    }

    public void setBillType(Integer billType) {
        this.billType = billType;
    }
}

/**
 * @Description 流程引擎请求参数上下文
 * @Date 2023/7/20 17:53
 * @Created by ZouLiPing
 */
public class RequestContext<T>  {

    private Map<String, Object> header = new ConcurrentHashMap<>();

    private T body;

    public T getBody() {
        return body;
    }

    public void setBody(T body) {
        this.body = body;
    }

    public Object getHeaderValueByKey(String key) {
        return header.get(key);
    }

    public void setHeaderValueByKey(String key, Object value) {
        if (header.containsKey(key)) {
            header.replace(key, value);
        } else {
            header.put(key, value);
        }

    }

    public void removeHeaderKey(String key)
    {
        if (header.containsKey(key)) {
            header.remove(key);
        }
    }


}
/**
 * @Description 流程引擎返回参数上下文
 * @Date 2023/7/22 17:53
 * @Created by ZouLiPing
 */
public class ResponseContext<T>{


    private Map<String, Object> header = new ConcurrentHashMap<>();

    public T getBody() {
        return body;
    }

    public void setBody(T body) {
        this.body = body;
    }

    private T body;


    public Object getHeaderValueByKey(String key) {
        return getHeader().get(key);
    }

    public void setHeaderValueByKey(String key, Object value) {
        if (getHeader().containsKey(key)) {
            getHeader().replace(key, value);
        } else {
            getHeader().put(key, value);
        }

    }

    public Map<String, Object> getHeader() {
        return header;
    }

    public void setHeader(Map<String, Object> header) {
        this.header = header;
    }

流程编排文件

功能:用来定义流程标签名称对应流程节点列表关系映射

[
  {
    "processTag":"ORDER_SECKILL_VIN_TAG",
    "nodeList":[
      "活动时间流程节点-PromoTimeValidNode",
      "商品库存校验流程节点-GoodsStockValidNode",
      "车主校验流程节点-VehicleOwnerValidNode",
      "限购条件流程节点-LimitBuyValidNode",
      "车龄验证流程节点-VehicleAgeValidNode",
      "车型验证流程节点-VehicleTypeValidNode",
      "订单业务组件流程节点-OrderServiceNode"
    ]
  }
]

流程节点

功能:流程工厂主要的功能是将编排好节点文件按照顺序放到容器中

/**
 * @Classname NodeComponent
 * @Description 节点组件基类
 * @Date 2023/7/20 22:13
 * @Created by ZouLiPing2
 */
@Slf4j(topic = "NodeComponent" )
public abstract class NodeComponent {

    /**
     * 执行条件判断分支,可以根据类型分支判断
     *
     * @param context
     * @return boolean
     */
    public void condition(Context context){
       log.info("执行条件语句判断");
    }


    /**
     *  处理上下文
     * @author ZouLiPing
     * @param context
     * @date 2023/7/20 22:15
     */
    public abstract void process(Context context);

    /**
     * 获取节点名称
     * 节点名称会放在node.js中,需要保证一一对应
     */
    public abstract String getProcessNodeName();

    @Override
    public String toString() {
        return getProcessNodeName();
    }
}

/**
 * @Description 车主校验流程节点
 * @Date 2023/7/20 22:22
 * @Created by ZouLiPing
 */
@Component
@RequiredArgsConstructor
@Slf4j(topic = "VehicleOwnerValidNode")
public class VehicleOwnerValidNode extends NodeComponent {

    private final VehicleService vehicleService;

    @Override
    public boolean condition(Context context) {
        if (Objects.equals(context.getBillType(), BillTypeEnum.BILL_TYPE_10001)) {
            OrderReq orderReq = OrderContextUtils.getOrderReq(context);
            Vehicle vehicle =vehicleService.getVehicleByModelCode(orderReq.getAssociateVin());
            if (!Objects.equals(vehicle.getVin(),orderReq.getAssociateVin())){
                throw new CustomException(ResultCode.PROCESS_ORDER_20003.getCode(), ResultCode.PROCESS_ORDER_20003.getMessage(), MethodUtil.getLineInfo());
            }
            return Boolean.TRUE;
        }
        return false;
    }

    @Override
    public void process(Context context) {
        OrderReq orderReq = OrderContextUtils.getOrderReq(context);
        Vehicle vehicle =vehicleService.getVehicleByVin(orderReq.getAssociateVin());
        if (!Objects.equals(vehicle.getVin(),orderReq.getAssociateVin())){
            throw new CustomException(ResultCode.PROCESS_ORDER_20003.getCode(), ResultCode.PROCESS_ORDER_20003.getMessage(), MethodUtil.getLineInfo());
        }
    }

    @Override
    public String getProcessNodeName() {
        return "车主校验流程节点-VehicleOwnerValidNode";
    }
}


/**
 * @Description 订单业务组件流程节点
 * @Date 2023/7/20 22:22
 * @Created by ZouLiPing
 */
@Component
@RequiredArgsConstructor
@Slf4j(topic = "OrderServiceNode")
public class OrderServiceNode extends NodeComponent {

    private final OrderService orderService;


    @Override
    public boolean condition(Context context) {
        return Boolean.FALSE;
    }

    @Override
    public void process(Context context) {
        OrderReq orderReq = OrderContextUtils.getOrderReq(context);
        Order order = orderService.createOrder(orderReq);
        OrderResp orderResp = OrderContextUtils.getOrderResp(context);
        orderResp.setReqNo(String.valueOf(System.currentTimeMillis()));
        orderResp.setStatus(StatusEnum.NORMAL.getValue());
        orderResp.setOrderCode(order.getOrderCode());
        orderResp.setPoint((int)(Math.random()*100));
        OrderResp.SkuResp skuResp = new OrderResp.SkuResp();
        skuResp.setSkuId(125);
        skuResp.setSpuId(456);
        skuResp.setQuantity(1);
        skuResp.setPromoId(567);
        skuResp.setStock(45);
        skuResp.setLimit(1);
        skuResp.setTotalLimit(3);
        orderResp.setSkuRespList(Arrays.asList(skuResp));
        context.createResponseBody(orderResp);
    }

    @Override
    public String getProcessNodeName() {
        return "订单业务组件流程节点-OrderServiceNode";
    }


}


流程执行工厂

功能:流程工厂主要的功能是将编排好节点文件按照顺序放到容器中,在项目启动中通过ApplicationContextAware注入到容器中,方便通过流程引擎调用。


/**
 * 流程编排工厂
 *
 * @date: 2023/7/21 21:00
 */
@Component
@Slf4j(topic = "ProcessNodesFactory")
public class ProcessNodesFactory implements ApplicationContextAware {

    /**
     * 存储的结构如下:
     * 流程标签名称->流程节点名称 列表映射关系
     */
    private ListMultimap<String, NodeComponent> processNodes = LinkedListMultimap.create(12);

    /**
     * 存储的结构如下:
     * 节点名称->节点Bean 列表映射关系
     */
    private ConcurrentHashMap<String, NodeComponent> nodeName = new ConcurrentHashMap<>(12);


    @Value("classpath:process/node.json")
    private Resource nodesJson;


    /**
     * 通过流程标签名称获取流程节点实例
     *
     * @Date: 2023/7/21 21:00
     */
    public List<NodeComponent> getNodeList(String processTag) {
        return processNodes.get(processTag);
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

        log.info("[节点工厂]->开始加载流程节点编排");
        Map<String, NodeComponent> beansOfType = applicationContext.getBeansOfType(NodeComponent.class);
        beansOfType.values().forEach(node -> {
            String processNodeName = node.getProcessNodeName();
            if (nodeName.containsKey(processNodeName)) {
                throw new CustomException(ResultCode.PROCESS_ORDER_20001.getCode(),
                        String.format(ResultCode.PROCESS_ORDER_20001.getMessage(), processNodeName), MethodUtil.getLineInfo());
            }
            nodeName.put(processNodeName, node);
        });

        // 解析节点编排文件
        String jsonNodeData;

        try {
            File file = nodesJson.getFile();
            jsonNodeData = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
        } catch (IOException e) {
            log.error("读取节点流程失败:error={}", e);
            throw new CustomException(ResultCode.PROCESS_ORDER_20003.getCode(), ResultCode.PROCESS_ORDER_20003.getMessage(), MethodUtil.getLineInfo());
        }

        List<ProcessTagNodes> processTagNodes = JSON.parseArray(jsonNodeData, ProcessTagNodes.class);

        for (ProcessTagNodes processNode : processTagNodes) {
            for (String node : processNode.nodeList) {
                NodeComponent nodeComponent = nodeName.get(node);
                if (Objects.isNull(nodeComponent)) {
                    throw new CustomException(ResultCode.PROCESS_ORDER_20004.getCode(),
                            String.format(ResultCode.PROCESS_ORDER_20004.getMessage(), node), MethodUtil.getLineInfo());
                }
                processNodes.put(processNode.processTag, nodeComponent);

            }
        }
        log.info("[节点工厂] 加载完毕,所有的编排为={}", JSON.toJSONString(processNodes.asMap()));
    }


    /**
     * 流程节点名称对象
     */
    @Data
    public static class ProcessTagNodes {

        /**
         * 流程标签名称
         */
        private String processTag;


        /**
         * 流程节点列表
         */
        private List<String> nodeList;

    }
}

流程引擎

功能:流程引擎主要的功能是将上下文参数交给流程引擎处理流程节点,并返回上下文

/**
 * 流程编排工厂
 *
 * @date: 2023/7/21 21:00
 */
@Component
@Slf4j(topic = "ProcessNodesFactory")
public class ProcessNodesFactory implements ApplicationContextAware {

    /**
     * 存储的结构如下:
     * 流程标签名称->流程节点名称 列表映射关系
     */
    private ListMultimap<String, NodeComponent> processNodes = LinkedListMultimap.create(12);

    /**
     * 存储的结构如下:
     * 节点名称->节点Bean 列表映射关系
     */
    private ConcurrentHashMap<String, NodeComponent> nodeComponents = new ConcurrentHashMap<>(12);


    @Value("classpath:process/node.json")
    private Resource nodesJson;


    /**
     * 通过流程标签名称获取流程节点实例
     *
     * @Date: 2023/7/21 21:00
     */
    public List<NodeComponent> getNodeList(String processTag) {
        return processNodes.get(processTag);
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

        log.info("[节点工厂]->开始加载流程节点编排");
        Map<String, NodeComponent> beansOfType = applicationContext.getBeansOfType(NodeComponent.class);
        beansOfType.values().forEach(node -> {
            String processNodeName = node.getProcessNodeName();
            if (nodeComponents.containsKey(processNodeName)) {
                throw new CustomException(ResultCode.PROCESS_ORDER_20001.getCode(),
                        String.format(ResultCode.PROCESS_ORDER_20001.getMessage(), processNodeName), MethodUtil.getLineInfo());
            }
            nodeComponents.put(processNodeName, node);
        });

        // 解析节点编排文件
        String jsonNodeData;

        try {
            File file = nodesJson.getFile();
            jsonNodeData = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
        } catch (IOException e) {
            log.error("读取节点流程失败:error={}", e);
            throw new CustomException(ResultCode.PROCESS_ORDER_20003.getCode(), ResultCode.PROCESS_ORDER_20003.getMessage(), MethodUtil.getLineInfo());
        }

        List<ProcessTagNodes> processTagNodes = JSON.parseArray(jsonNodeData, ProcessTagNodes.class);

        for (ProcessTagNodes processNode : processTagNodes) {
            for (String node : processNode.nodeList) {
                NodeComponent nodeComponent = nodeComponents.get(node);
                if (Objects.isNull(nodeComponent)) {
                    throw new CustomException(ResultCode.PROCESS_ORDER_20004.getCode(),
                            String.format(ResultCode.PROCESS_ORDER_20004.getMessage(), node), MethodUtil.getLineInfo());
                }
                processNodes.put(processNode.processTag, nodeComponent);

            }
        }
        log.info("[节点工厂] 加载完毕,所有的编排为={}", JSON.toJSONString(processNodes.asMap()));
    }


    /**
     * 流程节点名称对象
     */
    @Data
    public static class ProcessTagNodes {

        /**
         * 流程标签名称
         */
        private String processTag;


        /**
         * 流程节点列表
         */
        private List<String> nodeList;

    }
}

流程实例定义和执行

功能:通过模板方案定义执行器的骨架,把公共的校验和执行流程在基类执行,子类可以单独实现自己的方法

/**
 * 流程执行器接口
 */
public interface ExecutorService {


    /**
     * @param context
     *
     * 业务方案参数校验
     */
    void verification(Context context);

    /**
     * @param context
     *
     * 执行业务
     */
    ResponseContext execute(Context context);

    /**
     * @param context

     * 服务结束(返回参数)
     */
    void stop(Context context);
}

/**
 * @author :ZouLiPing
 * @Date :2023年7月22日15:21:47
 * @description:流程实例基础执行类
 */
@Slf4j(topic = "AbstractProcessExecutorService")
public abstract class AbstractProcessExecutorService implements ExecutorService {

    @Resource
    private ProcessEngin processEngin;

    @Resource
    protected ApplicationContext applicationContext;

    /**
     * AbstractProcessExecutorService
     */
    protected AbstractProcessExecutorService proxy;

    @PostConstruct
    public void init() {
        log.info("init 从Spring上下文中获取AOP代理对象");
        //从Spring上下文中获取AOP代理对象
        proxy = applicationContext.getBean(this.getClass());
    }


    @Override
    public void verification(Context context) {
        log.info("校验参数start processTag={}",context.getProcessTag());
        String processCode = context.getProcessTag();
        Assert.isFalse(Objects.isNull(processEngin),"[流程引擎]上下文对象为空!");
        Assert.isFalse(Objects.isNull(processCode),"[流程引擎]节点名称为空!");
        ProcessTypeEnum.checkProcessTag(processCode);
    }


    @Override
    public ResponseContext execute(Context context){
        log.info("execute 流程实例执行请求参数request={}", JSON.toJSONString(context.getRequest().getBody()));
        try {
            proxy.verification(context);
            processEngin.process(context.getProcessTag(), context);
        }catch (CustomException e){
            log.error("流程实例执行自定义异常",e);
            throw new CustomException(e.getCode(),e.getMessage(),e.getMethod());
        }catch (Exception e){
            log.error("流程实例执行异常",e);
            throw new CustomException(ResultCode.FAILED.getCode(),e.getMessage(), MethodUtil.getLineInfo());
        }
        proxy.stop(context);
        ResponseContext response = context.getResponse();
        log.info("execute 流程实例执行结束返回参数 response={}", JSON.toJSONString(response.getBody()));
        return response;
    }


}
/**
 * 流程节点执行
 *
 * @Date 2023/7/20 22:13
 * @Created by ZouLiPing
 */
@Component

@Slf4j(topic = "ProcessNodeExecute")
public class OrderProcessExecute extends AbstractProcessExecutorService implements ExecutorService {


    @Override
    public void verification(Context serviceContext) {
        super.verification(serviceContext);
    }


    @Override
    public ResponseContext execute(Context context) {
        return super.execute(context);

    }


    @Override
    public void stop(Context context) {
        OrderResp orderResp = OrderContextUtils.getOrderResp(context);
        orderResp.setAddress("上海市浦东新区航头镇123456789");

    }
}

流程测试

/**
 * 流程引擎测试
 * @Date 2022/9/21 12:57
 * @Created by ZouLiPing
 */
@RestController
@RequestMapping("/process")
@RequiredArgsConstructor
@Api(value = "process", tags = "流程引擎测试")
public class ProcessController {

    private final OrderProcessExecute processNodeExecute;


    @ApiOperation( "订单流程引擎测试")
    @PostMapping(value = "orderProcessTest")
    public Result<OrderResp> orderProcessTest(@Valid @RequestBody OrderReq orderReq){
        Context context = new Context();
        context.setProcessTag(orderReq.getProcessTag());
        context.setBillType(orderReq.getBillType());
        context.createRequestBody(orderReq);
        ResponseContext responseContext = processNodeExecute.execute(context);
        return Result.success(OrderContextUtils.getOrderResp(responseContext));
    }


}

接口地址:/basic/process/orderProcessTest

请求方式:POST

请求示例:

{
	"associateDealer": "",
	"associateVin": "",
	"billType": 0,
	"processTag": "",
	"receiverAddressId": 0,
	"skuReqList": [
		{
			"promoId": "",
			"quantity": 0,
			"skuId": 0,
			"spuId": 0
		}
	],
	"userId": 0
}

请求参数:

参数名称参数说明in是否必须数据类型schema
orderReqorderReqbodytrue订单请求参数订单请求参数
associateDealer经销商名称falsestring
associateVin车牌号码falsestring
billType业务类型falseinteger(int32)
processTag流程标签名称为空truestring
receiverAddressId收货地址信息falseinteger(int32)
skuReqListsku信息为空truearraySKU请求信息
promoId活动IDfalsestring
quantity数量falseinteger(int32)
skuIdskuIdfalseinteger(int32)
spuIdspuIdfalseinteger(int32)
userId用户IDfalseinteger(int32)

响应参数:

参数名称参数说明类型schema
code错误码integer(int64)integer(int64)
data响应数据订单返回参数订单返回参数
orderCode订单号string
point用户V值integer(int32)
processTag流程标签名称为空string
reqNo请求唯一编号string
skuRespListSKU返回信息arraySKU返回信息
limit限购数量integer(int32)
promoId活动IDinteger(int32)
quantity数量integer(int32)
skuIdskuIdinteger(int32)
spuIdspuIdinteger(int32)
stock库存integer(int32)
totalLimit秒杀商品总库存integer(int32)
status状态integer(int32)
message提示信息string

image.png

总结

优点

  1. 实现代码复用性(解决代码修改错、漏、不一致问题)。一个公共节点可以在多套流程中复用 ;
  2. 符合设计模式单一职责,开闭原则思想。一个节点只需要聚焦某一块业务上,如果产品需要在新增一个需求,只需要在新增一个节点,不需要再原先节点上进行修改;
  3. 方便理解业务。通过json格式配置配上每个节点的注释很好的能够帮助新同学理解该流程实现的过程 ;
  4. 每个流程节点可以有单独测试用例;

缺点

  1. 增加代码编写和理解的难度(毕竟新增一个节点需要新起一个类);
  2. 流程节点密度拆分;

小结

流程编排和责任链的区别
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传为什么说编程编排是“非典型”责任链模式

流程编排的SOLID
  • S (单一职责原则):每个“珠子”职责清晰
  • O (开闭原则):业务逻辑新增则新增“珠子”
  • D (依赖倒置原则):流程引擎执行的是抽象的“珠子接口”,具体“珠子”是使用时注入
避免流程化的陷进
微信截图_20230723114103.png
系统流程和业务流程有什么区别

**系统流程:**系统流程指的是强调的是一个线程一口气执行完的过程,用于描绘系统物理模型,表达数据在系统各个部件之间(程序、文件、数据库、人工过程等)流动的情况。
**业务流程:**业务流程是指一个业务行为的过程,比如请假的流程需要先提交申请,再逐级审批,最后请假成功这样的过程。

代码链接地址:
改变/basic-project
分支:pipeline-0722

参考链接

流程编排在电商系统中应用_uhana的博客-CSDN博客
【学架构也可以很有趣】【“趣”学架构】- 3.搭完架子串珠子_哔哩哔哩_bilibili
系统流程图和业务流程图的区别是什么?

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

恶意不息上线时间/游戏价格/配置要求/加速器推荐

Moon Studios 联合创始人、技术总监 Gennadiy Korol 解释说&#xff1a;我们的目标是让战斗更有身临其境感一些、更加专注一些。而不是屏幕上的信息量多到爆炸&#xff0c;让人看不过来。我们要让玩家真正感受到角色的每一个动作。战斗是贴近的&#xff0c;是专注的。不是屏幕上…

Day:007(3) | Python爬虫:高效数据抓取的编程技术(scrapy框架使用)

Scrapy 保存数据案例-小说保存 spider import scrapyclass XiaoshuoSpiderSpider(scrapy.Spider):name xiaoshuo_spiderallowed_domains [zy200.com]url http://www.zy200.com/5/5943/start_urls [url 11667352.html]def parse(self, response):info response.xpath(&qu…

Linux第90步_异步通知实验

“异步通知”的核心就是信号&#xff0c;由“驱动设备”主动报告给“应用程序”的。 1、添加“EXTI3.c” #include "EXTI3.h" #include <linux/gpio.h> //使能gpio_request(),gpio_free(),gpio_direction_input(), //使能gpio_direction_output(),gpio_get_v…

浅谈Java IO流

Java中的IO流&#xff08;Input/Output streams&#xff09;是Java程序用来处理数据输入和输出的核心工具集。IO流抽象了数据流动的概念&#xff0c;允许Java程序与外部世界进行数据交换&#xff0c;无论是从文件、网络、键盘输入还是向屏幕、文件或网络发送数据。Java IO流按照…

RocketMQ:Windows下开发环境搭建

一、准备工作 从RockitMQ官网下载 | RocketMQ下载最新的release包。我这里下载的版本是v5.2.0 解压到本地目录&#xff0c;bin目录下存放可运行的脚本。 二、RocketMQ基本结构 在动手开发之前&#xff0c;我们需要了解一下RocketMQ的基本结构 如上图所示&#xff0c;一个正常…

【ROS2笔记七】ROS中的参数通信

7.ROS中的参数通信 文章目录 7.ROS中的参数通信7.1使用CLI工具调整参数7.2参数通信之rclcpp实现7.2.1创建节点7.2.2rclcpp参数API Reference ROS2中的参数是由键值对组成的&#xff0c;参数可以实现动态调整。 7.1使用CLI工具调整参数 启动turtlesim功能包的环境 ros2 run …

进程、线程和协程

进程、线程和协程 进程是程序的执行实例 线程是进程的执行路径 协程是基于线程之上但又比线程更加轻量级的存在 进程与线程的区别 线程是程序执行的最小单位&#xff0c;而进程是操作系统分配资源的最小单位 进程和程序的区别 程序&#xff1a;执行特定任务的一串代码&a…

Fastjson报autotype is not support

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 打开AutoType功能 …

快速删除node_modules依赖包的命令rimraf

1、安装rimraf npm install -g rimraf 2、使用命令删除node_modules rimraf node_modules *** window系统&#xff0c;使用命令很快就删除node_modules ***

python-获取config.ini中的属性值

获取配置文件中的数据 import configparser class ReadConfig(object):def __init__(self,config_file_path):self.config configparser.ConfigParser()self.config.read(config_file_path,encodingutf-8)def get_config(self,section,option):config_valueself.config.get(s…

Linux系统——Zookeeper集群

目录 一、Zookeeper概述 1.Zookeeper简介 2.Zookeeper工作机制 3.Zookeeper数据结构 4.Zookeeper应用场景 4.1统一命名服务 4.2统一配置管理 4.3统一集群管理 4.4服务器动态上下线 4.5软负载均衡 5.Zookeeper选举机制 5.1第一次启动选举机制 5.2非第一次启动选举机…

前端开发框架BootStrap

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl BootStrap概述 Bootstrap是一个开源的前端框架&#xff0c;它由Twitter的设计师和开发者创建并维护。Bootstrap提供了许多现成的Web组件&#xff0c;可帮助开发者快速设计和…

关于java分页功能以及传参规范

java分页以及传参规范&#xff0c;特此工作记录 不用插件 //当前页码private static final Integer currentPage 2;//设置每页个数private static final Integer pageSize 5;Testpublic void test8() {//手写一个分页测试&#xff0c;不用插件List<Integer> list new…

vscode中运行js

vscode中运行js 目前vscode插件运行js都是基于node环境&#xff0c;vscode控制台打印有些数据不方便等缺点。 每次调试在浏览器中运行js&#xff0c;需要创建html模板、插入js。期望能够直接运行js可以打开浏览器运行js&#xff0c;在vscode插件市场找到一款插件可以做到。 插…

武汉星起航:跨境电商蓬勃发展 “买卖全球”激发外贸新活力

跨境电商正成为激活外贸新动能的重要力量。武汉星起航了解到&#xff0c;近日&#xff0c;沈阳综合保税区进口货仓内一派繁忙景象&#xff0c;满载着跨境电商进口商品的货车络绎不绝&#xff0c;这些商品将迅速送达全国各地消费者手中。这一景象&#xff0c;正是我国跨境电商业…

用Gold-yolo模块改进yolov8模型

gold-yolo论文&#xff1a; https://arxiv.org/pdf/2309.11331.pdf gold-yolo代码&#xff1a; https://github.com/huawei-noah/Efficient-Computing/tree/master/Detection/Gold-YOLO 一 gold模块简介 Gold-Yolo是华为诺亚方舟实验室2023年发布的工作&#xff0c;主要优化检…

操作系统安全

操作系统属于软件安全的范畴。 什么是操作系统&#xff1f; 操作系统是硬件和软件应用程序之间接口的程序模块&#xff0c;是计算机资源的管理者。操作系统是保证安全的重要基础。 一、操作系统安全基础 操作系统保护的对象 操作系统的安全功能 用户认证存储器保护文件与I/O设…

基于GIS、python机器学习技术的地质灾害风险评价与信息化建库应用

结合项目实践案例和科研论文成果进行讲解。入门篇&#xff0c;ArcGIS软件的快速入门与GIS数据源的获取与理解&#xff1b;方法篇&#xff0c;致灾因子提取方法、灾害危险性因子分析指标体系的建立方法和灾害危险性评价模型构建方法&#xff1b;拓展篇&#xff0c;GIS在灾害重建…

electron打包dist为可执行程序后记【electron-quick-start】

文章目录 目录 文章目录 前言 一、直接看效果 二、实现步骤 1.准备dist文件夹 2.NVM管理node版本 3.准备electron容器并npm run start 4.封装成可执行程序 1.手动下载electron对应版本的zip文件&#xff0c;解决打包缓慢问题 2.安装packager 3.配置打包命令执行内容…

利用CSS延迟动画,打造令人惊艳的复杂动画效果!

动画在前端开发中是经常遇到的场景之一&#xff0c;加入动画后页面可以极大的提升用户体验。 绝大多数简单的动画场景可以直接通过CSS实现&#xff0c;对于一些特殊场景的动画可能会使用到JS计算实现&#xff0c;通过本文的学习&#xff0c;可以让你在一些看似需要使用JS实现的…