corda_使用Spring WebFlux从Corda节点流式传输数据

corda

自上次发布以来已经有一段时间了,但我终于回来了! 由于我仍在我的项目中,因此我将再次撰写有关使用Corda的文章。 这次,我们将不再关注Corda,而是将Spring与Corda结合使用。 更具体地说,Spring WebFlux。 为什么这样 第一,因为我们可以。 第二,因为它允许我们流式传输来自Corda节点的事件。 这使我们能够跟踪流的进度或检索对Vault的更新,并将其发送给注册到相关端点的任何客户端。 将WebFlux与Corda结合使用确实会带来一些问题。 有些来自Corda,有些来自Spring。 虽然,Spring问题与我有关,但我期望Spring Boot + WebFlux组合默认对我有更多作用。

科尔达

在这篇文章中,我假设您对Corda有一定的经验,但是如果您确实需要有关Corda的一些信息,我建议您阅读我以前的文章: 什么是Corda和Corda 开发 。 此外,我还建议您看一下使用Spring WebFlux做事,作为WebFlux的介绍。

本教程的内容将使用Corda的3.2开源版本。 我实际上是根据3.1开始写这篇文章的,但是在此期间发布了较新的版本。 因此,有一些基于在这些版本之间移动的注释。

我们还将在Kotlin中实现所有内容,但本文的内容也可以在Java中实现。

示例应用程序简介

我们将为一个非常简单的应用程序建模,该应用程序不会提供太多使用,因此,出于这篇文章的目的,我将它们一起打包。 该应用程序将由一个发送方(由MessageState表示)的一方发送给另一方。 为此, SendMessageFlow将运行,一旦运行,双方将拥有消息的副本,仅此而已。 简短而简单,但应为我们提供足够的知识来证明WebFlux如何与Corda配合使用。

结构体

通常,我从查看依赖关系开始。 尽管由于将代码分成了单独的模块,所以最好先查看小示例应用程序的结构。

+-- app
|   +-- {spring code}
|   +-- build.gradle
+-- cordapp
|   +-- {flow code}
|   +-- build.gradle
+-- contracts-and-states
|   +-- {contracts and states code}
|   +-- build.gradle
+-- build.gradle

这是应用程序结构的快速视图。 app将包含所有Spring代码,并将通过RPC委托给Corda节点。 cordapp模块包含流逻辑, contracts-and-states按照名称的建议进行操作,并包含契约和状态代码。 cordapp模块和contracts-and-states模块都打包到Cordapp Jars中,并转储到Corda节点中。

这些模块中的每个模块都包含一个build.gradle文件,该文件包含其相关的构建信息和相关性。 由于本文不是直接着眼于编写Corda代码,因此我们将不继续详细研究每个模块及其构建文件。 取而代之的是,我们仅在帖子末尾重新整理流代码,以便我们专注于Spring实现。

Spring模块的依赖

以下是app模块的build.gradle文件(包含Spring代码):

我不是Gradle的专家,因此该代码段中可能有一些可以做得更好的事情,但它确实可以做到。

因此,我想强调一些事情。 使用Spring Boot 2.0.3.RELEASE并与此kotlin-spring使用,使用kotlin-spring插件向所有标有某些Spring注释的Kotlin类添加open 。 这在很多情况下都是必需的,因为Spring要求某些类是非最终的。 这在Java中不是问题,但对于Kotlin来说是有问题的,因为默认情况下所有类都是final。 有关该插件的更多信息,请访问kotlinlang.org 。

spring-boot-starter-webflux了WebFlux依赖关系以及常规的Spring Web服务器代码,以使一切正常运行。

rxjava-reactive-streams ,这是一个有趣的问题,稍后我们将看到它的作用。 由于Corda使用RxJava 1.xx而非较新的RxJava2,因此其Observable不会实现Spring WebFlux用于返回React流的Java 8 Publisher接口。 此依赖关系将这些较旧的Observable转换为Publisher因此它们与WebFlux兼容。 我们稍后会在查看代码进行转换时再次谈到。

最后, netty-all版本被强制为4.1.25.Final以解决依赖关系问题。

路由功能

WebFlux引入了一种功能性方法,用于将请求路由到处理请求的功能。 有关更多信息,请参见使用Spring WebFlux进行操作 。 我不想深入探讨WebFlux的工作方式,但我们将快速定义路由功能。 主要原因是由于使用Kotlin而不是Java。 Kotlin提供了使用DSL定义功能的另一种方法。

以下是定义本教程路由的代码:

routes bean接收MessageHandler bean(我们将在后面进行讨论)并将两个URI映射到该MessageHandler找到的函数。 与Java实现相比,DSL允许的版本略短。 此片段中有几个部分需要重点关注。

("/messages")定义两个路由功能的基本请求路径。 DSL允许功能从此基本路径嵌套自己,并帮助传达路线的结构。

一个函数在发送请求后返回的响应中接受TEXT_EVENT_STREAMtext/event-stream ),同时还将APPLICATION_JSONapplication/stream+json )指定为正文的内容。 由于我们已经定义了Content-Type ,因此在大多数情况下,我们可以假设我们将发送一个POST请求(就是这样)。 POST从以前的配置中进一步嵌套,并添加了另一个MessageHandler函数来接受请求。

第二个功能从Corda节点接收更新。 为此,它返回APPLICATION_STREAM_JSON并期望将GET请求发送到/messages/updates

处理函数

在本节中,我们将看一下上一节中几次提到的MessageHandler 。 此类包含执行实际业务逻辑的所有功能。 路由只是达到这一点的一种方法。

我以前的文章“用Spring WebFlux做事”将比本文更深入地解释这些示例中更多WebFlux的特定部分。

下面是处理程序代码:

首先,我们应突出显示NodeRPCConnection类及其类型为CordaRPCOps的属性proxy 。 我从示例Corda和Spring应用程序 (由R3员工编写)中窃取了NodeRPCConnection 。 长话短说, NodeRPCConnection创建到Corda节点的RPC连接,并且proxy返回CordaRPCOpsCordaRPCOps包含所有可用的RPC操作。 这就是Spring与Corda节点交互的方式。

让我们仔细看看updates功能:

此功能返回新消息,将其保存到Vault中。 如果您有一个监视来自Corda节点的更新的应用程序,则这种端点非常有用。

此代码段中与Corda相关的代码全部包含在trackNewMessages函数中。 它使用CordaRPCOpsvaultTrackBy访问保管库服务,并开始跟踪对任何MessageState的更新。 由于我们尚未将任何参数传递给该函数,因此它将仅跟踪UNCONSUMED状态。 vaultTrackBy返回一个DataFeed对象, DataFeed对象可以用于通过snapshot属性检索保管库的snapshot也可以通过访问updates属性来返回一个Observable以允许其订阅更新事件。 这个RxJava Observable是我们用来将数据流回调用者的工具。

这是我们需要使用我前面提到的rxjava-reactive-streams的第一个实例。 toPublisher方法接受一个Observable并将其转换为Publisher 。 请记住,WebFlux需要Java 8兼容的React式流库,这些库必须实现Publisher 。 例如,Spring倾向于使用提供MonoFlux类的Reactor 。

创建Publisher ,需要将其馈送到ServerResponse 。 由于目前一切顺利,我们将通过ok方法返回200响应。 然后将Content-Type设置为APPLICATION_STREAM_JSON因为它包含流数据。 最后,响应的主体从trackNewMessages中获取Publisher trackNewMessages 。 现在,端点已准备好由发出请求的客户端进行订阅。

现在,完成了从节点到客户端的流更新功能。 实际保存新消息怎么办? 此外,是否有任何信息可以传递给发送者有关执行流程的信息? 因此,让我们回答这两个问题。 是的,我们可以使用WebFlux保存新消息。 是的,流程可以返回其当前进度。

下面是post函数的代码,该函数在流的流进度时将新消息保存到发件人和收件人的节点上:

proxy.startTrackedFlow启动一个流程,该流程的进度可以由添加到该流程的任何ProgressTracker跟踪。 此类中定义的startTrackedFlow委托给上述函数,并返回其progress属性; 一个Observable<String>其事件由ProgressTracker的进度组成。

传递到流中的MessageState是从请求传递的Message对象创建的。 这是因为它包含的信息比MessageState本身少,因此可以更轻松地将消息数据输入到端点。 parseMessage传递的字符串X500名称转换为CordaX500Name ,然后假定存在网络中,转换为网络中的Party

然后通过created方法将其打包到响应中。 指定Content-Type来告诉客户端它包含text/event-stream 。 消息的路径使用在执行流之前创建的UUID 。 例如,这可以用于检索特定的消息,但是您需要自己实现该消息,因为我太懒了,因此本文不做。

创建一个客户

现在已经设置了端点,我们应该创建一个可以发送请求并使用发送回它的流的客户端。 稍后,我们将简要地看一下流代码,以更全面地了解正在发生的事情。

为了将请求发送到响应式后端,Spring WebFlux提供了WebClient类。 发送请求后, WebClient可以对响应中发送的每个事件做出React。 下面的MessageClient就是这样做的:

MessageClient包装并使用WebClient将请求发送到WebClient的构建器中指定的地址。 在该课程中,关于反序列化还有一些额外的配置,但是我想暂时重新介绍一下,因为还有一部分内容涉及该主题。

和以前一样, 使用Spring WebFlux做事提供了WebFlux特定方法的深入解释。

因此,让我们单独查看每个请求,首先将POST请求发送到/messages端点:

post方法创建一个用于指定请求内容的构建器。 这应该与我们之前定义的端点匹配。 建立请求后,请调用exchange方法将其发送到服务器。 然后将响应的主体映射到Flux<String> ,以使其可以订阅。 那就是使用React流的本质。 订阅响应后,客户端将决定对每个事件执行他们希望执行的任何处理。 在这种情况下,它只是打印出ProgressTracker的当前步骤。

如果我们通过这段代码发送请求,我们将收到以下信息:

STEP: Verifying
STEP: Signing
STEP: Sending to Counterparty
STEP: Collecting signatures from counterparties.
STEP: Verifying collected signatures.
STEP: Done
STEP: Finalising
STEP: Requesting signature by notary service
STEP: Broadcasting transaction to participants
STEP: Done
STEP: Done

这些是SendMessageFlowProgressTracker定义的步骤。 是的,我知道我还没有向您显示该代码,但是请相信我。 真的没有太多其他。 如您所见,流返回的每个字符串值都将“ STEP”附加到自身

现在进入到/messages/update端点的GET请求:

同样,在这一点上没有什么可显示的。 但是,在幕后,实际上需要大量工作才能使它工作。 我需要打通电话才能解决的所有问题都围绕着序列化和反序列化。 我们将在下一部分中进行介绍。

对此请求的响应如下:

UPDATE: 0 consumed, 1 producedConsumed:Produced:
56781DF3CEBF2CDAFACE1C5BF04D4962B5483FBCD2C2E428352AD82BC951C686(0)
: TransactionState(data=MessageState(sender=O=PartyA, L=London, C=GB, 
recipient=O=PartyB, L=London, C=GB, contents=hello there, 
linearId=1afc6144-32b1-4265-a06e-73b6bb81aef3_b0fa8491-c9b9-418c-ba6e-8b7840faaf30, 
participants=[O=PartyA, L=London, C=GB, O=PartyB, L=London, C=GB]), 
contract=com.lankydanblog.tutorial.contracts.MessageContract, 
notary=O=Notary, L=London, C=GB, encumbrance=null, 
constraint=net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint@4a1febb5)

关于此端点的好处是,它现在与节点保持连接,该节点将继续将所有相关更新发送回此客户端。 上面的请求是原始POST消息的更新。 客户端收到的任何新事件都会在客户端上输出更新。 这就是使此类端点非常适合触发进程或仅在与Corda节点本身分开的前端上显示最新数据的理想之选。

序列化和反序列化

在本节中,我想集中精力正确设置序列化和反序列化。 从/messages/updates端点检索到的数据需要正确地序列化其数据,以传递给客户端,客户端也需要能够反序列化响应数据。

通常,Spring会为您做很多事情,而且仍然会做,但是WebFlux似乎需要一些额外的步骤才能正确设置它。 免责声明,这是根据我的经验,如果您知道执行此操作的更好方法,我将很高兴收到您的来信。

Corda Jackson支持

Spring默认情况下倾向于使用Jackson,并且很方便地,Corda本身提供了很多Jackson设置。 JacksonSupport.cordaModule为诸如PartyCordaX500Name类的类提供了一些序列化和反序列化。 如果在某些基本情况下需要对Corda类进行序列化或反序列化,则这可能会满足您的需求。 在Spring中,您可以创建一个默认的ObjectMapper将检索并添加到其自身的bean。

但是,此路线有一些警告。 由于模块依赖于ObjectMapper可以访问节点信息,例如通过RPC客户端CordaRPCOps访问节点信息,因此无法反序列化某些类。 否则,反序列化PartyAbstractPartyAnonymousParty将会失败。 不仅如此,由于不安全,此功能现已从Corda 3.2弃用。 JacksonSupport.cordaModule也已移入其自己的类( CordaModule )。

我下面提供的解决方案也是Corda从现在开始建议采用的解决方案。

以下是MessageClient/messages/updates端点检索更新时引发的异常(对于本节的其余部分,将使用相同的端点):

com.fasterxml.jackson.databind.ObjectMapper cannot be cast to net.corda.client.jackson.JacksonSupport$PartyObjectMapper

由此,我们可以确定我们的ObjectMapper类型错误,并且实际上需要是PartyObjectMapper的子类型。 再进一步,我们可以看到在JacksonSupport类中也找到了该映射器。 现在,剩下要做的就是创建这个映射器,并使用它而不是默认的ObjectMapper

因此,让我们看看如何做到这一点:

这将创建一个RpcObjectMapper ,它实现PartyObjectMapper并利用RPC检索节点信息,从而可以反序列化各种参与方类。 在createDefaultMapper, CordaModule了之前的CordaModule ,并感谢Spring,对于大多数需要序列化或反序列化的实例(以后要特别注意),它现在是默认的对象映射器。

一些更多的序列化和反序列化配置

现在……我实际上处于一个很奇怪的位置。 我想通过所有其他步骤来使端点正常工作。 但是,无论我做什么,我似乎都无法重新创建曾经遇到的所有错误,然后才能使它起作用。 我不知道该说些什么……我的异常被吞没在某处,阻止我看到正在发生的事情。 无论如何,我们必须继续。 值得庆幸的是,我知道为什么我添加了其余的代码,但我无法再为您提供每个更改都已修复的例外……

Soooo,让我们看一下我们早先开始研究的rpcObjectMapper的最终产品:

这里有一些补充。 JsonComponentModule作为bean添加,以便它拾取定义的@JsonSerializer@JsonDeserializer自定义组件(在其他类中)。 似乎即使将其作为模块添加到映射器,如果要查找和注册自定义JSON组件,它仍然需要创建bean本身。

接下来是MixinModule 。 此类解决了在反序列化Vault.UpdateSecureHash时出现的问题。 让我们仔细看看。

Mixin允许我们将Jackson注释添加到类上,而实际上没有访问类本身的权限,这显然是我们无法控制的,因为这是Corda代码库中的对象。 另一个选择是将其添加到我们之前讨论的CordaModule ,但这是CordaModule

Vault.Update需要此方法,是因为它具有名为isEmpty的方法,该方法不能很好地与Jackson配合使用,Jackson感到困惑,并认为isEmpty与一个名为empty的布尔字段匹配。 因此,当将JSON反序列化回对象时,它将尝试为该字段传递一个值。

MixinModule本身只是一个类,其构造函数将VaultUpdateMixinSecureHashMixin添加到其自身中。 然后,映射器就像添加其他模块一样添加该模块。 任务完成。

添加到VaultUpdateMixin的Jackson批注是@JsonIgnore ,这可以说明@JsonIgnore 。 序列化或反序列化时, isEmpty函数将被忽略。

接下来是SecureHashMixin

3.1升级到3.2后,我已经添加了此功能。 对我来说,似乎忘记了为SecureHash添加Mixin。 CordaModule包括用于SecureHash.SHA256序列化和反序列化,但不包括SecureHash 。 上面的代码是从CordaModule复制和粘贴的, CordaModule一个类与Mixin绑定在一起。

包含此内容后,将解决3.13.2之间的差异。

我想我会为此提出一个问题!

定制序列化器和反序列器

要序列化Vault.UpdateAttachmentConstraint接口需要它自己的自定义序列化程序:

HashAttachmentConstraint可谈的,因为只有HashAttachmentConstraint实际上有任何字段。 这与稍后反序列化器匹配,在反序列化器上读取type JSON字段以确定创建哪个对象。

需要自定义反序列器的最后两个类是ContractStateAttachmentContract (与之前的序列化程序匹配):

ContractStateDeserialiser是一个非常懒惰的实现,因为在本教程中仅使用一种状态。 AttachmentConstraintDeserialiser使用序列化程序中定义的type字段来确定应将其转换为AttachmentConstraint哪种实现。

WebFlux特定的配置

由于使用了WebFlux,本小节将介绍额外的必需配置。 您已经在MessageClient看到了一些配置,但是还需要做一些额外的工作:

客户端需要这个bean能够反序列化application/stream+json以及响应中返回的对象。

要使用配置中定义的Jackson2JsonDecoder ,必须指定WebClientExchangeStrategies 。 不幸的是,没有编写ExchangeStrategies类来拾取我们已经创建的Jackson2JsonDecoder 。 我希望这种配置在默认情况下可以工作,但是哦。 要添加ExchangeStrategies ,必须使用WebClient构建器。 一旦完成,我们终于到了。 完成打包响应的所有序列化以及从客户端使用响应序列的反序列化已完成。

总结了我希望在本文中介绍的所有与Spring相关的代码。

快速了解Flow代码

在结束之前,我将简要展示为完成本教程的目的而编写的流程:

这是一个非常简单的流程,增加了一个ProgressTracker/messages请求用于跟踪流程的当前状态。 简而言之,此流程将传递给它的MessageState并将其发送给交易对手。 在流程中移动时, ProgressTracker将更新为相关步骤。 可以在Corda文档中找到有关使用ProgressTracker更多文档。

关闭时间

老实说,这比我想象的要长得多,而且花了我的时间比我希望的要长得多。

总之,Spring WebFlux提供了使用响应流在响应事件到达时进行处理的功能。 当与Corda一起使用时,可以跟踪流程的进度,并可以保持持久的库更新流,随时准备在它们到达时采取行动。 为了充分利用Corda的WebFlux,我们还必须研究确保对象由服务器正确序列化,然后由客户端反序列化,以便可以使用它们。 Lucky Corda确实提供了其中一些功能,但是缺少一两个类或功能,因此我们需要确保使用提供的对象映射器来使用它们。 不幸的是,WebFlux需要比使用Spring模块时通常所需要的更多的配置,但是没有什么不能解决的。

这篇文章的其余代码可以在我的GitHub上找到

如果您喜欢这篇文章,可以在@LankyDanDev的 Twitter上关注我,我在其中发布新帖子的更新(尽管最近它们的运行速度有所降低)。

翻译自: https://www.javacodegeeks.com/2018/07/streaming-data-corda-node-spring-webflux.html

corda

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

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

相关文章

iTerm2 隐藏用户名和主机名

有时候我们的用户名和主机名太长&#xff0c;比如我的&#xff1a; 这么长的提示符前缀&#xff0c;在终端显示的时候会很不好看&#xff0c;我们可以手动去除。 编辑 ~/.zshrc 文件&#xff0c;增加 DEFAULT_USER"lwx" 配置&#xff0c;如下所示&#xff1a; 注…

投影串口测试程序_串口测试方法和步骤

信号测试与分析版号&#xff1a;xxx编写&#xff1a;xxx1、232串口信号&#xff1a;要点&#xff1a;RS232采用三线制传输分别为TXD\RXD\GND其中TXD为发送信号&#xff0c;RXD为接收信号。全双工&#xff0c;在RS232中任何一条信号线的电压均为负逻辑关系。即&#xff1a;—15v…

springBoot中自定义的yml文件引用的方式

一、yml配置文件 在yam文件中配置自定义的标签 1.在yml配置文件中加入 through:url: http://10.4.2.140:49003/IBSThrough2.测试类进行测试 import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; /…

MacOS安装zsh插件zsh-autosuggestion(自动命令补全和建议)

文章目录安装命令一安装命令二安装命令三使用插件 zsh-autosuggestion 用于命令建议和补全。 GitHub主页&#xff1a;https://github.com/zsh-users/zsh-autosuggestions 安装命令一 cd ~/.oh-my-zsh/custom/plugins/ git clone https://github.com/zsh-users/zsh-autosugg…

javafx 调用java_Java,JavaFX的流畅设计风格拨动开关

javafx 调用java嗨&#xff0c;这次我将在新版本的JMetro中讨论新的Toggle Switch样式。 拨动开关是一种近年来非常流行的控件。 我前一段时间在ControlsFX库中添加了JavaFX实现。 刚刚发布的JMetro版本4.1中提供了此新样式。 什么是拨动开关 在以前的文章中&#xff0c;我讨…

polkit 重新安装_不折腾,为U-NAS安装一个清爽的桌面,把小U打造成双面高手

本帖最后由 emaic 于 2012-2-2 03:41 编辑除了文件的存储和下载外&#xff0c;U-NAS还可以干嘛&#xff1f;其实&#xff0c;只要你-U-NAS的硬件性能足够强悍&#xff0c;U-NAS可以完成很多你意想不到的工作哦&#xff0c;也会有很多意想不到的玩法&#xff0c;希望看了emaic打…

用于zsh的插件incr(目录提示和补全)

文章目录使用命令 wget 下载插件直接下载插件脚本文件配置提示存在不安全目录incr 是一个目录提示和补全插件。 使用命令 wget 下载插件 mkdir ~/.oh-my-zsh/custom/plugins/incr cd ~/.oh-my-zsh/custom/plugins/incr wget -O incr.plugin.zsh http://mimosa-pudica.net/src…

普罗米修斯使用es数据库_用普罗米修斯和格拉法纳仪法来豪猪

普罗米修斯使用es数据库Adam Bien的Porcupine库使配置充当应用程序隔板的专用执行程序服务变得容易。 我创建了一个扩展&#xff0c;通过MicroProfile Metrics公开了豪猪统计信息。 我们还可以通过Prometheus和Grafana仪表板使仪器可见。 进行此扩展的原因是我们希望对Porcupi…

2字节十六进制浮点数 qt_Qt二进制文件操作(读和写)详解

除了文本文件之外&#xff0c;其他需要按照一定的格式定义读写的文件都称为二进制文件。每种格式的二进制文件都有自己的格式定义&#xff0c;写入数据时按照一定的顺序写入&#xff0c;读出时也按照相应的顺序读出。例如地球物理中常用的 SEG-Y 格式文件&#xff0c;必须按照其…

cobol host变量_将Host Cobol批次和Monolith Webapps移动到云和微服务

cobol host变量在Amazon Event “从大型机到微服务– Vanguard迁移到云”中非常有趣的演示。 以下部分可用作迁移模式 &#xff1a;如何从大型机迁移到微服务的不同方式&#xff1a; 重新托管 再造 重构 使用Linux和Java重新平台 回购 退役 全部结合 该演示文稿还展示了V…

maven的常用命令

install 安装 功能&#xff1a; 编译和打包&#xff0c;把打好的可执行的jar包&#xff08;或者war包或者其他包&#xff09;部署到本地maven仓库 编译 javac 打包 -jar&#xff0c;将java代码打包为jar文件 安装到本地仓库-将打包的jar文件&#xff0c;保存到本地仓库目录中…

MacOS在zsh环境下安装和使用终端插件autojump

文章目录介绍安装 autojump使用 git clone使用 HomeBrew 安装配置使用 autojump卸载 autojump介绍 autojump is a faster way to navigate your filesystem. It works by maintaining a database of the directories you use the most from the command line. Directories must…

如何做到服务器虚拟化_尽可能地做到无服务器,但不止于此

如何做到服务器虚拟化毫无疑问&#xff0c;如果您一直关注技术趋势&#xff0c;那么您会看到“无服务器”的兴起。 在某些情况下&#xff0c;“无服务器”被称为“下一个应用程序体系结构”样式。 我什至听说有人说“您不需要技术X&#xff0c;因为无服务器是未来的方式”或“技…

MAC启动redis的目录

找到目录 cd /redis-6.2.1/src ./redis-service

Linux/MacOS 安装 Oh my zsh

文章目录安装curl 安装wget 安装卸载安装 安装方法有两种&#xff0c;可以使用 curl 或 wget&#xff0c;看自己环境或喜好 curl 安装 sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"curl 是下载文件的…

pc 图片预览放大 端vue_企业为什么需要建设PC+手机微信三合一全网营销的网站呢...

随着信息时代的发展&#xff0c;手机一步步的进入了我们的生活&#xff0c;渐渐的有很多东西都在从pc端过渡到手机端&#xff0c;我们在网购的时候会发现一系列的活动&#xff0c;比如说下载手机客户端就能免减多少钱或者抽奖这样的活动&#xff0c;这样在一定的程度上推动了人…

arrays.sort(._Arrays.hashCode(Object [])与Objects.hash(Object…)

arrays.sort(.从JDK 1.5开始 &#xff0c; Arrays类提供了名为“ hashCode ”的重载static方法。 大多数重载方法都接受特定原始类型的数组&#xff0c;但是Arrays.hashCode&#xff08;Object []&#xff09;方法可用于计算引用类型数组的int哈希码。 自从JDK 1.7诞生以来 &am…

windows系统SSH证书设置

在客户端终端运行命令 ssh-keygen -t rsa 然后就会显示这两行&#xff1a; Generating public/private rsa key pair. Enter file in which to save the key (/c/Users/16627/.ssh/id_rsa): 这是让你输入一个文件名&#xff0c;用于保存刚才生成的 SSH key 代码。为了避免麻烦…

肺功能曲线图怎么看_【家装干货】有人说是鸡肋,有人说是功能升级,卫生间装双人洗漱台,你怎么看?...

家里如果是2人甚至4人以上共同居住&#xff0c;为了使用方便&#xff0c;安装双人洗漱台是值得尝试的&#xff0c;毕竟刷牙、洗脸、饭前洗手&#xff0c;或是梳妆、卸妆都会在这里进行。不过是否能安装&#xff0c;需要对双人洗漱台的尺寸有一定掌握&#xff0c;根据卫生间的大…

Linux命令中的$()和${}的区别

文章目录$()${}$() $() 小括号里面是 Linux 命令&#xff0c;作用就是执行里面的命令后返回执行的结果&#xff1b;和 &#xff08;反引号&#xff09;作用一样。 $() 与 &#xff08;反引号&#xff09;都是用来作命令替换的。命令替换与变量替换差不多&#xff0c;都是用来…