Dubbo 接口测试原理及多种方法实践总结

1、什么是 Dubbo?

Dubbo 最开始是应用于淘宝网,由阿里巴巴开源的一款优秀的高性能服务框架,由 Java 开发,后来贡献给了 Apache 开源基金会组织。

下面以官网的一个说明来了解一下架构的演变过程,从而了解 Dubbo 的诞生原因:

  • 单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

  • 垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,提升效率的方法之一是将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的 Web 框架(MVC)是关键。

  • 分布式服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。

  • 流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。

2、Dubbo 架构简介

Dubbo 比较有特点的地方就是这个注册中心,平常我们测试较多的 HTTP 接口,直接请求接口,调用后端服务即可;而 Dubbo 是要先走注册中心获取服务的位置,下面来举个现实生活中的例子来说明。

现实举例

好比大家平常约朋友一起出去吃饭,听说川菜馆“赠李白”不错,然后需要找这家饭店在哪(用小蓝或小黄App),知道了具体的地址才出发,至于是走路,打车还是骑车,就随意了。

这里 App 就相当于注册中心(Registry),我们这群吃货就是消费者(Consumer),商家属于生产者(Provider)。商家把自己的信息注册在 App 上,消费者根据 App 查询到商家的信息,再根据信息找到商家进行消费。

2.1、Zookeeper 简介

之前经常有小伙伴问我 zk 干啥的?怎么用?下面就来简单了解一哈:

ZK,全称就是zookeeper,是 Apache 软件基金会的一个软件项目,它为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。

下面的图示也可以清晰的说明zk的部署和工作的一些方式(具体的技术细节需要的话可以针对zk专门搜索学习):

  • Leader:集群工作的核心,事务请求的唯一调度和处理者,保证事务处理的顺序性。对于有写操作的请求,需统一转发给Leader处理。Leader需决定编号执行操作。

  • Follower:处理客户端非事务请求,转发事务请求转发给Leader,参与Leader选举。

  • Observer观察者:进行非事务请求的独立处理,对于事务请求,则转发给Leader服务器进行处理.不参与投票。

3、什么是 Dubbo 接口?

所谓的 Dubbo 接口,其实就是一个个 Dubbo 服务中的方法,而测试 Dubbo 接口就相当于我们测试人员充当消费者或者创造消费者去"消费"这个方法。

具体的方式有很多,代码、工具、命令皆可,在接下来的内容中来一一演示。

4、Dubbo 接口测试(创造消费者)

以下我将以本地的一个简单的 Dubbo 服务 demo 为例,演示 Dubbo 测试的各种方法。

interface只有两个,分别是OrderServiceUserService

OrderService :slight_smile:

 package com.qinzhen.testmall.service;import com.qinzhen.testmall.bean.UserAddress;import java.util.List;public interface OrderService {/*** 初始化订单* @param userID*/public List<UserAddress> initOrder(String userID);}

UserService :slight_smile:

 package com.qinzhen.testmall.service;import com.qinzhen.testmall.bean.UserAddress;import java.util.List;/*** 用户服务*/public interface UserService {/*** 按照userId返回所有的收获地址* @param userId* @return*/public List<UserAddress> getUserAddressList(String userId);/*** 返回所有的收获地址* @param * @return*/public List<UserAddress> getUserAddressList();}

JavaBean 对象 UserAddress 如下:

package com.qinzhen.testmall.bean;import lombok.AllArgsConstructor;import lombok.Data;import java.io.Serializable;@AllArgsConstructor@Datapublic class UserAddress implements Serializable {private Integer id;private String userAddress; //用户地址private String userId; //用户IDprivate String consignee; //收货人private String phoneNum; //电话号码private String isDefault; //是否为默认地址 Y-是  N-否public UserAddress(){}}

创建一个provider 来实现UserServiceInterface

实现方法中,根据 id 返回对应的用户地址信息即可: ··· package com.qinzhen.testmall.bootuserserviceprovider.service.impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.qinzhen.testmall.bean.UserAddress;
import com.qinzhen.testmall.service.UserService;
import org.springframework.stereotype.Component;import java.util.Arrays;
import java.util.Collections;
import java.util.List;@Component
@Service  //暴露服务
public class UserServiceImpl implements UserService {private UserAddress userAddress1 = new UserAddress(1, "杭州市西湖区XX公司", "1", "qz", "12345678", "Y");private UserAddress userAddress2 = new UserAddress(2, "杭州市西湖区花园", "2", "qz", "12345678", "N");@Overridepublic List<UserAddress> getUserAddressList(String userId) {if (userId.equals("1")){return Collections.singletonList(userAddress1);}else if (userId.equals("2")){return Collections.singletonList(userAddress2);}else {return Arrays.asList(userAddress1, userAddress2);}}@Overridepublic List<UserAddress> getUserAddressList(){return Arrays.asList(userAddress1, userAddress2);}
}

4.1 Java consumer 代码

下面我们编写consumer代码,让服务消费者去注册中心订阅服务提供者的服务地址,以RPC方式,获取远程服务代理,从而执行远程方法,代码也很简单,如下:

  • 代码结构:

    实现场景就是实现OrderService 中的initOrder() 方法,初始化订单,初始化中直接调用userServicegetUserAddressLis(java.lang.String) 方法,具体代码如下:
package com.qinzhen.testmall.service.impl;import com.qinzhen.testmall.bean.UserAddress;import com.qinzhen.testmall.service.OrderService;import com.qinzhen.testmall.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.List;/*** 1、讲服务提供者注册到注册中心(暴露服务)*          1)导入dubbo依赖:操作zookeeper的客户端(curator)* 2、让服务消费者去注册中心订阅服务提供者的服务地址*/@Servicepublic class OrderServiceImpl implements OrderService {@AutowiredUserService userService;public List<UserAddress> initOrder(String userId) {//1.查询用户的收货地址System.out.println("用户ID为:" + userId);List<UserAddress> userAddressList = userService.getUserAddressList(userId);return userAddressList;}}
  • consumer MainApplication
 package com.qinzhen.testmall;import com.qinzhen.testmall.bean.UserAddress;import com.qinzhen.testmall.service.OrderService;import com.qinzhen.testmall.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.stereotype.Service;import java.io.IOException;import java.util.List;/*** 1、将服务提供者注册到注册中心(暴露服务)*          1)导入dubbo依赖:操作zookeeper的客户端(curator)* 2、让服务消费者去注册中心订阅服务提供者的服务地址*/@Servicepublic class MainApplication {public static void main(String[] args) throws IOException {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"consumer.xml"});context.start();OrderService orderService = context.getBean(OrderService.class); // 获取远程服务代理List<UserAddress> userAddresses = orderService.initOrder("3");// 执行远程方法System.out.println(userAddresses);System.out.println("调用完成。。。");System.in.read();}}
  • consumer.xml:
 <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="com.qinzhen.testmall.service.impl"></context:component-scan><dubbo:application name="order-service-comsumer"></dubbo:application><dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry><!--声明需要远程调用远程服务的接口,生成远程服务代理--><dubbo:reference interface="com.qinzhen.testmall.service.UserService" id="userService"></dubbo:reference></beans>
  • 实例演示

首先确保provider已启动: 

运行consumer ,可以看到成功调用到dubbo方法,获取地址列表信息:

4.2 telnet+invoke

我们使用 telnet 命令可以直接访问对应的服务,但是前提是你需要知道服务对应的ip+端口。

如下配置文件,我们可以知道服务暴露在本地的20880端口

dubbo.application.name=boot-user-service-providerdubbo.registry.address=127.0.0.1:2181dubbo.registry.protocol=zookeeperdubbo.protocol.name=dubbodubbo.protocol.port=20880

使用 telnet 命令进行访问,如下出现 Dubbo 字样时说明连接成功:

% telnet localhost 20880Trying ::1...Connected to localhost.Escape character is '^]'.dubbo>

Dubbo 内建的 telnet 命令的说明和用法如下

  • ls

    • ls: 显示服务列表

    • ls -l: 显示服务详细信息列表

    • ls XxxService: 显示服务的方法列表

    • ls -l XxxService: 显示服务的方法详细信息列表

dubbo>lscom.qinzhen.testmall.service.UserServicedubbo>ls -lcom.qinzhen.testmall.service.UserService -> dubbo://192.168.2.xxx:20880/com.qinzhen.testmall.service.UserService?anyhost=true&application=boot-user-service-provider&bind.ip=192.168.2.xxx&bind.port=20880&dubbo=2.6.2&generic=false&interface=com.qinzhen.testmall.service.UserService&methods=getUserAddressList&pid=55472&qos.enable=false&side=provider&timestamp=1615088321885dubbo>dubbo>ls com.qinzhen.testmall.service.UserServicegetUserAddressListgetUserAddressListdubbo>dubbo>ls -l com.qinzhen.testmall.service.UserServicejava.util.List getUserAddressList(java.lang.String)java.util.List getUserAddressList()
  • invoke

    • invoke XxxService.xxxMethod(1234, "abcd", {"prop" : "value"}): 调用服务的方法

    • invoke com.xxx.XxxService.XxxService.xxxMethod(1234, "abcd", {"prop" : "value"}): 调用全路径服务的方法

    • invoke xxxMethod(1234, "abcd", {"prop" : "value"}): 调用服务的方法(自动查找包含此方法的服务)

    • invoke xxxMethod({"name":"zhangsan","age":12,"class":"org.apache.dubbo.qos.legacy.service.Person"}) :当有参数重载,或者类型转换失败的时候,可以通过增加class属性指定需要转换类

    • 当参数为Map<Integer,T>key的类型为Integer时,建议指定类型。例如invoke com.xxx.xxxApiService({"3":0.123, "class":"java.util.HashMap"})

然后我们使用invoke 命令对dubbo方法getUserAddressList()进行调用,如下:

 dubbo>invoke getUserAddressList()[{"consignee":"qz","id":1,"isDefault":"Y","phoneNum":"12345678","userAddress":"杭州市西湖区xx公司","userId":"1"},{"consignee":"qz","id":2,"isDefault":"N","phoneNum":"12345678","userAddress":"杭州市西湖区xx花园","userId":"2"}]dubbo>invoke getUserAddressList("1")[{"consignee":"qz","id":1,"isDefault":"Y","phoneNum":"12345678","userAddress":"杭州市西湖区xx公司","userId":"1"}]elapsed: 14 ms.

泛化调用

测试 Dubbo 服务的时候,我们需要服务端的同学给我们提供 API,没有这个 API 我们是测不了的,而为了解决这个问题,Dubbo 官方又给我们提供了另外一个方法,就是泛化调用,来看看官方的解释:

  • 泛化调用的使用

Dubbo 给我们提供了一个接口GenericService,这个接口只有一个方法,就是$invoke,它接受三个参数,分别为方法名方法参数类型数组参数值数组

下面我们直接上代码演示:

import com.alibaba.dubbo.config.ApplicationConfig;import com.alibaba.dubbo.config.ReferenceConfig;import com.alibaba.dubbo.config.RegistryConfig;import com.alibaba.dubbo.rpc.service.GenericService;import org.junit.jupiter.api.Test;public class TestDemo {@Testvoid testDubboGenericService(){// 引用远程服务// 该实例很重量,里面封装了所有与注册中心及服务提供方连接,请缓存ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();// 弱类型接口名reference.setApplication(new ApplicationConfig("order-service-consumer"));reference.setInterface("com.qinzhen.testmall.service.UserService");reference.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));// 声明为泛化接口reference.setGeneric(true);// 用org.apache.dubbo.rpc.service.GenericService可以替代所有接口引用GenericService genericService = reference.get();Object result = genericService.$invoke("getUserAddressList", new String[] {"java.lang.String"}, new Object[] {"2"});System.out.println(result);}}

运行后我们来看看结果,咦~也成功访问了:

  • 泛化调用的原理

我们通过 debug 跟入 Dubbo 的源码中,可以得到如下的调用链:

服务消费端:

服务提供端:

从上面的调用链可以知道完成一次泛化调用,Dubbo 框架经历了很多过滤器,我们分别选取两端链路中的最后一步的 Filter 来简单了解一下泛化调用做了哪些事.

简化后的调用关系就如下:

先来看consumer 端的GenericImplFilter ,大概看下核心的处理步骤:

// 判断是否为泛化调用if (invocation.getMethodName().equals(Constants.$INVOKE)&& invocation.getArguments() != null&& invocation.getArguments().length == 3&& ProtocolUtils.isGeneric(generic)) {// 获取泛化调用参数Object[] args = (Object[]) invocation.getArguments()[2];// 判断是否为nativejava方式if (ProtocolUtils.isJavaGenericSerialization(generic)) {for (Object arg : args) {if (!(byte[].class == arg.getClass())) {error(byte[].class.getName(), arg.getClass().getName());}}// 判断是否为bean方式} else if (ProtocolUtils.isBeanGenericSerialization(generic)) {for (Object arg : args) {if (!(arg instanceof JavaBeanDescriptor)) {error(JavaBeanDescriptor.class.getName(), arg.getClass().getName());}}}// 设置为泛化调用方式((RpcInvocation) invocation).setAttachment(Constants.GENERIC_KEY, invoker.getUrl().getParameter(Constants.GENERIC_KEY));}// 发起远程调用return invoker.invoke(invocation);

再来看provider端的GenericFilter,大概的核心处理步骤如下:

package com.alibaba.dubbo.rpc.filter;import .../*** GenericInvokerFilter.*/@Activate(group = Constants.PROVIDER, order = -20000)public class GenericFilter implements Filter {@Overridepublic Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {// 判断是否为泛化请求if (inv.getMethodName().equals(Constants.$INVOKE)&& inv.getArguments() != null&& inv.getArguments().length == 3&& !ProtocolUtils.isGeneric(invoker.getUrl().getParameter(Constants.GENERIC_KEY))) {// 获取参数名称、参数类型、参数值String name = ((String) inv.getArguments()[0]).trim();String[] types = (String[]) inv.getArguments()[1];Object[] args = (Object[]) inv.getArguments()[2];try {// 使用反射获取调用方法Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);Class<?>[] params = method.getParameterTypes();if (args == null) {args = new Object[params.length];}// 获取泛化引用方式使用的泛化类型String generic = inv.getAttachment(Constants.GENERIC_KEY);// 泛化类型为空的话就使用generic=true的方式if (StringUtils.isEmpty(generic)|| ProtocolUtils.isDefaultGenericSerialization(generic)) {args = PojoUtils.realize(args, params, method.getGenericParameterTypes());// 判断是否为generic=nativejava方式} else if (ProtocolUtils.isJavaGenericSerialization(generic)) {for (int i = 0; i < args.length; i++) {if (byte[].class == args[i].getClass()) {try {UnsafeByteArrayInputStream is = new UnsafeByteArrayInputStream((byte[]) args[i]);args[i] = ExtensionLoader.getExtensionLoader(Serialization.class).getExtension(Constants.GENERIC_SERIALIZATION_NATIVE_JAVA).deserialize(null, is).readObject();} catch (Exception e) {。。。}} else {。。。}}// 判断是否为generic=bean方式} else if (ProtocolUtils.isBeanGenericSerialization(generic)) {for (int i = 0; i < args.length; i++) {if (args[i] instanceof JavaBeanDescriptor) {args[i] = JavaBeanSerializeUtil.deserialize((JavaBeanDescriptor) args[i]);} else {。。。}}}// 传递请求,执行服务Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));。。。}

上面的代码很多,着重来提取一小段看一下:

Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);Class<?>[] params = method.getParameterTypes();

从上面的代码中我们便可以得知原来泛化调用中使用了Java的反射技术来获取对应的方法信息完成调用的

4.6 用 Python 来测试 Dubbo

我们知道 Dubbo 是个 Java 项目,测试 Dubbo 就是模拟消费者去调用 Dubbo 的 Java 方法,那显而易见,用 Python 是肯定没法去直接调用Java的,但是在日常的工作中,很多小伙伴可能是 Pyhton技术栈的,或者因为一些测试条件限制亦或历史原因,必须要将 Dubbo 测试用 Python 实现以满足各种接口测试的一个组合。

  • 1. python-hessian库

Dubbo是支持hessian+http协议调用的,hessian是一种二进制序列化的方式。

了解到可以通过这种方式实现,具体没有尝试过,还需要开发在项目中将序列化的方式改为hessian,并且需要知道URL,有兴趣的小伙伴可以去了解一下。

  • 2. telnetlib库

telnetlib是Python3自带的一个库,可以调用telnet命令,其实也就相当于上面说到的使用telnet方式访问dubbo的方法

  • 3. 开发dubbo测试服务

我们可以使用 Java 来开发一个 Dubbo 测试的 Web 服务,实现上就可以使用 Dubbo 的泛化调用,然后我们再用 HTTP 访问的形式去访问这个服务,将我们的测试参数信息传过去,剩下的就交给 Java 去处理就好了。

这样经过封装设计后,可以实现 Python 端的使用者在访问 Dubbo 时就像在测试HTTP接口一样(例如 Python 的request库);另外服务的 IP、端口、注册中心等信息都不用出现在测试的工程中,只需要用环境标签做区分,在服务端进行请求转发即可,也保证了一定安全性。

大体上的思路流程如下:

以上,期待与大家多交流学习。

最后:下面是配套学习资料,对于做【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!

软件测试面试小程序

被百万人刷爆的软件测试题库!!!谁用谁知道!!!全网最全面试刷题小程序,手机就可以刷题,地铁上公交上,卷起来!

涵盖以下这些面试题板块:

1、软件测试基础理论 ,2、web,app,接口功能测试 ,3、网络 ,4、数据库 ,5、linux

6、web,app,接口自动化 ,7、性能测试 ,8、编程基础,9、hr面试题 ,10、开放性测试题,11、安全测试,12、计算机基础

  全套资料获取方式:点击下方小卡片自行领取即可

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

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

相关文章

如何使用SQL系列 之 如何在SQL中使用WHERE条件语句

引言 在结构化查询语言 (SQL)语句中&#xff0c;WHERE子句限制了给定操作会影响哪些行。它们通过定义特定的条件(称为搜索条件)来实现这一点&#xff0c;每一行都必须满足这些条件才能受到操作的影响。 本指南将介绍WHERE子句中使用的通用语法。它还将概述如何在单个WHERE子句…

开了抖店后就可以直播带货了吗?想在抖音带货的,建议认真看完!

我是王路飞。 关于抖店和直播带货的关系&#xff0c;其实很多人经常搞不清楚。 不然的话&#xff0c;也不会有这个问题的出现了&#xff1a;开了抖店后就可以直播带货了吗&#xff1f; 在我看来&#xff0c;这个问题很简单&#xff0c;但在不了解抖音电商和直播带货其中门道…

LeetCode——和为K的子数组(中等)

题目 给你一个整数数组 nums 和一个整数 k &#xff0c;请你统计并返回 该数组中和为 k 的连续子数组的个数 。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,1], k 2 输出&#xff1a;2示例 2&#xff1a; 输入&#xff1a;nums [1,2,3], k 3 输出&#xff1a;2 题解 …

The WebSocket session [x] has been closed and no method (apart from close())

在向客户端发送消息时&#xff0c;session关闭了。 不管是单客户端发送消息还是多客户端发送消息&#xff0c;在发送消息之前判断session 是否关闭 使用 isOpen() 方法

Mysql-DML(数据处理语言)

-- 插入数据 insert into member values(1,张三,21,5000); insert into member(id,name) values(2,李四); insert into member values(3,王五,23,3000) ,(4,纪六,24,4000) ,(5,查七,25,5000); -- 更新&#xff08;修改数据) 不加限制条件 该字段全部修改 update member set age…

python .gitignore文件配置

logs/ venv/ *.log .idea/ dist/test.py注意&#xff1a;.gitignore最好放在根目录中。如果gitignore放在被忽略的父目录中&#xff0c;那忽略文件就无法生效。 .gitignore规则 .gitignore只能忽略那些原来没有被track的文件&#xff0c;如果某些文件已经被纳入了版本管理中&…

Elasticsearch 8.X 可以按照数组下标取数据吗?

1、线上环境问题 老师、同学们&#xff0c;有人遇到过这个问题么&#xff0c;索引中有一个 integer 数组字段&#xff0c;然后通过脚本获取数组下标为1的值作为运行时字段&#xff0c;发现返回的值是乱的&#xff0c;并不是下标为1的值&#xff0c; 具体如下: DELETE my_index …

sql:SQL优化知识点记录(六)

&#xff08;1&#xff09;索引优化1 查看一下有没有建立索引&#xff1a; 用到索引中的一个&#xff1a;type中的ref决定访问性能 用到索引中的两个&#xff1a;通过key_len的长度可以看出来&#xff0c;比第一个大一点。或者通过ref&#xff1a;中用到了两个常量const 用到了…

【Android Framework系列】第14章 Fragment核心原理(AndroidX版本)

1 简介 Fragment是一个历史悠久的组件&#xff0c;从API 11引入至今&#xff0c;已经成为Android开发中最常用的组件之一。 Fragment表示应用界面中可重复使用的一部分。Fragment定义和管理自己的布局&#xff0c;具有自己的生命周期&#xff0c;并且可以处理自己的输入事件。…

线程池使用不规范导致线程数大以及@Async的规范使用

文章详细内容来自&#xff1a;线程数突增&#xff01;领导&#xff1a;谁再这么写就滚蛋&#xff01; 下面是看完后文章的&#xff0c;一个总结 线程池的使用不规范&#xff0c;导致程序中线程数不下降&#xff0c;线程数量大。 临时变量的接口&#xff0c;通过下面简单的线…

如何合并为pdf文件?合并为pdf文件的方法

在数字化时代&#xff0c;人们越来越依赖电子文档进行信息交流和存储。合并为PDF成为一种常见需求&#xff0c;它能将多个文档合而为一&#xff0c;方便共享和管理。无论是合并多个单页文档&#xff0c;还是将多页文档合并&#xff0c;操作都变得简单高效。那么。如何合并为pdf…

ESP-C3入门24. 使用中断监控GPIO口

ESP-C3入门24. 使用中断监控GPIO口 一、ESP IDF中断概念1. 一些关键概念&#xff1a;2. GPIO 触发方式 二、ESP32 IDF 使用中断来处理 GPIO 输入的步骤1. 引入 ESP32 IDF 头文件&#xff1a;2. 配置 GPIO 引脚&#xff1a;3. 定义中断处理函数&#xff1a;4. 初始化 GPIO 引脚5…

移除链表元素_每日一题

“路虽远&#xff0c;行则将至” ❤️主页&#xff1a;小赛毛 ☕今日份刷题&#xff1a;移除链表元素 题目描述&#xff1a; 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例1&…

真香:Alibaba开源GitHub星标100K微服务架构全彩进阶手册

前言&#xff1a; 微服务架构作为一种高效灵活的应用架构&#xff0c;正在成为企业级应用开发的主流选择。在众多的微服务架构指南中&#xff0c;阿里巴巴开源的GitHub微服务架构全彩进阶手册备受瞩目&#xff0c;其100star更是证明了其在开发者社区中的重要地位。 这本手册汇…

【Yolov5+Deepsort】训练自己的数据集(3)| 目标检测追踪 | 轨迹绘制 | 报错分析解决

&#x1f4e2;前言&#xff1a;本篇是关于如何使用YoloV5Deepsort训练自己的数据集&#xff0c;从而实现目标检测与目标追踪&#xff0c;并绘制出物体的运动轨迹。本章讲解的为第三部分内容&#xff1a;数据集的制作、Deepsort模型的训练以及动物运动轨迹的绘制。本文中用到的数…

PHP旅游管理系统Dreamweaver开发mysql数据库web结构php编程计算机网页

一、源码特点 PHP 旅游管理系统是一套完善的web设计系统&#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 PHP 旅游管理系统 源码下载地址&#xff1a; https://download.csdn.net/download/qq_41…

2.4 Vector<T> 动态数组(随机访问迭代器)

C自学精简教程 目录(必读) 该 Vector 版本特点 这里的版本主要是使用模板实现、支持随机访问迭代器&#xff0c;支持std::sort等所有STL算法。(本文对随机迭代器的支持参考了 复旦大学 大一公共基础课C语言的一次作业) 随机访问迭代器的实现主要是继承std::iterator<std:…

LeetCode 46题:全排列

题目 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]示例 2&#xff1a; 输入&#xff1a;…

容器技术简介

引言 随着云计算、大数据、人工智能等技术的不断发展&#xff0c;容器技术作为一种新兴的虚拟化技术&#xff0c;正逐渐成为IT领域的热点。容器技术可以帮助开发者更好地管理、部署和扩展应用程序&#xff0c;提高开发效率和应用程序的可靠性。本文将深入探讨容器技术的概念、…

gerrit 如何提交进行review

前言 本文主要介绍如何使用gerrit进行review。 下述所有流程都是参考&#xff1a; https://gerrit-review.googlesource.com/Documentation/intro-gerrit-walkthrough.html 先给一个commit后但是还没有push上去的一个办法&#xff1a; git reset --hard HEAD^可以多次reset.…