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

news2024/11/23 18:38:35

image1080×478 86.9 KB

1、什么是 Dubbo?

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

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

  • 单一应用架构

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

  • 垂直应用架构

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

  • 分布式服务架构

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

  • 流动计算架构

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

2、Dubbo 架构简介

image884×644 68.1 KB

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 

 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 

  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
    @Data
    public class UserAddress implements Serializable {
    
        private Integer id;
        private String userAddress; //用户地址
        private String userId; //用户ID
        private String consignee; //收货人
        private String phoneNum; //电话号码
        private String isDefault; //是否为默认地址 Y-是  N-否
    
        public UserAddress(){
    
        }
    }

创建一个provider 来实现UserService 的Interface :


实现方法中,根据 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");

    @Override
    public 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);
        }
    }

    @Override
    public List<UserAddress> getUserAddressList(){
        return Arrays.asList(userAddress1, userAddress2);
    }
}

···

4.1 Java consumer 代码

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

  • 代码结构:


    实现场景就是实现OrderService 中的initOrder() 方法,初始化订单,初始化中直接调用userService 的getUserAddressLis(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、让服务消费者去注册中心订阅服务提供者的服务地址
     */
    @Service
    public class OrderServiceImpl implements OrderService {
    
        @Autowired
        UserService 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、让服务消费者去注册中心订阅服务提供者的服务地址
     */
    @Service
    public 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.xsd
            http://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已启动:

image1080×276 121 KB


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

image1080×151 57.6 KB

4.2 telnet+invoke

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

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

dubbo.application.name=boot-user-service-provider
    dubbo.registry.address=127.0.0.1:2181
    dubbo.registry.protocol=zookeeper
    dubbo.protocol.name=dubbo
    dubbo.protocol.port=20880

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

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

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

  • ls

    • ls: 显示服务列表

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

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

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

 dubbo>ls
    com.qinzhen.testmall.service.UserService
    
    dubbo>ls -l
    com.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=1615088321885
    
    dubbo>dubbo>ls com.qinzhen.testmall.service.UserService
    getUserAddressList
    getUserAddressList
    
    dubbo>dubbo>ls -l com.qinzhen.testmall.service.UserService
    java.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.

学习链接

其他 Telnet 命令相关操作,需要可参考 Dubbo 官网:

  • https://dubbo.apache.org/zh/docs/v2.7/user/references/telnet 1

4.3 JMeter

对于 JMeter 测试 Dubbo 接口的方法,可参考往期文章:

《基于 Jmeter 完成 Dubbo 接口的测试》 7

4.4 Dubbo-admin

对于 Dubbo-admin 的安装调试,可参考文章:

《dubbo-admin+zookeeper 的环境搭建实操与 Could not extract archive 报错踩坑》

  • dubbo-admin+zookeeper的环境搭建实操与Could not extract archive报错踩坑_TesterAllen的博客-CSDN博客_extract_archive 1

4.5 泛化调用

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

image1023×204 32.6 KB

  • 泛化调用的使用

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 {
    
        @Test
        void 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);
    
        }
    }

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

image1080×198 64.9 KB

  • 泛化调用的原理

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

服务消费端:

image1080×239 15.8 KB


服务提供端:

image1080×331 26 KB

从上面的调用链可以知道完成一次泛化调用,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 {
    
        @Override
        public 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、端口、注册中心等信息都不用出现在测试的工程中,只需要用环境标签做区分,在服务端进行请求转发即可,也保证了一定安全性。

大体上的思路流程如下:

image988×137 9.26 KB


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

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

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

相关文章

初学Java中的方法,看这篇就够了

本篇介绍了Java中方法的概念以及方法的使用(方法的定义和调用,实参和形参的关系).方法重载的介绍和使用,编译器如何实现方法重载- -方法签名,介绍和使用方法调用自身解决问题的技巧–递归 对比递归和循环的优缺点 掌握Java中的方法一.方法的概念及使用1.什么是方法2.方法的使用…

【C++】AVL树

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《吃透西嘎嘎》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;AVL树&am…

【Linux】CentOS、CentOS Stream、RedHat 和Fedora 之间的关系

目录 简单说明 详细说明 红帽&#xff08;Red Hat&#xff09;系和德班&#xff08;Debian&#xff09;系 简单说明 在centos8之前&#xff1a; Fedora 》RedHat 》CentOS Fedora 是RedHat的“试验场”&#xff0c;很多新功能和特性先加入Fedora 稳定后再加入RedHat&…

YOLOv5 引入 最新 BiFusion Neck | 附详细结构图

YOLO 社区自前两次发布以来一直情绪高涨&#xff01;随着中国农历新年2023兔年的到来&#xff0c;美团对YOLOv6进行了许多新的网络架构和训练方案改进。此版本标识为 YOLOv6 v3.0。对于性能&#xff0c;YOLOv6-N在COCO数据集上的AP为37.5%&#xff0c;通过NVIDIA Tesla T4 GPU测…

99.恢复二叉搜索树

99.恢复二叉搜索树 1、题目描述 题目的额外要求是: 使用 O(n) 空间复杂度的解法很容易实现。你能想出一个只使用 O(1) 空间的解决方案吗&#xff1f; 2、解题思路 二叉搜索树中某两个节点被交换了数值&#xff0c;那么对中序序列的影响如下&#xff1a; 假设没有交换之前二叉…

活动星投票千人共读一本书网络评选微信的投票方式线上免费投票

“千人共读一本书”网络评选投票_视频投票评选_投票统计小程序_微信不记名投票用户在使用微信投票的时候&#xff0c;需要功能齐全&#xff0c;又快捷方便的投票小程序。而“活动星投票”这款软件使用非常的方便&#xff0c;用户可以随时使用手机微信小程序获得线上投票服务&am…

正则表达式和re模块

目录 一.基础匹配 1.什么是正则表达式 re模块三个基础方法 re.match(匹配规则&#xff0c;被匹配字符串) search(匹配规则&#xff0c;被匹配字符串) findall(匹配规则&#xff0c;被匹配字符串) 小结 二.元字符匹配 单字符匹配: 示例: 数量匹配 边界匹配 分组匹配…

【Java】【系列篇】【Spring源码解析】【三】【体系】【BeanFactory体系】

BeanFactory体系BeanFactory整体结构体系图顶层接口-BeanFactory1.1、描述1.2、方法解析&#xff08;15个&#xff09;1.2.1、属性1.2.2、获取bean实例1.2.3、获取bean的提供者&#xff08;对象工厂&#xff09;1.2.4、判断是否包含bean1.2.5、单例,原型,bean类型的判断1.2.6、…

SAP ABAP——SAP包(二)【CTS | 传输请求】

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计专业大二本科在读&#xff0c;阿里云社区专家博主&#xff0c;华为云社区云享专家&#xff0c;CSDN SAP应用技术领域新兴创作者。   在学习工…

CentOS7.9配置Nginx反向代理+NodeJS部署上线

Nginx配置 Nginx是一个高性能的HTTP和反向代理服务&#xff0c;许多的大型网站都会采用Nginx来进行HTTP服务器托管 安装编译环境gcc g 进入到root目录&#xff1a; yum -y install make zlib zlib-devel gcc-c libtool openssl openssl-devel 安装PCRE PCRE功能时让Ngi…

ue4c++日记3(碰撞报告碰撞位置|力和扭矩)

目录 代码速查 根据世界方向/局部方向移动 根据世界方向/局部方向旋转 加力加扭矩 1碰撞并报告碰撞位置 两个设为阻挡才会阻挡&#xff0c;其中一个是重叠就会重叠 2例子&#xff1a;旋转前进&#xff01;/原地踏步&#xff1f; 3增加力和扭矩 1.头文件 2.cpp文件 …

(14)go-micro微服务服务层Handle开发

文章目录一 Handle层开发功能说明需要完成的服务开发功能&#xff1a;从哪找需要开发的功能二 代码编写三 最后一 Handle层开发功能说明 需要完成的服务开发功能&#xff1a; 登录注册查询用户信息修改信息发送注册邮件发送重置密码邮件重置密码获取权限修改权限退出账号删除…

【计算机视觉】梯度消失和爆炸以及解决方法

问题 梯度消失无论是笔试还是面试都是常客了,其实对应于梯度消失,还有一个梯度爆炸的概念,这又是什么导致的呢?下面我们将根据公式推导来解释何为梯度消失与梯度爆炸。 梯度消失和梯度爆炸的表现 网络层数越多,模型训练的时候便越容易出现 梯度消失(gradient vanish) 和…

史上最全| 14种自动化分拣系统合集

导语大家好&#xff0c;我是智能仓储物流技术研习社的社长&#xff0c;你的老朋友&#xff0c;老K。新书上市《智能物流系统构成与技术实践》2023年度-厂商宣传合作位--->点击详情本文为研习社原创&#xff0c;违规转载必究01移栽式02偏转轮式03扫臂式04滑靴式05侧向翻转06推…

C++设计模式(3)——抽象工厂模式

抽象工厂模式 亦称&#xff1a; Abstract Factory 意图 抽象工厂模式是一种创建型设计模式&#xff0c; 它能创建一系列相关的对象&#xff0c; 而无需指定其具体类。 问题 假设你正在开发一款家具商店模拟器。 你的代码中包括一些类&#xff0c; 用于表示&#xff1a; …

Vue3系列二:如何实现对响应式数据的代理

上一篇文章中&#xff0c;我们讲解了 Vue3 中对响应式系统的实现&#xff0c;本章节会更进一步的从数据层面分享 Vue3 中对响应式数据是如何进行代理的&#xff0c;本文主要从引用类型数据和基本类型数据两个方面进行讲解。 实现数据代理的基础 理解 Proxy 和 Reflect 首先&…

26.Isaac教程--导航算法

导航算法 本节详细介绍导航算法。 ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html 文章目录导航算法全局路径规划器规划器模型可见性图算法优化器轨迹规划器全局路径规划器 Isaac 框架中的全局规划器问题被分解为三类&#xff1a;规划器模型、…

SpringBoot使用Swagger2

SpringBoot使用Swagger21.引入swagger依赖2.添加swagger配置类3.测试Controller4.测试5.swagger的注解Api注解ApiOperation注解ApiImplicitParam、ApiImplicitParams注解ApiParam注解ApiResponse、ApiResponses注解ResponseHeader注解ApiModel、ApiModelProperty注解6.更多1.引…

Redis 分布式锁实现文章集锦

前言近两年来微服务变得越来越热门&#xff0c;越来越多的应用部署在分布式环境中&#xff0c;在分布式环境中&#xff0c;数据一致性是一直以来需要关注并且去解决的问题&#xff0c;分布式锁也就成为了一种广泛使用的技术&#xff0c;常用的分布式实现方式为Redis&#xff0c…

PDF压缩在线怎么操作?这几个操作谁还不知道

我们在工作里经常处理非常多的文件&#xff0c;如果每个文件都要储存到设备上是非常困难的&#xff0c;因为这需要占用大量的内存&#xff0c;所以我们需要将PDF文件进行压缩&#xff0c;这样就可以释放我们设备的储存空间&#xff0c;不过对于很多人来说&#xff0c;压缩文件并…