分享实录|区块链技术与智能合约入门(开发实例)

image

1

什么是区块链

1.1白话讲解区块链

现在区块链特别火,可能大家都听说过区块链,听说过比特币,那到底什么是区块链?

前几天和一个朋友撸串,我给他安利区块链和比特币以及一些数字货币的知识,这个朋友也是一个技术人,他问了我一句:你说了半天区块链、数字货币,我是做技术的能听懂,但是别人就不一定了,所以除了这些技术名词以外,你能不能通俗地介绍一下什么是区块链。

当时一下就把我问住了,百度搜索出来的一个官方答案是:区块链是分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式 。看完这句话之后,相信大部分人还是不能完全理解。

那么,我用大白话重新解释一下“什么是区块链”。区块链就是人人参与的、不需要中介的、带有奖励机制的一种记账方式。因为人人都有这个账本的备份,每笔账具有连续性,所以不会作假,公开透明。并且不会有一家独大的情况存在,因为需要大家达成共识后才会记这笔帐。

image

举个例子来帮助理解。

比如有一个村子,村民老孙找老李借一百块钱,但老李怕他赖账,于是就找来村长做公证,并记下这笔账。这个就叫中心化。但如果你不找村长,直接拿个喇叭在村里大喊“我老李借给老孙一百块钱!请大家记在账本里”,然后大家过来验证过确实有这么回事,于是记到了账本上,就叫去中心化。

以前村长德高望重,掌握全村的账本,大家都把钱存在他这里,这是过去大家对中心化的信任。现在,大家都担心村长会偷偷挪用大家的钱,怎么办呢?于是大家就给每个人都发了一本账本,任何人之间转账都通过大喇叭发布消息,收到消息后,每个人都在自家的账本上记下这笔交易,这就是去中心化。有了分布式账本,即使老张或老李家的账本丢了也没关系,因为老赵、老马等其他家都有账本。

后来,村民觉得每个人都记太麻烦了,可以每天轮流换人来记,谁来干这个活给谁一定的奖励。这样大家都抢着来记于是大家约定,谁摇骰子先摇到6了,就可以来记,但是记帐的格式有要求,后一笔帐的开头必须带上前一笔帐的一个标识,这样防止作假帐,并且记完帐要向大家公布出来,让大家能公开的查账。

这个村子自从采用了这种新的记账方式以后,每个人都很开心,不用担心假账、赖账的事情发生,记账还能拿到奖励,于是周围的村子也纷纷效仿这种方式。

其实区块链技术没有想象中的那么神秘,它的运行方式和运行原理就像例子中所说的村里的记账方式。我们通过这个例子,可以比较生动地理解区块链本身的运转模式。

2.2 比特币和区块链的关系

说到区块链就必然会提到比特币,从去年开始,比特币一路飙升,连带其它数字加密货币一起火爆起来,使得数字加密货币市场风头一度盖过股票、外汇、黄金等市场。那么比特币又是什么呢?区块链和比特币之间有什么关系呢?其实比特币就是世界上第一个使用区块链技术的应用。

2008年的金融危机几乎席卷了整个世界,2008年年底,一个叫中本聪的人发表了一篇论文《比特币:一种点对点的电子现金系统》,他通过这篇论文阐述了一些观点以及对金融危机后的思考。这篇论文主要提出了一个系统设计:

通过随机散列(hashing)对全部交易加上时间戳(timestamps),将它们合并入一个不断延伸的基于随机散列的工作量证明(proof-of-work)的链条作为交易记录,除非重新完成全部的工作量证明,形成的交易记录将不可更改。最长的链条不仅将作为被观察到的事件序列(sequence)的证明,而且被看做是来自CPU计算能力最大的池(pool)。只要大多数的CPU计算能力都没有打算合作起来对全网进行攻击,那么诚实的节点将会生成最长的、超过攻击者的链条。

(也就是说如果想要攻击,必须掌握超过半数以上也就是51%的节点的算力,就是我们常说的51%攻击,要做到是非常难的,攻击的成本远大于攻击带来的收益。)

这个系统本身需要的基础设施非常少。信息尽最大努力在全网传播即可,节点(nodes)可以随时离开和重新加入网络,并将最长的工作量证明链条作为在该节点离线期间发生的交易的证明。

这个系统有一个非常完善的经营模型来确保系统的正常运转以及不受黑客攻击。

比特币的数据结构

image

从图上来看,一个区块是由区块头和区块体构成的,区块头中有前一个区块头的哈希值和梅克尔根,每个区块头中的梅克尔根关联了区块中众多的交易事务,而每个区块之间又是通过区块头的哈希值也就是它的散列值关联起来的。

可以看出来这是很有意思的数据格式,它将连续不断发生的数据分成了一个个的数据块,在下载同步这些数据的时候可以并行地从各个节点来获得。不管数据先后,到达本地后再根据数据块的标识组装起来就行。另外,这是一种链式的结构,链式结构的最大特点是一环扣一环,很难从中破坏。

简单介绍一下梅克尔树,如上图右下方树形的数据结构。比特币中的梅克尔树称为二叉梅克尔树,它是通过区块中的交易事务哈希值两两结对计算出的新的哈希值。交易事务哈希值两两结对后再进行一段递归循环,直到计算出最后一个根哈希值,这样的一棵树就是梅克尔树。

为什么要用梅克尔树这样的数据结构来存储交易的事务数据呢?因为比特币系统是分布式的网络结构,当一个节点需要同步自己的区块链账本数据时,并没有一个明确的服务器来下载数据,而是通过与其他节点的通信来实现的。在下载区块数据的时候,难免会有某部分数据可能会发生损坏,所以对于一条条的交易事务就可以用梅克尔树这种形式来进行验证。

后来人们通过研究比特币的白皮书和它的源代码,提炼出一系列背后的思想及技术,并将提炼出来的结果称之为区块链。区块链技术不仅仅能实现数字货币,还能基于这个技术实现各种各样的应用。因此比特币是区块链技术的一种实践方式,区块链是比特币的技术基础,这就是比特币和区块链两者之间的关系。

现在的比特币是一个家族,包括BTC 、BCC、BTG等,还出现了类似于比特币的各种加密货币。

为什么现在有比特现金、比特黄金、莱特币这些币?这要从扩容说起:比特币本身的区块容量限定为1M,随着时间的推移,运行速度越来越慢,因为1M的容量是在是太小了,一个区块只能容纳有限的交易事务,网络中等待确认的交易会排很长的队伍,这时就需要进行扩容。

所谓的区块链扩容主要是指增加区块容纳交易事务的能力,通常有两种做法:一是增加许可空间的大小,二是缩小交易数据的尺寸。

第一种做法符合一般思维,相当于我们的衣服越来越多,一个衣柜放不下,就多买了几个衣柜;第二种做法的意思是,比如还是之前的例子,衣服越来越多,我们在不增加衣柜的情况下,把衣服好好地叠一叠压缩打包起来,这样同样的空间就能容纳更多的衣服。第二种做法引申出来一个概念:segregated witness隔离验证。

隔离验证

比特币的交易数据结构是通过发起者签署自己的UTXO(未花费的交易输出),然后填上接收者的地址建立起来的。这个交易的过程就像签署一张支票,这张支票就相当于一条比特币的交易事务,签署UTXO相当于给支票签名,也就是所谓的验证,这个签名就是用来确认支票的合法性,然后我们可以看到支票上的关键内容:发起方的签名、接收方的地址以及要支付的金额。

那怎么来确定支票数据的唯一性或完整性呢?在比特币中对每一条交易事务进行一次哈希计算得到一个事务ID,在计算这个事务ID的过程中,整条交易事务包括前面所说的签名都参与进去。签名信息的主要作用就是验证交易数据来源的合法性。实际上验证的过程也就是交易的过程,只需要进行一次也就是计算一次就可以了。矿工负责验证交易是否得到合法授权,而其他普通节点只关心接收的结果,这部分切分数据对于普通节点来说其实没有太大的用处,可以在接收的时候把这部分数据丢弃掉,然后起到一个节省资源的目的,这种将验证信息与交易数据分离的思想就是隔离验证。

分叉

而分叉是分布式共识的副产物,只要两个矿工几乎同时发现区块就会发生分叉。当后续区块添加到其中一个区块,这种不确定性就会消失,使这个链最长;另一个区块则被网络“孤立”或“抛弃”。

分叉又分为硬分叉和软分叉。

硬分叉是软件升级,将不兼容旧软件的新规则引入网络。你可以将它看作规则的延伸(使区块大小为2MB,而不是1MB的新规则将需要硬分叉)。

分叉后,继续运行旧版软件的节点将发现新交易是无效的。因此为了切换到新链继续挖有效区块,所有网络节点必须升级为新规则。

当出现某种政治僵局,社区部分人坚持旧规则,就会出现问题。旧链的哈希率、网络算力将变得不合时宜。重要的是,旧链的数据和规则仍被看作具备价值,矿工当然希望继续挖矿,开发者也希望继续支持它。

比特币的第一次硬分叉是在2017年8月1日,产生了比特现金,当时也有大量的矿工和社区支持,比特现金的市值一度超过了ETH,成了一个主流的数字货币。在此之后,比特币的分叉就一发不可收拾了,出现了各种币,但从走势来看,这些分叉币如果没有很好的技术支撑的话,并没有那么多人支持,这主要取决于市场的走势以及它能不能给矿工带来收益。

软分叉是强化一些规则。因此新规则可能否定1MB的区块,而支持500K的区块。软分叉是向后兼容的。

没升级的节点会继续将新交易视为有效的。然而未升级节点继续挖出的区块将被升级节点拒绝。因此软分叉需要网络的大部分算力。

如果软分叉获得少数哈希算力支持,可能变成最短的链,然后被网络鼓励。或者它可以向硬分叉一样分离出来,单独运行。

软分叉是升级比特币的常用方法,因为它使网络分裂的风险被认为是较低的。过去成功的软分叉包括BIP 66软件升级(涉及签名验证)、P2SH(修改比特币地址格式)。

比特币比较重要的一次升级是2012年,当时升级了一个P2SH机制,被社区称之为多重签名软分叉,这次升级修改了比特币的一个数据结构中的锁定脚本字段,使得比特币比较方便地通过多重签名的方式来发送交易。

除了修改数据结构以外,软分叉还能定向地修改比特币的交易历史。举个例子,2010年8月有一个黑客利用比特币“没有禁止输出值为负值”这个漏洞,在74638这个高度的区块上刷出了一笔包含1844个比特币的交易,半天后开发人员发现了这个事故,紧急发布了一个补丁:将一类负值输出的交易定义为无效,利用软分叉的特性将这个补丁发布出去修补了漏洞。

因为硬分叉以及扩容,所以会有比特现金、比特黄金等分叉币,因为一开始的扩容问题,所以莱特币等山寨币在一开始就设计了8M的区块大小,而运行机制几乎与比特币是一样的。

image

2

什么是以太坊

2.1 区块链1.0——数字加密货币

以前我们常说的web1.0、web2.0、移动互联网等概念,那么区块链1.0,区块链2.0乃至区块链3.0又是什么呢?区块链1.0是数字加密货币。

image

上图是数字加密货币的结构,从整体来看,一个区块链系统分为节点后台和前端两大部分,前端包含多种表现形式,比如钱包、命令行接口、区块链浏览器、图形化界面开发工具等。

我们接触得比较多的就是各种各样的钱包,钱包的主要作用是保管账户或者说保管私钥,在交易的时候进行签名,发送交易验证等。

节点后台包含着系统最核心的功能,包括共识机制、邻节点的管理、区块链管理、交易验证、内存管理、脚本引擎等。节点可以安装在各式各样的计算机或设备中,专门用来挖矿的设备叫矿机。

2.2 区块链2.0 可编程区块链

接下来一起看看区块链2.0。区块链2.0是创建去中心化的程序、自治组织和智能合约的一个系统。

和1.0不同的是2.0提供了一个平台,可以在平台上搭建应用。最大的代表是以太坊,提供了各种模块,让用户可以在其基础上搭建应用,这个应用本质上就是智能合约。

除了构建货币体系之外,智能合约可以用来做股票债券期货贷款产权智能财产等功能。智能合约的核心是利用程序算法替代人去执行合同,这些合同需要自动化的资产过程。合约包含三个基本要素:要约、承诺和价值交换。智能合约有效定义了新的应用形式,使得区块链从最初的货币体系拓展到金融以及其他行业的应用。

image

如图,我们来看看2.0的内部结构,和1.0相比,2.0也是分为前端应用和后台节点两大部分。前端应用大同小异,不同的是,在区块链2.0中有一个智能合约开发工具,这个开发工具是一个基于web的应用,它在智能合约的开发中被广泛使用。

节点后台最大的变化也是加入了智能合约这一模块以及EVM虚拟机。EVM是用来执行智能合约的虚拟机,它类似于Java的虚拟机,可以执行智能合约编译后的字节码。

以太坊目前支持三种编程语言:solidity、serpent、LLL。Solidity比较主流,除了金融类的应用,solidity还被广泛应用在游戏的表现上,比如去年流行的加密猫就是用solidity开发的。

除此以外,以太坊还加入了数据库模块,数据库模块是存储以太坊的事件状态的,以太坊的区块并不存储所有的交易信息,而只存交易的哈希值。关于以太坊的存储结构,我们会在后面的章节中详细介绍。

2.3 区块链3.0 区块链在金融行业之外的各行业的应用场景

上面介绍了区块链1.0和区块链2.0的特征,那么区块链3.0是什么呢?

区块链3.0:区块链3.0是指区块链在金融行业之外的各行业的应用场景。能够满足更加复杂的商业逻辑。区块链3.0被称为互联网技术之后的新一代技术创新,足以推动更大的产业改革。

2.4 什么是以太坊

以太坊的概念首次在2013至2014年间由程序员Vitalik Buterin,受比特币启发后提出,大意为“下一代加密货币与去中心化应用平台”,在2014年通过ICO众筹开始得以发展。

与其它区块链一样,以太坊需要几千人在自己的计算机上运行一个软件,为该网络提供动力。网络中的每个节点(计算机)运行一个叫做以太坊虚拟机(EVM)的软件。将以太坊虚拟机想象成一个操作系统,它能理解并执行通过以太坊特定编程语言编写的软件。由以太坊虚拟机执行的软件/应用程序被称为“智能合约”。

要在这一世界计算机上做任何事都需付费。不过,付的不是美元或英镑等普通货币,而是该网络自带的加密货币,叫做以太币。以太币与比特币大致相同,除了一点,即以太币可以为在以太坊上执行智能合约而付费。

可以换个方式来理解,就像现在的云计算一样,比如我们要做虚拟机需要付费,以太坊可以理解成另外一种形式的云,我们在它上面跑自己的应用或服务业需要用它自己的货币来支付费用。

image

以太坊不像比特币那样只是一种加密货币,它还存在其它特征,使其成为了一个巨大的分布式计算机。但是,它运行起来极其缓慢——比如今的普通计算机的运行速度缓慢约5至100倍——而且成本很高。那么为什么这个看起来不怎么样的计算机系统能火遍全球呢?

在以太坊上,无论是人还是智能合约都可作为用户。人类用户能做的事,智能合约也能做,而且还远不止如此。以太坊提供了一个内部的图灵完备的脚本语言以供用户来构建任何可以精确定义的智能合约或交易类型。想建立一个全规模的守护程序(Daemon)或天网(Skynet),你可能需要几千个联锁合约并且确定慷慨地喂养它们,一切皆有可能。

2.5智能合约

那什么是智能合约呢?

想象一下,我们俩关于明天的天气打个赌。我赌明天天晴,你赌明天下雨。我们约定输家必须给赢家100美元。我们如何打这个赌,还要确保输家会履行诺言呢?我能想出以下三种不同方法:

  • 互相信任

最简单的方法是互相信任。如果我们已经是老朋友了,很容易信任对方。我知道你的家庭住址而你知道我的黑历史。然而,如果我们是陌生人的话,那就难办多了。你没理由信任我,我也没理由信任你。

  • 签署法定合同

另一个可行的方法是根据我们之间的赌约制定一份法定合同。我们双方会签署一份详细规定了赌约条款的合同——包括关于输家违约的规定。该合同会让我们有向赢家支付赌金的法律义务,却不具实用性。因为如果通过法律途径强迫对方履行合同,其代价高出赌金本身。

  • 寻求共同朋友的帮助

我们可以找一个双方都信任的共同朋友,各交100美元在他/她那里保管。第二天,他/她会查看天气情况,将这200美元都交给赢家。这种方式简单明了,除非出现一种情况:要是这位朋友卷款而逃该怎么办呢?

image

以太坊的智能合约在这种情况下就可以派上用场了。智能合约就像是寻求共同朋友的帮助,不过是被编写成了代码。通过以太坊,我们可以编写一款软件,向两方各收取价值100美元的以太币。第二天打开接入天气应用的API查看天气情况,并将总价值为200美元的以太币转给赢家。因为区块链的特性,智能合约一旦完成,无论如何都无法被编辑或修改。因此,可以肯定的是不管合约中有何规定,无论如何都会被执行。

2.6 智能合约的原理

image

本质上来说,智能合约也是一条以太坊交易。不管智能合约于何时执行,它都记录了在区块上执行的交易的信息。上图描述了以太坊的交易,它分为几个字段:时间戳、发送方、接收方、账户发送的金额、data。在这里发送方就是调用者,接收方是智能合约的地址,data就是智能合约要调用的内容,也就是俗称的API,DATA字段,赋予了以太坊独特的能力,用于创建记录和执行智能合约。

通常来说以太坊的交易分为三种:

第一种是人类用户之间的互相转账,如下图所示,这个时候 Data字段是没有内容的,发送者和接收者都是人类。

image

第二种是无接收方的以太币转账。在进行没有接收方的交易时,这就意味着该交易的目的是在网络中利用“数据”项的内容创建一个智能合约。“数据”项中包含软件代码,该代码会像网络中的其它用户一样进行操作。

image

第三种是用户和智能合约之间的以太币转账。无论用户(或智能合约)何时想要执行智能合约,他/她/它需要与智能合约进行一次交易,将执行指令置于“数据”项中。

image

2.6.1 以太坊的账户

在以太坊中有两种账号共享地址空间:外部账号和合约账号。外部账号是由公钥和私钥控制的(如人),合约账号是由账号存储的代码所控制。

无论账户类型是什么都存在这四个组成部分:Nonce、Balance、StorageRoot、CodeHash。

外部账号的地址是由公钥决定的,而合约地址是在智能合约被创建的时候决定的(这个地址由创建者的地址和发送方发送过来的交易数字衍生而来,这个数字通常被叫做“nonce”)不管是否账号存有代码(合约账号存储了代码,而外部账号没有),对于EVM来说这两种账号是相等的。每一个账号都有持久化存储一个key和value长度都为256位字的键值对,被称为“storage”。而且,在以太坊中,每个账号都有一个余额(确切的是用“Wei”来作为基本单位),该余额可以被发送方发送过来带有以太币的交易所更改。

2.6.2 交易

交易的本质是一个账户和另一个账户之间的信息交换,它包含了二进制数据(也就是消费数据)和以太数据。如果目标账号包含了代码,这个代码一旦被执行,那么它的消费数据就会作为一个输入数据。如果目标账号是一个0账号(地址为0的账号),交易会生成一个新的合约。

image

image

上图中红框的内容就是一个新的合约所编译出来的字符串。

这个合约的地址不为0,但是是来源于发送方,之后这个账号的交易数据会被发送。这个合约消费会被编译为EVM的二进制代码,并执行。这次的执行会被作为这个合约的代码持久化。这就是说:为了创建一个合约,你不需要发送真正的代码到这个合约上,事实上是代码的返回作为合约代码。

2.6.3 Gas

以太坊上的每笔进行一笔交易都会被收取一定数量的Gas.这是为了限制交易的数量,同时对每一笔交易的进行支付额外费用。当EVM执行一个交易,交易发起方就会根据定义的规则消耗对应的Gas。交易的创造者定义了的Gas 价格。所以交易发起方每次需要支付 gas_price * gas 。如果有gas在执行后有剩余,会以同样的方法返回给交易发起方。如果gas在任何时候消耗完,out-of-gas 异常会被抛出,那当前的这边交易所执行的后的状态全部会被回滚到初始状态。

这是一个保护机制,用来保护以太坊不会受到恶意攻击,让整个网络中一些恶意的程序无法进行。这里又可以看出区块链是用经济模型的方式,或者说用经济的方式来限制黑客攻击的。黑客并不是不能用技术手段来进行恶意攻击,但是他要付出的代价成本远大于获得的价值。

2.6.4 以太坊虚拟机的内存结构

  • 内存、主存和栈

以太坊每个账号都有持久化的内存空间叫做存储. 存储是一个key和value长度都为256位的key-value键值对。从一个合约里列举存储是不大可能的。读取存储里的内容是需要一定的代价的,修改storage里的内容代价则会更大。一个合约只能读取或是修改自己的存储内容。

以太坊的第二内存区域叫做主存。系统会为每个消息的调用分配一个新的,被清空的主存空间。主存是线性并且以字节粒度寻址。读的粒度为32字节(256位),写可以是1个字节(8位)或是32个字节(256字节)。当访问一个字(256位)内存时,主存会按照字的大小来扩展。主存扩展时候,消耗Gas也必须要支付,主存的开销会随着其增长而增大(指数增长)。

EVM不是一个基于寄存器,而是基于栈的。所以所有的计算都是在栈中执行。最大的size为1024个元素,每个元素为256位的字。栈的访问限于顶端,按照如下方式:允许拷贝最上面的16个元素中的一个到栈顶或是栈顶和它下面的16个元素中的一个进行交换。所有其他操作会从栈中取出两个(有可能是1个,多个,取决于操作)元素,把操作结果在放回栈中。当然也有可能把栈中元素放入到存储或是主存中,但是不可能在没有移除上层元素的时候,随意访问下层元素。

2.6.5 指令集

为了避免错误的实现而导致的一致性问题,EVM的指令集保留最小集合。所有的指令操作都是基于256位的字。包含有常用的算术,位操作,逻辑操作和比较操作。条件跳转或是非条件跳转都是允许的。而且合约可以访问当前区块的相关属性比如编号和时间戳。

2.6.6 消息调用

合约可以通过消息调用来实现调用其他合约或是发送以太币到非合约账号。消息调用和交易类似,他们都有一个源,一个目标,数据负载,以太币,gas和返回的数据。事实上,每个交易都包含有一个顶层消息调用,这个顶层消息可以依次创建更多的消息调用。

一个合约可以定义内部消息调用需要消耗多少gas,多少gas需要被保留。如果在内部消息调用中出现out-of-gas异常,合约会被通知,会在栈里用一个错误值来标记。这种情况只是这次调用的gas被消耗完。在Solidity,这种情况下调用合约会引起一个人为异常,这种异常会抛出栈的信息。

上面提到,调用合约会被分配到一个新的,并且是清空的主存,并能访问调用的负载。调用负载时被称为calldata的一个独立区域。调用结束后,返回一个存储在调用主存空间里的数据。这个存储空间是被调用者预先分配好的。调用限制的深度为1024.对于更加复杂的操作,我们更倾向于使用循环而不是递归。

2.6.7 代理调用/ 代码调用和库

存在一种特殊的消息调用,叫做代理调用。除了目标地址的代码在调用方的上下文中被执行,而且msg.sender和msg.value不会改变他们的值,其他都和消息调用一样。这就意味着合约可以在运行时动态的加载其他地址的代码。存储,当前地址,余额都和调用合约有关系。只有代码是从被调用方中获取。这就使得我们可以在Solidity中使用库。比如为了实现复杂的数据结构,可重用的代码可以应用于合约存储中。

2.6.8日志

我们可以把数据存储在一个特殊索引的数据结构中。这个结构映射到区块层面的各个地方。为了实现这个事件,在Solidity把这个特性称为日志。合约在被创建出来后是不可以访问日志数据的。但是他们可以从区块链外面有效的访问这些数据。因为日志的部分数据是存储在bloom filters上。我们可以用有效并且安全加密的方式来查询这些数据。即使不用下载整个区块链数据(轻客户端)也能找到这些日志。

2.6.9创建

合约可以通过特殊的指令来创建其他合约。这些创建调用指令和普通的消息调用唯一区别是:负载数据被执行,结果作为代码被存储,调用者在栈里收到了新合约的地址。

2.6.10 自毁

从区块链中移除代码的唯一方法是合约在它的地址上执行了selfdestruct操作。这个账号下剩余的以太币会发送给指定的目标,存储和代码从栈中删除。

3

附:基于solidity的实例PPT

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

内容来源:HiBlock区块链课堂011期 尚涛老师的线上分享《区块链技术与智能合约入门

本文编辑:Cynthia

点击“阅读原文”即可回听课程分享。

以下是我们的社区介绍,欢迎各种合作、交流、学习:)

image

猜你喜欢

转载自blog.csdn.net/HiBlock/article/details/81369313