dubbo接口地址栏测试(dubbo接口调用原理)

网友投稿 502 2023-04-28


本篇文章给大家谈谈dubbo接口地址栏测试,以及dubbo接口调用原理对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。 今天给各位分享dubbo接口地址栏测试的知识,其中也会对dubbo接口调用原理进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!

本文目录一览:

Dubbo——路由机制(下)

在 Dubbo——路由机制(上) ,介绍了 Router 接口的基本功能以及 RouterChain 加载多个 Router 的实现,之后介绍了 ConditionRouter 这个类对条件路由规则的处理逻辑以及 ScriptRouter 这个类对脚本路由规则的处理逻辑。本文继续介绍剩余的三个 Router 接口实现类。

FileRouterFactory 是 ScriptRouterFactory 的装饰器,其扩展名为 file,FileRouterFactory 在 ScriptRouterFactory 基础上增加了读取文件的能力。可以将 ScriptRouter 使用的路由规则保存到文件中,然后在 URL 中指定文件路径,FileRouterFactory 从中解析到该脚本文件的路径并进行读取,调用 ScriptRouterFactory 去创建相应的 ScriptRouter 对象。

下面来看 FileRouterFactory 对 getRouter() 方法的具体实现,其中完成了 file 协议的 URL 到 script 协议 URL 的转换,如下是一个转换示例,首先会将 file:// 协议转换成 script:// 协议,然后会添加 type 参数和 rule 参数,其中 type 参数值根据文件后缀名确定,该示例为 js,rule 参数值为文件内容。

可以再结合接下来这个示例分析 getRouter() 方法的具体实现:

TagRouterFactory 作为 RouterFactory 接口的扩展实现,其扩展名为 tag。但是需要注意的是,TagRouterFactory 与之前介绍的 ConditionRouterFactory、ScriptRouterFactory 的不同之处在于,它是通过继承 CacheableRouterFactory 这个抽象类,间接实现了 RouterFactory 接口。

CacheableRouterFactory 抽象类中维护了一个 ConcurrentMap 集合(routerMap 字段)用来缓存 Router,其中的 Key 是 ServiceKey。在 CacheableRouterFactory 的 getRouter() 方法中,会优先根据 URL 的 ServiceKey 查询 routerMap 集合,查询失败之后会调用 createRouter() 抽象方法来创建相应的 Router 对象。在 TagRouterFactory.createRouter() 方法中,创建的自然就是 TagRouter 对象了。

通过 TagRouter,可以将某一个或多个 Provider 划分到同一分组,约束流量只在指定分组中流转,这样就可以轻松达到流量隔离的目的,从而支持灰度发布等场景。

目前,Dubbo 提供了动态和静态两种方式给 Provider 打标签,其中动态方式就是通过服务治理平台动态下发标签,静态方式就是在 XML 等静态配置中打标签。Consumer 端可以在 RpcContext 的 attachment 中添加 request.tag 附加属性,注意保存在 attachment 中的值将会在一次完整的远程调用中持续传递,我们只需要在起始调用时进行设置,就可以达到标签的持续传递。

了解了 Tag 的基本概念和功能之后,再简单介绍一个 Tag 的使用示例。

在实际的开发测试中,一个完整的请求会涉及非常多的 Provider,分属不同团队进行维护,这些团队每天都会处理不同的需求,并在其负责的 Provider 服务中进行修改,如果所有团队都使用一套测试环境,那么测试环境就会变得很不稳定。如下图所示,4 个 Provider 分属不同的团队管理,Provider 2 和 Provider 4 在测试环境测试,部署了有 Bug 的版本,这样就会导致整个测试环境无法正常处理请求,在这样一个不稳定的测试环境中排查 Bug 是非常困难的,因为可能排查到最后,发现是别人的 Bug。

为了解决上述问题,我们可以针对每个需求分别独立出一套测试环境,但是这个方案会占用大量机器,前期的搭建成本以及后续的维护成本也都非常高。

下面是一个通过 Tag 方式实现环境隔离的架构图,其中,需求 1 对 Provider 2 的请求会全部落到有需求 1 标签的 Provider 上,其他 Provider 使用稳定测试环境中的 Provider;需求 2 对 Provider 4 的请求会全部落到有需求 2 标签的 Provider 4 上,其他 Provider 使用稳定测试环境中的 Provider。

在一些特殊场景中,会有 Tag 降级的场景,比如找不到对应 Tag 的 Provider,会按照一定的规则进行降级。如果在 Provider 集群中不存在与请求 Tag 对应的 Provider 节点,则默认将降级请求 Tag 为空的 Provider;如果希望在找不到匹配 Tag 的 Provider 节点时抛出异常的话,我们需设置 request.tag.force = true。

如果请求中的 request.tag 未设置,只会匹配 Tag 为空的 Provider,也就是说即使集群中存在可用的服务,若 Tag 不匹配也就无法调用。一句话总结,携带 Tag 的请求可以降级访问到无 Tag 的 Provider,但不携带 Tag 的请求永远无法访问到带有 Tag 的 Provider。

下面再来看 TagRouter 的具体实现。在 TagRouter 中持有一个 TagRouterRule 对象的引用,在 TagRouterRule 中维护了一个 Tag 集合,而在每个 Tag 对象中又都维护了一个 Tag 的名称,以及 Tag 绑定的网络地址集合,如下图所示:

另外,在 TagRouterRule 中还维护了 addressToTagnames、tagnameToAddresses 两个集合(都是 Map<String, List<String 类型),分别记录了 Tag 名称到各个 address 的映射以及 address 到 Tag 名称的映射。在 TagRouterRule 的 init() 方法中,会根据 tags 集合初始化这两个集合。

了解了 TagRouterRule 的基本构造之后,我们继续来看 TagRouter 构造 TagRouterRule 的过程。TagRouter 除了实现了 Router 接口之外,还实现了 ConfigurationListener 接口,如下图所示:

ConfigurationListener 用于监听配置的变化,其中就包括 TagRouterRule 配置的变更。当我们通过动态更新 TagRouterRule 配置的时候,就会触发 ConfigurationListener 接口的 process() 方法,TagRouter 对 process() 方法的实现如下:

我们可以看到,如果是删除配置的操作,则直接将 tagRouterRule 设置为 null,如果是修改或新增配置,则通过 TagRuleParser 解析传入的配置,得到对应的 TagRouterRule 对象。TagRuleParser 可以解析 yaml 格式的 TagRouterRule 配置,下面是一个配置示例:

经过 TagRuleParser 解析得到的 TagRouterRule 结构,如下所示:

除了上图展示的几个集合字段,TagRouterRule 还从 AbstractRouterRule 抽象类继承了一些控制字段,后面介绍的 ConditionRouterRule 也继承了 AbstractRouterRule。

AbstractRouterRule 中核心字段的具体含义大致可总结为如下:

我们可以看到,AbstractRouterRule 中的核心字段与前面的示例配置是一一对应的。

我们知道,Router 最终目的是要过滤符合条件的 Invoker 对象,下面我们一起来看 TagRouter 是如何使用 TagRouterRule 路由逻辑进行 Invoker 过滤的,大致步骤如下:

上述流程的具体实现是在 TagRouter.route() 方法中,如下所示:

除了之前介绍的 TagRouterFactory 继承了 CacheableRouterFactory 之外,ServiceRouterFactory 也继承 CachabelRouterFactory,具有了缓存的能力,具体继承关系如下图所示:

ServiceRouterFactory 创建的 Router 实现是 ServiceRouter,与 ServiceRouter 类似的是 AppRouter,两者都继承了 ListenableRouter 抽象类(虽然 ListenableRouter 是个抽象类,但是没有抽象方法留给子类实现),继承关系如下图所示:

ListenableRouter 在 ConditionRouter 基础上添加了动态配置的能力,ListenableRouter 的 process() 方法与 TagRouter 中的 process() 方法类似,对于 ConfigChangedEvent.DELETE 事件,直接清空 ListenableRouter 中维护的 ConditionRouterRule 和 ConditionRouter 集合的引用;对于 ADDED、UPDATED 事件,则通过 ConditionRuleParser 解析事件内容,得到相应的 ConditionRouterRule 对象和 ConditionRouter 集合。这里的 ConditionRuleParser 同样是以 yaml 文件的格式解析 ConditionRouterRule 的相关配置。ConditionRouterRule 中维护了一个 conditions 集合(List<String 类型),记录了多个 Condition 路由规则,对应生成多个 ConditionRouter 对象。

整个解析 ConditionRouterRule 的过程,与前文介绍的解析 TagRouterRule 的流程类似。

在 ListenableRouter 的 route() 方法中,会遍历全部 ConditionRouter 过滤出符合全部路由条件的 Invoker 集合,具体实现如下:

ServiceRouter 和 AppRouter 都是简单地继承了 ListenableRouter 抽象类,且没有覆盖 ListenableRouter 的任何方法,两者只有以下两点区别。

本文我们是紧接 Dubbo——路由机制(上) 的内容,继续介绍了剩余 Router 接口实现的内容。

我们介绍了基于文件的 FileRouter 实现,其底层会依赖之前介绍的 ScriptRouter;接下来又讲解了基于 Tag 的测试环境隔离方案,以及如何基于 TagRouter 实现该方案,同时深入分析了 TagRouter 的核心实现;最后我们还介绍了 ListenableRouter 抽象类以及 ServerRouter 和 AppRouter 两个实现,它们是在条件路由的基础上添加了动态变更路由规则的能力,同时区分了服务级别和服务实例级别的配置。

Dubbo——Mock 机制

Mock 机制是 RPC 框架中非常常见、也非常有用的功能,不仅可以用来实现服务降级,还可以用来在测试中模拟调用的各种异常情况。Dubbo 中的 Mock 机制是在 Consumer 这一端实现的,具体来说就是在 Cluster 这一层实现的。

在前面深入介绍了 Dubbo 提供的多种 Cluster 实现以及相关的 Cluster Invoker 实现,其中的 ZoneAwareClusterInvoker 就涉及了 MockClusterInvoker 的相关内容。本文我们就来介绍 Dubbo 中 Mock 机制的全链路流程,不仅包括与 Cluster 接口相关的 MockClusterWrapper 和 MockClusterInvoker,还会回顾之前的 Router 和 Protocol 接口,分析它们与 Mock 机制相关的实现。

Cluster 接口有两条继承线(如下图所示):一条线是 AbstractCluster 抽象类,这条继承线涉及的全部 Cluster 实现类;另一条线是 MockClusterWrapper 这条线。

MockClusterWrapper 是 Cluster 对象的包装类,在之前介绍 Dubbo SPI 机制时已经分析过 Wrapper 的功能,MockClusterWrapper 类会对 Cluster 进行包装。下面是 MockClusterWrapper 的具体实现,其中会在 Cluster Invoker 对象的基础上使用 MockClusterInvoker 进行包装:

MockClusterInvoker 是 Dubbo Mock 机制的核心,它主要是通过 invoke()、doMockInvoke() 和 selectMockInvoker() 这三个核心方法来实现 Mock 机制的。

下面就来逐个介绍这三个方法的具体实现。

首先来看 MockClusterInvoker 的 invoke() 方法,它会先判断是否需要开启 Mock 机制。如果在 mock 参数中配置的是 force 模式,则会直接调用 doMockInvoke() 方法进行 mock。如果在 mock 参数中配置的是 fail 模式,则会正常调用 Invoker 发起请求,在请求失败的时候,会调动 doMockInvoke() 方法进行 mock。下面是 MockClusterInvoker 的 invoke() 方法的具体实现:

在 doMockInvoke() 方法中,首先调用 selectMockInvoker() 方法获取 MockInvoker 对象,并调用其 invoke() 方法进行 mock 操作。doMockInvoke() 方法的具体实现如下:

selectMockInvoker() 方法中并没有进行 MockInvoker 的选择或是创建,它仅仅是将 Invocation 附属信息中的 invocation.need.mock 属性设置为 true,然后交给 Directory 中的 Router 集合进行处理。selectMockInvoker() 方法的具体实现如下:

MockInvokersSelector 是 Dubbo Mock 机制相关的 Router 实现,在未开启 Mock 机制的时候,会返回正常的 Invoker 对象集合;在开启 Mock 机制之后,会返回 MockInvoker 对象集合。MockInvokersSelector 的具体实现如下:

在 getMockedInvokers() 方法中,会根据 URL 的 Protocol 进行过滤,只返回 Protocol 为 mock 的 Invoker 对象,而 getNormalInvokers() 方法只会返回 Protocol 不为 mock 的 Invoker 对象。

介绍完 Mock 功能在 Cluster 层的相关实现之后,还要来看一下 Dubbo 在 RPC 层对 Mock 机制的支持,这里涉及 MockProtocol 和 MockInvoker 两个类。

首先来看 MockProtocol,它是 Protocol 接口的扩展实现,扩展名称为 mock。MockProtocol 只能通过 refer() 方法创建 MockInvoker,不能通过 export() 方法暴露服务,具体实现如下:

下面再来看 MockInvoker 是如何解析各类 mock 配置的,以及如何根据不同 mock 配置进行不同处理的。这里重点来看 MockInvoker.invoke() 方法,其中针对 mock 参数进行的分类处理具体有下面三条分支:

MockInvoker.invoke() 方法的具体实现如下所示:

针对 return 和 throw 的处理逻辑比较简单,但 getInvoker() 方法略微复杂些,其中会处理 MOCK_MAP 缓存的读写、Mock 实现类的查找、生成和调用 Invoker,具体实现如下:

在 getMockObject() 方法中会检查 mockService 参数是否为 true 或 default,如果是的话,则在服务接口后添加 Mock 字符串,作为服务接口的 Mock 实现;如果不是的话,则直接将 mockService 实现作为服务接口的 Mock 实现。getMockObject() 方法的具体实现如下:

本文重点介绍了 Dubbo 中 Mock 机制涉及的全部内容:

14. dubbo源码-集群容错之MergeableCluster

在dubbo官方的用户手册中,提到了使用 MergeableCluster 的场景--分组聚合:

功能示意图如下:

定义菜单接口方式:

Provider暴露服务--一个服务属于 group-hot ,一个服务属于 group-cold :

笔者测试时启动了两个Provider,所以总计有四个服务,dubbo-monitor监控显示如下:

Consumer调用服务:

几个重要的配置说明:

com.alibaba.dubbo.rpc.cluster.Merger 文件内容如下:

核心源码在 MergeableClusterInvoker.java 中,源码如下所示:

在条件分支 if ( merger.startsWith(".") ) {} 中,有一段逻辑: method = returnType.getMethod( merger, returnType ); ,即从dubbo服务接口方法返回类型即 java.util.List 中查找merger配置的方法,例如 .addAll ,我们先看一下debug过程各变量的值:

dubbo源码中 method = returnType.getMethod( merger, returnType ); 调用 Method method = getMethod0(name, parameterTypes, true); ,再调用 Method res = privateGetMethodRecursive(name, parameterTypes, includeStaticMethods, interfaceCandidates); ,最后调用 searchMethods(privateGetDeclaredMethods(true), name, parameterTypes)) ,得到最后方法匹配的核心逻辑如下:

从searchMethods()源码可知,方法匹配需要满足几个条件:

由上面的分析可知,如果要merger=".addAll"能够正常工作,那么只需要将dubbo服务的返回类型改成 Collection 即可,例如:

如果 com.alibaba.dubbo.rpc.cluster.Merger 文件集中方法无法满足需求,需要自定义实现,那么还是和dubbo其他扩展实现一样,依赖SPI。只需要一下几步实现即可:

Dubbo的基本使用

官网地址: http://dubbo.apache.org/zh/docs/v2.7/user/examples/loadbalance
如果在消费端和服务端都配置了负载均衡策略,以消费端为准。

在服务提供者和服务消费者上都可以配置服务超时时间,这两者是不一样的。

消费者调用一个服务,分为三步:
1.消费者发送请求(网络传输)
2.服务端执行服务
3.服务端返回响应(网络传输)

如果在服务端和消费端各配置了一个timeout,那就比较复杂了,假设
1.服务执行为5s
2.消费端timeout=3s
3.服务端timeout=6s
那么消费端掉用服务时,消费端会收到超时异常(因为消费端超时了),服务端一切正常(服务端没有超时)。

官网地址: http://dubbo.apache.org/zh/docs/v2.7/user/examples/fault-tolerent-strategy/
集群容错表示:服务消费者在掉用某个服务时,这个服务有多个服务提供者,在经过负载均衡后选择其中一个服务提供者之后进行调用,但调用报错后,Dubbo所采取的后续处理策略。

官网地址: http://dubbo.apache.org/zh/docs/v2.7/user/example/service-downgrade/

服务降级表示:服务消费者在调用某个服务提供者时,如果该服务提供者报错了,所采取的措施。

集群容错和服务降级的区别在于:
1.集群容错时整个集群范围内的容错
2.服务降级时单个服务提供者的自身容错

官网地址: http://dubbo.apache.org/zh/docs/v2.7/user/examples/local-stub/
本地存根:名字很抽象,但实际上不难理解,本地存根就是一段逻辑,这段逻辑是在服务消费端执行的,这段逻辑一般都是由服务提供者提供,服务提供者可以利用这种机制在服务消费者远程调用服务提供者之前或之后再做一些其他事情,比如结果缓存,请求参数验证等等。

官网地址: http://dubbo.apache.org/zh/docs/v2.7/user/examples/local-mock
本地伪装就是Mock,Dubbo中的Mock的功能相对于本地存根更简单一点,Mock其实就是Dubbo中的服务容错的解决方案。

官网地址: http://dubbo.apache.org/zh/docs/v2.7/user/examples/callback-parameter/
如果当前服务支持参数回调,意思就是对于某个服务接口中的某个方法,如果想支持消费者在调用这个方式时能设置回调逻辑,那么该方法就是需要提供一个入参用来表示回调逻辑
因为Dubbo协议是基于长连接,所以消费者在两次调用同一个方法想指定不同的回调逻辑,那么就需要在调用时在指定一定key进行区分。

官网地址: http://dubbo.apache.org/zh/docs/v2.7/user/examples/async-call/
理解起来比较容易,主要要理解CompletableFuture,如果不理解,就直接把它理解为Future
其他异步调用方式: https://mp.weixin.qq.com/s/U3eyBUy6HBVy-xRw3LGbRQ

官网地址: http://dubbo.apache.org/zh/docs/v2.7/user/example/generic-reference/
泛化调用可以用来做服务测试。
在Dubbo中,如果某个服务想要支持泛化调用,就可以将该服务的generic属性设置为true,那对于服务消费者来说,就可以不用依赖该服务的接口,直接利用GenericService接口来进行服务调用

官网地址: http://dubbo.apache.org/zh/docs/v2.7/user/examples/generic-service/
实现了GenericService接口就是泛化服务

官网地址: http://dubbo.apache.org/zh/docs/v2.7/user/rest/

github地址: https://github.com/apache/dubbo-admin

官网地址: http://dubbo.apache.org/zh/docs/v2.7/user/examples/config-rule/
注意动态配置修改的是服务参数,并不能修改服务的协议,IP,PORT,VERSION,GROUP,因为这5个信息是服务的标识信息,是服务的身份证号,是不能修改的。

官网地址: http://dubbo.apache.org/zh/docs/v2.7/user/example/routing-rule/

https://zhuanlan.zhihu.com/p/42671353

telnet调用Dubbo接口的坑

通过telnet命令dubbo接口地址栏测试,去调⽤使用dubbo接口地址栏测试了dubbo框架dubbo接口地址栏测试的查询订单接口,一直调用出错, 查询后台⽇志,发现uAccount字段没有传递过去,为null。

通过telnet命令调用的dubbo接口,会在InvokeTelnetHandler将json字符串转换为对应的请求参数对象,在95行中,通过PojoUtils.realize()方法进行转换。

在PojoUtils中,主要是通过realize0这个方法进行反序列化,在438和439来获取属性的set方法或者直接获取这个属性,然后在下面调用反射调用set方法或者直接通过反射赋值。

获取set方法的时候,把set紧跟的这个字符转换成了大写,也就是setUAccount,咱们通过idea自动生成get/set方法,像这种类型(小写开头,紧跟大写字母)产生的get/set后面紧跟的第一个字母都是小写字母,所以就获取不到set方法了。

getField方法中,由于咱们设置的属性一般都private属性的,所有这里通过class.getField()和class.getFields()都获取不到。

通过dubbo接口调用时,会通过序列化组件将请求对象通过指定的协议(默认hessian2)转换为二进制,此方式转化时不会出现以上问题。

小白也能看懂的dubbo3应用级服务发现详解

dubbo 是一款开源的 RPC 框架,主要有3个角色: 提供者(provider) 、 消费者(consumer) 、 注册中心(registry)

提供者启动时向注册中心注册服务地址,消费者启动时订阅服务,并通过获取到的提供者地址发起调用,当提供者地址变更时,通过注册中心向消费者推送变更。这就是 dubbo 主要的工作流程。

在2.7.5之前,dubbo 只支持接口级服务发现模型,=2.7.5的版本提供了接口级与应用级两种服务发现模型,3.0之后的版本应用级服务发现更是非常重要的一个功能。

本文将从为什么需要引入应用级服务发现,dubbo 实现应用级服务发现的难点以及dubbo3 是如何解决这些问题这三个部分进行讲解。

开始前,dubbo接口地址栏测试我们先了解下 dubbo 最初提供的接口级服务发现是怎样的。

dubbo 服务的注册发现是以 接口 为最小粒度的,在 dubbo 中将其抽象为一个 URL ,大概长这样:

看着很乱dubbo接口地址栏测试?捋一捋:

无论是存储还是变更推送压力都可能遇到瓶颈,数据多表现在这两个方面:

这个问题好解决: 拆dubbo接口地址栏测试

dubbo 在 2.7 之后的版本支持了 元数据中心 与 配置中心 ,对于URL的参数进行分类存储。持久不变的(如application、method等)参数存储到元数据中心中,可能在运行时变化(timeout、tag)的存储到配置中心中

无论是增加一台机器还是增加一个接口,其增长都是线性的,这个问题比单条数据大更严重。

当抹去注册信息中的 interface 信息,这样数据量就大大减少

只用过 dubbo 的同学可能觉得这很主流。

但从服务发现的角度来看:

无论是用的最多的服务注册发现系统 DNS ,又或者是 SpringCloud 体系、 K8S 体系,都是以应用为维度进行服务注册发现的,只有和这些体系对齐,才能更好地与之进行打通。

在我了解的范围里,目前只有 dubbo 、 SOFARPC 、 HSF 三个阿里系的 RPC 框架支持了接口级的服务发现。

provider端暴露服务:

consumer端引用服务:

本地调用远程的方法时,只需要配置一个 reference ,然后直接使用 interface 来调用,我们不必去实现这个 interaface,dubbo 自动帮我们生成了一个代理进行 RPC 调用,屏蔽了通信的细节,让我们有种 像调用本地方法一样调用远程方法的感觉 ,这也是 dubbo 的优势。

从这里我们能看出为什么 dubbo 要设计成接口级服务发现,因为要为每一个 interface 生成一个代理,就必须定位到该 interface 对应服务暴露的服务地址,为了方便,dubbo 就这么设计了。

如果让我来设计应用级服务发现,注册不必多说,按应用名注册即可。

至于订阅,在目前 dubbo 机制下,必须得告诉消费者消费的每个接口是属于哪个应用,这样才能定位到接口部署在哪里。

实现 dubbo 应用级服务发现,难点在于

保留接口级服务发现,且默认采取双注册方式,可配置使用哪种服务发现模型,如下配置使用应用级服务发现

名词有点高大上,但道理很简单,让 dubbo 自己去匹配,提供者注册的时候把接口和应用名的映射关系存储起来,消费者消费时根据接口名获取到部署的应用名,再去做服务发现。

数据存储在哪里?显然元数据中心非常合适。该方案用户使用起来和之前接口级没有任何不同,但需要增加一个元数据中心,架构变得复杂。

且有一个问题是,如果接口在多个应用下部署了,dubbo 查找的策略是都去订阅,这可能在某些场景下不太合适。

本文从接口级服务发现讲到应用级服务发现,包含了为什么 dubbo 设计成接口级服务发现,接口级服务发现有什么痛点?基于 dubbo 现状如何设计应用级服务发现,应用级服务发现实现有什么难点等等问题进行解答,相信看完的小伙伴一定有所收获。

关于dubbo接口地址栏测试和dubbo接口调用原理的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。 dubbo接口地址栏测试的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于dubbo接口调用原理、dubbo接口地址栏测试的信息别忘了在本站进行查找喔。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:详解如何构建Angular项目目录结构
下一篇:浅谈关于axios和session的一些事
相关文章

 发表评论

暂时没有评论,来抢沙发吧~