【译】A Practical Introduction to Blockchain with Python

区块链可以说是互联网自成立以来最重要和最具颠覆性的技术之一。 这是比特币和其他加密货币背后的核心技术,在过去几年引起了很多关注。

作为其核心,区块链是一个分布式数据库,允许双方直接交易,而无需中央机构。 这个简单而强大的概念对银行,政府和市场等各种机构具有重大意义,仅举几例。 任何依赖中央数据库作为核心竞争优势的企业或组织都可能受到区块链技术的干扰。

抛开所有关于比特币和其他加密货币价格的炒作,本博文的目标是给你一个区块链技术的实用介绍。 第1节和第2节介绍了区块链背后的一些核心概念,第3节介绍了如何使用Python实现区块链。 我们还将实施2个Web应用程序,以方便最终用户与我们的区块链互动。

请注意,我在此使用比特币作为解释“区块链”更一般技术的媒介,本文中描述的大多数概念都适用于其他区块链使用案例和加密货币。

以下是我们将在第3节中构建的两个Web应用程序的动画gif。

1.区块链中的崩溃课程

这一切都始于2008年由一个不知名的人或实体使用Satoshi Nakamoto这个名字发布的白皮书。 该白皮书的标题是“比特币:对等电子现金系统” ,它奠定了后来被称为区块链的基础。 在最初的比特币白皮书中,Satoshi描述了如何建立点对点电子现金系统,使得在线支付可以直接从一方发送到另一方,而无需通过中央机构。 该系统解决了数字货币中称为双重支出的重要问题。

1.1。 什么是双重支出?

假设Alice想支付Bob 1 $。 如果Alice和Bob使用实物现金,那么在交易执行后Alice将不再拥有1美元。 如果Alice和Bob使用数字货币,那么问题变得更加复杂。 数字货币是数字形式,可以很容易地复制。 如果Alice通过电子邮件向Bob发送一个价值1美元的数字文件,Bob无法确定Alice是否删除了她的文件副本。 如果Alice仍然有1 $数字文件,那么她可以选择将同一个文件发送给Carol。 这个问题叫做双重支出。

替代文字

解决双重支出问题的一种方法是在Alice,Bob和网络中的所有其他参与者之间建立一个可信的第三方(例如银行)。 该第三方负责管理集中分类帐,用于跟踪和验证网络中的所有交易。 这个解决方案的缺点是要使系统正常工作,它需要信任一个集中的第三方。

1.2。 比特币:双重支出问题的分散解决方案

为了解决双重支出问题,Satoshi提出了一个公共分类账,即比特币的区块链来跟踪网络中的所有交易。 比特币的区块链具有以下特点:

  • 分布式:分类账在多台计算机上复制,而不是存储在中央服务器上。 任何具有互联网连接的计算机都可以下载区块链的完整副本。
  • 密码:密码术用于确保发件人拥有她试图发送的比特币,并决定如何将交易添加到区块链中。
  • 不可变:区块链可以以追加方式更改。 换句话说,交易只能添加到区块链中,但不能删除或修改。
  • 使用工作证明(PoW):网络中一种特殊类型的参与者称为矿工竞争寻找加密难题的解决方案,这将使他们能够在比特币区块链中添加大量交易。 这个过程称为工作证明(Proof of Work),它允许系统安全(稍后会介绍这一点)。

替代文字

发送比特币钱如下:

  • 步骤1(一次性努力):创建一个比特币钱包。 对于一个人发送或接收比特币,她需要创建一个比特币钱包。 比特币钱包存储2条信息:私钥和公钥。 私钥是一个秘密号码,允许所有者向其他用户发送比特币,或者将比特币用于接受他们的服务作为付款方式。 公钥是接收比特币所需的数字。 公钥也被称为比特币地址(不完全正确,但为了简单起见,我们将假定公钥和比特币地址相同)。 请注意,钱包本身不存储比特币。 关于比特币余额的信息存储在比特币区块链中。
  • 第2步:创建一个比特币交易。 如果Alice想要给Bob发送1 BTC,Alice需要使用她的私钥连接到她的比特币钱包,并创建一个包含她想要发送的比特币数量和她要发送的地址的交易(在这种情况下鲍勃的公共地址)。
  • 第3步:将交易广播至比特币网络。 一旦Alice创建比特币交易,她需要将此交易广播到整个比特币网络。
  • 第4步:确认交易。 一位收听比特币网络的矿工使用Alice的公钥验证交易,证实Alice的钱包中有足够的比特币(在这种情况下至少为1 BTC),并在比特币的区块链中增加了一个新记录,其中包含交易详情。
  • 第5步:将区块链更改广播给所有矿工。 交易确认后,矿工应该向所有矿工广播区块链更改,以确保区块链的副本全部同步。

2.区块链技术深入研究

本部分的目标是深入介绍区块链技术构建模块。 我们将介绍公钥密码学,散列函数,区块链的挖掘和安全性。

2.1。 公钥加密

公钥密码学或不对称密码学是使用密钥对的任何密码系统:可以广泛传播的公钥和只有所有者知道的私钥。 这实现了两个功能:认证,其中公钥验证发送消息的配对私钥的持有者和加密,其中只有配对私钥持有者可以解密用公钥加密的消息。 [1]

RSA椭圆曲线数字签名(ECDSA)是最流行的公钥密码算法。

就比特币而言,ECDSA算法用于生成比特币钱包。 比特币使用各种密钥和地址,但为了简单起见,我们将在本博文中假设每个比特币钱包都有一对私钥/公钥,比特币地址就是钱包的公钥。 如果您对比特币钱包的完整技术细节感兴趣,我推荐这篇文章 。

要发送或接收BTC,用户首先生成一个包含一对私钥和公钥的钱包。 如果Alice想要给Bob发送一些BTC,她创建一个交易,在该交易中她输入她和Bob的公钥,以及她想要发送的BTC数量。 她然后使用她的私钥签署交易。 区块链中的计算机使用Alice的公钥来验证交易是否真实,并将交易添加到稍后将添加到区块链的区块。

替代文字

2.2。 散列函数和挖掘

所有比特币交易都分组在称为块的文件中。 比特币每10分钟添加一个新的交易块。 一旦新块被添加到区块链中,它就变得不可变,并且不能被删除或修改。 网络中一个特殊的参与者称为矿工(连接到区块链的计算机)负责创建新的交易块。 矿工必须使用发件人的公钥验证每笔交易,确认发件人有足够的余额用于所请求的交易,并将交易添加到该块。 矿工们可以完全自由地选择块中包含哪些交易,因此发件人需要包含交易费以激励矿工将其交易添加到块中。

对于被区块链接受的区块,它需要被“挖掘”。 为了挖掘一个块,矿工需要找到一个极其罕见的解决密码难题的方法。 如果区块链接受了开采区块,矿工将获得比特币奖励,这是对交易费用的额外激励。 挖掘过程也被称为工作量证明(PoW),它是使区块链变得可靠和安全的主要机制(更多关于区块链安全性)。

哈希和区块链的密码拼图

要理解区块链的加密难题,我们需要从哈希函数开始。 哈希函数是可用于将任意大小的数据映射到固定大小的数据的任何函数。 散列函数返回的值称为散列。 散列函数通常用于通过检测重复记录来加速数据库查找,并且它们也广泛用于密码学。 密码散列函数允许人们容易地验证某些输入数据映射到给定的散列值,但是如果输入数据是未知的,则通过知道存储的散列值来重建它是故意困难的。 [2]

比特币使用称为SHA-256的加密散列函数。 SHA-256应用于块数据(比特币交易)和一个称为nonce的数字的组合。 通过更改块数据或随机数,我们可以得到完全不同的哈希值。 对于被认为有效或“挖掘”的块,块的散列值和随机数需要满足一定的条件。 例如,散列的四位前导数字需要等于“0000”。 我们可以通过使条件更复杂来增加挖掘的复杂性,例如我们可以增加散列值开始所需的0的数量。

矿工需要解决的cryptograhic难题是找到一个随机数值,使得哈希值满足挖掘条件。 您可以使用下面的应用程序来模拟块挖掘。 当您输入“数据”文本框或更改随机数值时,您可以注意到哈希值的变化。 当你点击“我的”按钮时,应用程序以nonce等于零开始,计算散列值并检查散列值的前四位数是否等于“0000”。 如果前四位数字不等于“0000”,它将随机数加1,并重复整个过程直到找到一个满足条件的随机数值。 如果该块被视为开采,背景颜色将变成绿色。

随机数:
数据:
哈希:

2.3。 从块到区块链

正如前面部分所讨论的,交易以块形式分组,并且块被附加到区块链。 为了创建块链,每个新块都使用前一个块的散列作为其数据的一部分。 为了创建一个新块,矿工选择一组事务,添加上一个块的散列并以上述类似的方式挖掘块。

对任何块中的数据所做的任何更改都会影响到它后面的块的所有散列值,并且它们将变为无效。 这给区块链带来了不变性。

替代文字

您可以使用下面的应用程序来模拟3块的区块链。 当您输入“Data”文本框或更改nonce值时,可以注意到下一个块的散列值和“Prev”值(前一个散列)的更改。 您可以通过单击每个块的“Mine”按钮来模拟采矿过程。 在挖掘3个块之后,尝试更改块1或块2中的数据,并且您会注意到后面的所有块都变为无效。

块:
随机数:
数据:
上一篇:
哈希:

块:
随机数:
数据:
上一篇:
哈希:

块:
随机数:
数据:
上一篇:
哈希:

上述两款采矿模拟器都是根据Anders Brownworth出色的Blockchain Demo改编的。

2.4。 将区块添加到区块链

比特币网络中的所有矿工相互竞争,找到一个有效的区块,并将其添加到区块链中,并从网络中获得奖励。 发现一个验证块的随机数很少见,但由于矿工人数众多,网络中的矿工验证块的可能性非常高。 第一位提交有效街区的矿工将他的街区加入区块链,并获得比特币奖励。 但是,如果两名矿工或更多的同时递交他们的块,会发生什么?

解决冲突

如果两个矿工几乎同时解决一个块,那么我们将在网络中有两个不同的块链,我们需要等待下一个块来解决冲突。 一些矿工将决定在区块链1和区块链2顶部的其他矿区之上开采矿石。第一个找到新区块的矿工将解决冲突。 如果新区块在区块链1的顶部被开采,则区块链2变为无效,前区块的奖励从区块链1进入矿工,并且区块链2的一部分并且未被添加到区块链的交易返回到事务池并添加到下一个块。 简而言之,如果区块链上存在冲突,那么最长的连锁赢家。

替代文字

2.5。 区块链和双重支出

在本节中,我们将介绍对区块链执行双重支出攻击的最常见方式,以及用户应采取哪些措施来防止他们遭受损失。

种族攻击

攻击者快速连续发送同一枚硬币到两个不同的地址。 为了防止这种攻击,建议在接受付款之前等待至少一个数据块确认。 [3]

芬尼攻击

攻击者预先在事务中挖掘一个块,并在释放块之前在第二个事务中花费相同的硬币。 在这种情况下,第二个事务将不会被验证。 为了防止这种攻击,建议在接受付款之前至少等待6个数据块确认。 [3]

多数人攻击(也被称为51%攻击)

在这次攻击中,攻击者拥有51%的网络计算能力。 攻击者首先在整个网络上创建一个交易,然后挖掘一个私人区块链,在那里他将前一个交易的硬币加倍。 由于攻击者拥有大部分计算能力,因此他可以保证他在某个时间点的链接比“诚实”的网络要长。 然后,他可以发布他的较长的区块链,取代“诚实”区块链并取消原始交易。 这种攻击极不可能,因为它在像比特币这样的区块链网络中非常昂贵。 [4]

3. Python中的区块链实现

在本节中,我们将使用Python实现基本区块链和区块链客户端。 我们的区块链将具有以下功能:

  • 将多个节点添加到区块链的可能性
  • 工作证明(PoW)
  • 节点之间的简单冲突解决
  • 使用RSA加密的交易

我们的区块链客户端将具有以下功能:

  • 使用公钥/私钥加密(基于RSA算法)生成钱包
  • 使用RSA加密生成事务

我们还将实施2个仪表板:

  • 面向矿工的“Blockchain Frontend”
  • “区块链客户端”让用户生成钱包并发送硬币

区块链实施主要基于这个github项目 。 我对原始代码进行了一些修改,以便为事务添加RSA加密。 钱包生成和交易加密基于Jupyter笔记本 。 2个仪表板从头开始使用HTML / CSS / JS实现。

您可以从https://github.com/adilmoujahid/blockchain-python-tutorial下载完整的源代码。

请注意,此实现仅用于教育目的,不应用于生产,因为它没有很好的安全性,不能很好地扩展并缺少许多重要功能。

3.1。 区块链客户端实施

您可以通过访问blockchain_client文件夹并键入python blockchain_client.py来从终端启动区块链客户端。 在浏览器中,转至http://localhost:8080 ,您将看到下面的仪表板。


仪表板在导航栏中有3个选项卡:

  • 钱包生成器:使用RSA加密算法生成钱包(公钥/私钥对)
  • 进行交易:生成交易并将其发送到区块链节点
  • 查看转换:查看区块链上的交易

为了制作或查看交易,您至少需要运行一个区块链节点(将在下一节中介绍)。

以下是blockchain_client.py代码中最重要部分的一些说明。

我们定义了一个名为Transaction的python类,它具有4个属性sender_address , sender_private_key , recipient_address , value 。 这些是发件人创建交易所需的4条信息。

to_dict()方法以Python字典格式(没有发件人的私钥)返回交易信息。 sign_transaction()方法获取交易信息(不包括发件人的私钥),并使用发件人的私钥对其进行签名。

   交易 

     def __init__  self  sender_address  sender_private_key  recipient_address  value ):
         自我   sender_address = sender_address
         自我   sender_private_key = sender_private_key
         自我   recipient_address = recipient_address
         自我    = 

     def __getattr__  self  attr ):
         回归 自我   数据 [ attr ]

     def to_dict  self ):
         返回 OrderedDict ({ 'sender_address'  self。sender_address 
                             'recipient_address'  自我   recipient_address 
                             '价值'  自我    })

     def sign_transaction  self ):
         “””
 用私钥签名交易
 “””
         private_key = RSA   importKey  binascii。unhexlify  self。sender_private_key ))
         签署者 = PKCS1_v1_5     private_key 
         h = SHA   new  str  self.to_dict ())  encode  'utf8' ))
         返回 binascii   hexlify  签字人 签字  h ))   解码  'ascii' 

下面的代码启动一个Python Flask应用程序,我们将使用它来创建不同的API来与区块链及其客户端进行交互。

  app = Flask  __name__ 

下面我们定义返回html页面的3个Flask路由。 每个标签页都有一个html页面。

  @ app.route  '/' 
 def index ():
   返回 render_template  './index.html' 

 @ app.route  '/ make / transaction' 
 def make_transaction ():
     返回 render_template  './make_transaction.html' 

 @ app.route  '/ view / transactions' 
 def view_transaction ():
     返回 render_template  './view_transactions.html' 

下面我们定义一个生成钱包的API(私钥/公钥对)。

  @ app.route  '/ wallet / new'  methods = [ 'GET' ])
 def new_wallet ():
   random_gen = 加密   随机   new ()   
   private_key = RSA   生成  1024  random_gen 
   public_key = private_key   publickey ()
   response = {
     'private_key'  binascii   hexlify  private_key。exportKey  format = 'DER' ))   解码  'ascii' ),
     'public_key'  binascii   hexlify  public_key。exportKey  format = 'DER' ))   解码  'ascii' 
   }

   返回 jsonify  响应 ), 200



下面我们定义一个API,它将输入sender_address , sender_private_key , recipient_address , value作为输入,并返回事务(无私钥)和签名。

  @ app.route  '/ generate / transaction'  methods = [ 'POST' ])
 def generate_transaction ():

   sender_address = 请求   表单 [ 'sender_address' ]
   sender_private_key = 请求   表单 [ 'sender_private_key' ]
   recipient_address = 请求   表单 [ 'recipient_address' ]
    = 请求   形式 [ 'amount' ]

   transaction = Transaction  sender_address  sender_private_key  recipient_address  value 

   response = { 'transaction'  交易   to_dict (), '签名'  事务   sign_transaction ()}

   返回 jsonify  响应 ), 200



3.2。 区块链实施

您可以通过转到blockchain文件夹启动区块链节点,然后输入python blockchain_client.pypython blockchain_client.py -p <PORT NUMBER> 。 如果您未指定端口号,则默认为端口5000.在浏览器中,转到http://localhost:<PORT NUMBER>以查看区块链前端仪表板。


仪表板在导航栏中有2个选项卡:

  • Mine:用于查看交易和区块链数据,以及用于挖掘新的交易区块。
  • 配置:用于配置不同区块链节点之间的连接。

以下是blockchain.py代码中最重要部分的一些说明。

我们首先定义一个具有以下属性的Blockchain类:

  • transactions :将被添加到下一个块的交易列表。
  • chain :实际的区块链,这是一个块数组。
  • nodes :包含节点URL的集合。 区块链使用这些节点从其他节点检索区块链数据,并在区块链不同步时更新区块链。
  • node_id :识别node_id链节点的随机字符串。

Blockchain类还实现了以下方法:

  • register_node(node_url) :将新的register_node(node_url)链节点添加到节点列表中。
  • verify_transaction_signature(sender_address, signature, transaction) :检查提供的签名是否与由公钥签名的事务(sender_address)相对应。
  • submit_transaction(sender_address, recipient_address, value, signature) :如果验证签名,则将事务添加到事务列表。
  • create_block(nonce, previous_hash) :向区块链添加一个交易块。
  • hash(block) :创建hash(block)的SHA-256散列。
  • proof_of_work() :工作证明算法。 寻找满足采矿条件的随机数。
  • valid_proof(transactions, last_hash, nonce, difficulty=MINING_DIFFICULTY) :检查哈希值是否满足挖掘条件。该函数在proof_of_work函数中使用。
  • valid_chain(chain) :检查链接是否有效。
  • resolve_conflicts() :通过替换网络中最长的链来解决区块链节点之间的冲突。
  class blockchain 

     def __init__  self ):

         自我   交易 = []
         自我   chain = []
         自我   nodes = set ()
         #生成用作node_id的随机数
         自我   node_id = str  uuid4 ())   替换  ' - '  '' 
         #创建基因块
         自我   create_block  0  '00' 

     def register_node  self  node_url ):
         “””
 向节点列表中添加一个新节点
 “””
         ...

     def verify_transaction_signature  self  sender_address  signature  transaction ):
         “””
 检查提供的签名是否与交易相符
 由公钥(sender_address)签名
 “””
         ...

     def submit_transaction  self  sender_address  recipient_address  value  signature ):
         “””
 如果验证签名,则将事务添加到事务数组中
 “””
         ...

     def create_block  self  nonce  previous_hash ):
         “””
 添加交易块到区块链
 “””
         ...

     def hash  self  block ):
         “””
 创建一个块的SHA-256散列
 “””
         ...

     def proof_of_work  self ):
         “””
 工作证明算法
 “””
         ...

     def valid_proof  self  transactions  last_hash  nonce  difficulty = MINING_DIFFICULTY ):
         “””
 检查哈希值是否满足挖掘条件。  该函数在proof_of_work函数中使用。
 “””
         ...

     def valid_chain  self  chain ):
         “””
 检查一个链接是否有效
 “””
         ...

     def resolve_conflicts  self ):
         “””
 解决区块链节点之间的冲突
 通过取代我们的网络中最长的链。
 “””
         ...

下面的代码启动一个Python Flask应用程序,我们将使用它来创建不同的API来与区块链进行交互。

  app = Flask  __name__ 
 CORS  应用程序 

接下来,我们启动一个Blockchain实例。

  blockchain = 区块链 ()

下面我们定义返回我们区块链前端仪表盘html页面的2个Flask路由。

  @ app.route  '/' 
 def index ():
     返回 render_template  './index.html' 

 @ app.route  '/ configure' 
 def configure ():
     返回 render_template  './configure.html' 

下面我们定义Flask API来管理交易和挖掘区块链。

  • '/transactions/new' :这个API作为'sender_address' , 'recipient_address' , 'amount''signature' ,并将交易添加到交易列表中,如果签名有效,交易列表将被添加到下一个区块。
  • '/transactions/get' :该API返回将被添加到下一个块的所有事务。
  • '/chain' :该API返回所有区块链数据。
  • '/mine' :该API运行工作证明算法,并将新的交易块添加到区块链。
  @ app.route  '/ transactions / new'  methods = [ 'POST' ])
 def new_transaction ():
      = 请求   形成

     #检查所需字段是否在POST中
     required = [ 'sender_address'  'recipient_address'  'amount'  '签名' ]
     如果 不是 全部  k  需要的 k  ):
         返回 '缺失值'  400
     #创建一个新的交易
     transaction_result = 区块链   submit_transaction  values [ 'sender_address' ], values [ 'recipient_address' ], values [ 'amount' ], values [ 'signature' ])

     如果 transaction_result == False 
         response = { 'message'  '无效的交易!'  }
         返回 jsonify  响应 ), 406
     其他 
         response = { 'message'  '交易将被添加到Block' + str  transaction_result )}
         返回 jsonify  响应 ), 201

 @ app.route  '/ transactions / get'  methods = [ 'GET' ])
 def get_transactions ():
     #从事务池获取事务
     交易 = 区块链   交易

     response = { 'transactions'  transactions }
     返回 jsonify  响应 ), 200

 @ app.route  '/ chain'  methods = [ 'GET' ])
 def full_chain ():
     response = {
         '链'  区块链    
         'length'  len  区块链   ),
     }
     返回 jsonify  响应 ), 200

 @ app.route  '/ mine'  methods = [ 'GET' ])
 def mine ():
     #我们运行工作证明算法以获得下一个证明...
     last_block = 区块     [ - 1 ]
     nonce = 区块链   proof_of_work ()

     #我们必须获得找到证据的奖励。
     区块链   submit_transaction  sender_address = MINING_SENDER  recipient_address = blockchain。node_id  value = MINING_REWARD  signature = “” 

     #通过将新块添加到链中来锻造新块
     previous_hash = 区块链   散列  last_block 
     block = blockchain   create_block  nonce  previous_hash 

     response = {
         '消息'  “伪造的新块” 
         'block_number'  block [ 'block_number' ],
         'transactions'  block [ 'transactions' ],
         'nonce'  block [ 'nonce' ],
         'previous_hash'  block [ 'previous_hash' ],
     }
     返回 jsonify  响应 ), 200



下面我们定义Flask API来管理区块链节点。

  • '/nodes/register' :该API将节点URL列表作为输入,并将它们添加到节点列表中。
  • '/nodes/resolve' :该API通过用网络中可用的最长链替换本地链来解决区块链节点之间的冲突。
  • '/nodes/get' :该API返回节点列表。
  @ app.route  '/ nodes / register'  methods = [ 'POST' ])
 def register_nodes ():
      = 请求   形成
     节点 =    get  '节点'    替换  “”  “”    split  ',' 

     如果 节点  None 
         返回 “错误:请提供一个有效的节点列表”  400

     节点 中的 节点 
         区块链   寄存器 节点  节点 

     response = {
         'message'  '已添加新节点' 
         'total_nodes'  [ 节点  块链中的 节点   节点 ],
     }
     返回 jsonify  响应 ), 201


 @ app.route  '/ nodes / resolve'  methods = [ 'GET' ])
 def consensus ():
     取代 = 区块链   resolve_conflicts ()

     如果 更换 
         response = {
             '消息'  '我们的连锁已被替换' 
             'new_chain'  区块    
         }
     其他 
         response = {
             '消息'  '我们的连锁店是权威的' 
             '链'  区块链   
         }
     返回 jsonify  响应 ), 200


 @ app.route  '/ nodes / get'  methods = [ 'GET' ])
 def get_nodes ():
     nodes = list  blockchain。nodes 
     response = { 'nodes'  nodes }
     返回 jsonify  响应 ), 200



结论

在本篇博文中,我们介绍了区块链背后的一些核心概念,并学习了如何使用Python实现一个核心概念。 为了简单起见,我没有介绍一些技术细节,例如:钱包地址和默克尔树。 如果您想了解更多关于此主题的信息,我建议您阅读比特币原始白皮书,并跟进比特币维基和Andreas Antonopoulos出色的书籍: 掌握比特币:编程开放区块链 。

参考

http://adilmoujahid.com/posts/2018/03/intro-blockchain-bitcoin-python/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值