如何为以太坊区块链构建 Node.js API

已发表: 2022-03-10
快速总结 ↬在本文中,John Agbanusi 解释了如何通过构建和部署以太坊区块链以实现去中心化,从头开始构建 Node.js API。 他还向您展示了将 API 和区块链集成到称为“去中心化应用程序 API”的单个 API 的分步过程。

区块链技术在过去十年中一直在崛起,并带来了大量的产品和平台,例如 Chainalysis(金融科技)、Burstiq(健康科技)、Filament(物联网)、Opus(音乐流媒体)和 Ocular(网络安全)。

从这些示例中,我们可以看到区块链跨越了许多产品和用例——使其非常重要和有用。 在金融科技(金融科技)中,它被用作 Chain、Chainalysis 等地方的去中心化账本以确保安全性和透明度,并且在健康科技中也可用于保护 Burstiq 和 Robomed 中敏感健康数据的安全——不要忘记 Opus 等媒体技术和 Audius 也使用区块链来实现版税透明度,从而获得全额版税。

Ocular 使用区块链附带的安全性来进行生物识别系统的身份管理,而 Filament 使用区块链分类帐进行实时加密通信。 这表明区块链通过改善我们的生活对我们来说变得多么重要。 但究竟什么是区块链?

区块链是跨计算机网络共享的数据库。 一旦将记录添加到链中,就很难更改。 为了确保数据库的所有副本都相同,网络会不断进行检查。

那么我们为什么需要区块链呢? 与极有可能发生黑客攻击、错误和停机时间的传统记录或数据库相比,区块链是一种记录活动和保持数据新鲜的安全方式,同时保持其历史记录。 数据不会被任何人损坏或意外删除,您可以从历史数据跟踪和即时更新的记录中受益,这些记录不会因服务器停机而被删除或无法访问。

由于整个区块链在多台计算机上复制,任何用户都可以查看整个区块链。 交易或记录不是由一个中央管理员处理,而是由一个致力于验证数据并达成共识的用户网络处理。

使用区块链的应用程序称为dApps (去中心化应用程序)。 今天环顾四周,我们主要会在金融科技中找到去中心化的应用程序,但区块链超越了去中心化金融。 我们拥有健康平台、音乐流媒体/共享平台、电子商务平台、网络安全平台和物联网,这些平台正朝着上述去中心化应用程序 (dApp) 发展。

那么,什么时候考虑在我们的应用程序中使用区块链而不是标准数据库或记录才有意义呢?

跳跃后更多! 继续往下看↓

区块链的常见应用

  • 管理和保护数字关系
    任何时候你想保持资产的长期、透明记录(例如,记录财产或公寓权利),区块链可能是理想的解决方案。 尤其是以太坊的“智能合约”,非常适合促进数字关系。 使用智能合约,当交易各方同意满足他们的条件时,可以释放自动支付。
  • 消除中间人/看门人
    例如,目前大多数供应商都必须通过一个集中的聚合平台与客人互动,比如 Airbnb 或 Uber(反过来,这些平台会从每笔交易中分一杯羹)。 区块链可以改变这一切。
    例如,TUI 深信区块链的力量,它开创了直接连接酒店经营者和客户的方式。 这样,他们可以通过区块链以一种简单、安全和一致的方式进行交易,而不是通过中央预订平台。
  • 记录合作伙伴之间的安全交易以确保信任
    传统数据库可能有利于记录两方之间的简单交易,但当事情变得更加复杂时,区块链可以帮助减少瓶颈并简化关系。 更重要的是,分散系统的附加安全性使区块链成为一般交易的理想选择。
    一个例子是墨尔本大学,它开始将其记录存储在区块链中。 区块链在高等教育中最有前途的用例是转变学位、证书和文凭的“记录保存”。 这节省了大量用于存储或记录的专用服务器的成本。
  • 为数据不断变化的应用程序记录过去的操作
    区块链是一种更好、更安全的方式来记录活动并保持数据新鲜,同时保留其历史记录。 数据不会被任何人损坏或意外删除,您可以从历史数据跟踪和即时更新的记录中受益。 一个好的用例示例是电子商务中的区块链,区块链和电子商务都涉及交易。
    区块链使这些交易更安全、更快捷,而电子商务活动则依赖于它们。 区块链技术使用户能够自动和手动共享和安全存储数字资产。 该技术能够处理用户活动,例如支付处理、产品搜索、产品购买和客户服务。 它还减少了用于库存管理和支付处理的费用。
  • 去中心化可以在任何地方使用
    与以前由于货币兑换政策等各种原因我们不得不将自己限制在特定地区不同,支付网关的限制使得您难以获得许多不在您所在地区或大陆的国家的金融资源。 随着区块链去中心化或点对点系统的兴起和力量,这变得更容易与其他国家合作。
    例如,欧洲的电子商务商店可以在非洲拥有消费者,并且不需要中间人来处理他们的付款请求。 此外,这些技术正在为在线零售商打开大门,利用比特币(即加密货币)利用遥远国家的消费市场。
  • Blockhain 是技术中立的
    区块链适用于开发人员使用的所有和任何技术堆栈。 作为 Python 开发人员,您无需学习 Node 即可使用区块链或学习 Golang。 这使得区块链非常易于使用。
    实际上,我们可以将它直接与 Vue/React 中的前端应用程序一起使用,区块链作为我们唯一的数据库,用于简单不复杂的任务和用例,例如上传数据或获取哈希以显示我们的用户记录,或构建像赌场这样的前端游戏游戏和博彩游戏(需要高度信任)。 另外,借助 web3 的强大功能,我们可以直接将数据存储在链中。

现在,我们已经看到了使用区块链的很多好处,但是我们什么时候应该完全不使用区块链呢?

区块链的缺点

  • 降低数字交易的速度
    区块链需要大量的计算能力,这往往会降低数字交易的速度,尽管有一些变通方法,当需要以毫秒为单位的高速交易时,建议使用集中式数据库。
  • 数据不变性
    数据不变性一直是区块链的最大缺点之一。 很明显,多个系统都从中受益,包括供应链、金融系统等。 但是,它的缺点是数据一旦写入,就无法删除。 地球上的每一个人都有隐私权。 但是,如果同一个人使用基于区块链技术的数字平台,那么当他不希望它存在时,他将无法从系统中删除它的痕迹。 简而言之,他无法删除他的踪迹——将隐私权分割成碎片。
  • 需要专业知识
    实施和管理区块链项目很困难。 它需要全面的知识才能完成整个过程。 这就是为什么很难遇到区块链专家或专家的原因,因为培训区块链专家需要花费大量时间和精力。 因此,如果您已经开始,这篇文章是一个很好的起点和一个很好的指南。
  • 互操作性
    多个区块链网络以独特的方式努力解决分布式账本问题,这使得它们难以关联或相互集成。 这使得不同链之间的通信变得困难。
  • 旧版应用程序集成
    许多企业和应用程序仍然使用遗留系统和架构; 采用区块链技术需要对这些系统进行彻底改革,我必须说这对其中许多系统来说是不可行的。

区块链一直在不断发展和成熟,所以如果今天提到的这些缺点后来变成了专业人士,请不要感到惊讶。 作为一种加密货币的比特币是区块链的一个流行例子,除了比特币加密货币之外,一个流行的区块链正在崛起,它是以太坊区块链。 比特币专注于加密货币,而以太坊则更专注于智能合约,而智能合约一直是新技术平台的主要驱动力。

推荐阅读比特币与以太坊:有什么区别?

让我们开始构建我们的 API

对区块链有了深入的了解,现在让我们看看如何构建以太坊区块链并将其集成到 Node.js 中的标准 API 中。 最终目标是更好地了解 dApp 和区块链平台的构建方式。

大多数 dApp 具有相似的架构和结构。 基本上,我们有一个用户与 dApp 前端交互——无论是 Web 还是移动端——然后与后端 API 交互。 然后,后端应请求通过公共节点与智能合约或区块链交互; 这些要么运行 Node.js 应用程序,要么后端通过直接运行 Node.js 软件来使用区块链。 从选择构建完全去中心化的应用程序或半去中心化的应用程序到选择应该去中心化的内容以及如何安全地存储私钥,这些过程之间还有很多事情要做。

推荐阅读去中心化应用架构:后端、安全和设计模式

我们应该首先知道的事情

在本教程中,我们将尝试构建一个去中心化音乐商店应用程序的后端,该应用程序使用以太坊区块链的力量来存储音乐并共享它以供下载或流式传输。

我们正在尝试构建的应用程序的基本结构包含三个部分:

  1. 身份验证,通过电子邮件完成; 当然,我们需要在应用程序中添加加密密码。
  2. 数据的存储,音乐数据首先存储在ipfs中,存储地址存储在区块链中以供检索。
  3. 检索,任何经过身份验证的用户都可以访问我们平台上存储的数据并使用它。

我们将使用 Node.js 构建它,但您也可以使用 Python 或任何其他编程语言构建它。 我们还将了解如何在 IPFS 中存储媒体数据、获取地址并编写函数来存储该地址——并使用 Solidity 编程语言从区块链中检索该地址。

以下是我们应该拥有的一些工具,用于构建或使用以太坊和 Node.js。

  • 节点.js
    第一个要求是 Node 应用程序。 我们正在尝试构建一个 Node.js 应用程序,因此我们需要一个编译器。 请确保您已安装 Node.js — 并请下载最新的长期支持二进制文件 ( LTS )。
  • 松露套房
    Truffle 是一个合约开发和测试环境,也是以太坊区块链的资产管道。 它为编译、流水线和运行脚本提供了一个环境。 一旦你谈到开发区块链,Truffle 是一个受欢迎的去处。 查看 Truffle Suite 上的 Truffle Suite:智能合约的甜蜜工具。
  • 甘纳许 CLI
    另一个与 Truffle 配合得很好的工具是 Ganache-CLI。 它由 Truffle Suite 团队构建和维护。 构建编译后,需要一个模拟器来开发和运行区块链应用,然后部署智能合约来使用。 Ganache 使您可以更轻松地在模拟器中部署合约,而无需使用实际资金来支付交易成本、可循环账户等。 在 Ganache CLI 和 Ganache 阅读有关 Ganache CLI 的更多信息。
  • 混音
    Remix 就像 Ganache 的替代品,但还带有一个 GUI 来帮助导航以太坊智能合约的部署和测试。 您可以在 Remix — 以太坊 IDE 和社区上了解更多信息。 您所要做的就是访问 https://remix.ethereum.org 并使用 GUI 编写和部署智能合约。
  • Web3
    Web3 是一个库集合,允许您与以太坊节点进行交互。 这些可以是通过 HTTP、IPC 或 Web 套接字的合约的本地或远程节点。 Web3.js 简介 · 以太坊区块链开发者速成课程是了解 Web3 的好地方。
  • IPFS
    用于构建 dApp 的核心协议。 星际文件系统(IPFS) 是一种协议和点对点网络,用于在分布式文件系统中存储和共享数据。 IPFS Powers the Distributed Web 解释了有关 IPFS 及其通常使用方式的更多信息。

从头开始创建后端 API

所以首先我们必须创建一个要使用的后端,并且我们使用的是 Node.js。 当我们想要创建一个新的 Node.js API 时,我们要做的第一件事就是初始化一个 npm 包。 您可能知道,npm 代表Node Package Manager ,它与 Node.js 二进制文件一起预打包。 因此,我们创建了一个新文件夹并将其命名为“blockchain-music” 。 我们在该文件夹目录中打开终端,然后运行以下命令:

 $ npm init -y && touch server.js routes.js

这将使用package.json文件启动项目,并对所有提示回答“”。 然后我们还创建了一个server.js文件和一个routes.js文件,用于在 API 中编写routes函数。

毕竟,您将必须安装我们需要的软件包,以使我们的构建变得简单明了。 这个过程是一个连续的过程,也就是说,您可以在项目开发过程中的任何时候安装一个包。

让我们安装我们现在需要的最重要的:

  • Express.js
  • @松露/合同
  • 松露.js
  • web3.js
  • dotenv
  • short-id
  • MongoDB
  • 节点监视器

您还必须全局安装 Truffle.js,这样您就可以在本地环境中的任何地方使用它。 如果您想一次安装所有这些,请在终端中运行以下代码:

 $ npm install nodemon truffle-contract dotenv mongodb shortid express web3 --save && npm install truffle -g

--save标志是将包的名称保存在package.json文件中。 -g标志是全局存储这个特定的包,以便我们可以在我们将要处理的任何项目中使用它。

然后我们创建一个.env文件,我们可以在其中存储我们的 MongoDB 数据库秘密 URI 以供使用。 我们通过在终端中运行touch.env来做到这一点。 如果您还没有 MongoDB 的数据库帐户,请先从 MongoDB 页面开始。

dotenv包将我们存储的变量导出到 Node.js 进程环境。 请确保在推送到公共存储库时不要推送.env文件,以免泄露您的密码和私人数据。

接下来,我们必须在package.json文件中为项目的构建和开发阶段添加脚本。 目前我们的package.json看起来像这样:

 { "name": "test", "version": "1.0.0", "description": "", "main": "server.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "express": "^4.17.1", "socket.io": "^2.3.0", "truffle-contract": "^4.0.31", "web3": "^1.3.0" } }

然后,我们将在package.json文件中添加一个启动脚本以使用 nodemon 服务器,这样每当我们进行更改时,它都会重新启动服务器本身,以及一个直接使用节点服务器的构建脚本,它可能如下所示:

 { "name": "test", "version": "1.0.0", "description": "", "main": "server.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "nodemon server.js", "build": "node server.js" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "express": "^4.17.1", "socket.io": "^2.3.0", "truffle-contract": "^4.0.31", "web3": "^1.3.0" } }

接下来,我们必须使用我们之前在全球范围内安装的 Truffle 包来初始化 Truffle 以在我们的智能合约中使用。 在我们项目的同一文件夹中,我们在终端中运行以下命令:

 $ truffle init

然后我们可以开始在server.js文件中编写代码。 同样,我们正在尝试构建一个简单的去中心化音乐商店应用程序,客户可以在其中上传音乐供其他所有用户访问和收听。

我们的server.js应该是干净的,以便于组件的耦合和解耦,因此路由和其他功能将放在其他文件中,例如routes.js 。 我们的示例server.js可能是:

 require('dotenv').config(); const express= require('express') const app =express() const routes = require('./routes') const Web3 = require('web3'); const mongodb = require('mongodb').MongoClient const contract = require('truffle-contract'); app.use(express.json()) mongodb.connect(process.env.DB,{ useUnifiedTopology: true },(err,client)=>{ const db =client.db('Cluster0') //home routes(app,db) app.listen(process.env.PORT || 8082, () => { console.log('listening on port 8082'); }) })

基本上,上面我们使用require导入我们需要的库,然后使用app.use添加一个允许在我们的 API 中使用 JSON 的中间件,然后连接到我们的 MongoDB 数据库并获取数据库访问权限,然后我们指定哪个数据库集群我们正在尝试访问(对于本教程,它是“Cluster0” )。 在此之后,我们调用该函数并从路由文件中导入它。 最后,我们侦听端口8082上的任何尝试连接。

这个server.js文件只是启动应用程序的准系统。 请注意,我们导入了 routes.js 。 该文件将保存我们 API 的路由端点。 我们还在server.js文件中导入了我们需要使用的包并对其进行了初始化。

我们将为用户消费创建五个端点

  1. 仅通过电子邮件注册用户的注册端点。 理想情况下,我们会使用电子邮件和密码进行操作,但由于我们只想识别每个用户,为了本教程的简洁性,我们不会冒险使用密码安全性和散列。
     POST /register Requirements: email
  2. 通过电子邮件为用户登录端点。
     POST /login Requirements: email
  3. 用户上传端点——获取音乐文件数据的 API。 前端会将 MP3/WAV 文件转换为音频缓冲区并将该缓冲区发送到 API。
     POST /upload Requirements: name, title of music, music file buffer or URL stored
  4. 访问端点,它将向任何请求它的注册用户提供音乐缓冲区数据,并记录访问它的人。
     GET /access/{email}/{id} Requirements: email, id
  5. 我们还希望提供对整个音乐库的访问并将结果返回给注册用户。
     GET /access/{email} Requirements: email

然后我们在routes.js文件中编写路由函数。 我们利用数据库存储和检索功能,然后确保我们在文件末尾导出路由功能,以便可以在另一个文件或文件夹中导入。

 const shortid = require('short-id') function routes(app, db){ app.post('/register', (req,res)=>{ let email = req.body.email let idd = shortid.generate() if(email){ db.findOne({email}, (err, doc)=>{ if(doc){ res.status(400).json({"status":"Failed", "reason":"Already registered"}) }else{ db.insertOne({email}) res.json({"status":"success","id":idd}) } }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.post('/login', (req,res)=>{ let email = req.body.email if(email){ db.findOne({email}, (err, doc)=>{ if(doc){ res.json({"status":"success","id":doc.id}) }else{ res.status(400).json({"status":"Failed", "reason":"Not recognised"}) } }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.post('/upload', (req,res)=>{ let buffer = req.body.buffer let name = req.body.name let title = req.body.title if(buffer && title){ }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.get('/access/:email/:id', (req,res)=>{ if(req.params.id && req.params.email){ }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) } module.exports = routes

在这个route函数中,我们在appdb参数中调用了许多其他函数。 这些 API 端点函数使用户能够在 URL 中指定端点。 最终,我们选择其中一个函数来执行并​​提供结果作为对传入请求的响应。

我们有四个主要的端点功能:

  1. get :用于读取记录操作
  2. post : 用于创建记录操作
  3. put : 用于更新记录操作
  4. delete :用于删除记录操作

在这个routes函数中,我们使用了getpost操作。 我们使用post进行注册、登录和上传操作,并使用get进行访问数据操作。 有关这方面的更多解释,您可以查看 Jamie Corkhill 的文章“如何开始使用 Node:API、HTTP 和 ES6+ JavaScript 简介”。

在上面的代码中,我们还可以看到一些数据库操作,比如注册路由。 我们使用db.findOne存储了新用户的电子邮件,并使用db.createa在登录函数中检查了该电子邮件。 现在,在我们完成所有这些之前,我们需要使用db.collection方法命名一个集合或表。 这正是我们接下来要介绍的内容。

注意要了解有关 MongoDB 中数据库操作的更多信息,请查看 mongo Shell 方法文档。

用 Solidity 构建一个简单的区块链智能合约

现在我们将用 Solidity(这是编写智能合约的语言)编写一个区块链合约,以简单地存储我们的数据并在需要时检索它。 我们要存储的数据是音乐文件数据,这意味着我们必须将音乐上传到 IPFS,然后将缓冲区的地址存储在区块链中。

首先,我们在合约文件夹中创建一个新文件并将其命名为Inbox.sol 。 要编写智能合约,对 Solidity 有很好的了解是很有用的,但并不难,因为它类似于 JavaScript。

注意如果您有兴趣了解有关 Solidity 的更多信息,我在文章底部添加了一些资源来帮助您入门。

 pragma solidity ^0.5.0; contract Inbox{ //Structure mapping (string=>string) public ipfsInbox; //Events event ipfsSent(string _ipfsHash, string _address); event inboxResponse(string response); //Modifiers modifier notFull (string memory _string) { bytes memory stringTest = bytes(_string); require(stringTest.length==0); _; } // An empty constructor that creates an instance of the conteact constructor() public{} //takes in receiver's address and IPFS hash. Places the IPFSadress in the receiver's inbox function sendIPFS(string memory _address, string memory _ipfsHash) notFull(ipfsInbox[_address]) public{ ipfsInbox[_address] = _ipfsHash; emit ipfsSent(_ipfsHash, _address); } //retrieves hash function getHash(string memory _address) public view returns(string memory) { string memory ipfs_hash=ipfsInbox[_address]; //emit inboxResponse(ipfs_hash); return ipfs_hash; } }

在我们的合约中,我们有两个主要函数: sendIPFSgetHash函数。 在讨论函数之前,我们可以看到我们必须首先定义一个名为Inbox的合约。 在这个类中,我们在ipfsInbox对象中使用了结构(首先是事件,然后是修饰符)。

在定义了结构和事件之后,我们必须通过调用constructor函数来初始化合约。 然后我们定义了三个函数。 ( checkInbox函数在测试中用于测试结果。)

sendIPFS是用户输入标识符和哈希地址的地方,然后将其存储在区块链上。 getHash函数在给定标识符时检索哈希地址。 同样,这背后的逻辑是我们最终希望将音乐存储在 IPFS 中。 要测试它是如何工作的,您可以跳到 Remix IDE,复制、粘贴和测试您的合约,以及调试任何错误并再次运行(希望不需要它!)。

在测试我们的代码在混音中正常工作之后,让我们继续使用 Truffle 套件在本地编译它。 但首先,我们需要对文件进行一些更改并使用ganache-cli设置我们的模拟器:

首先,让我们安装ganache-cli 。 在同一目录中,在终端中运行以下命令:

 $ npm install ganache-cli -g

然后让我们打开另一个终端并在同一文件夹中运行另一个命令:

 $ ganache-cli

这将为我们的区块链合约启动模拟器以连接和工作。 最小化终端并继续使用您一直在使用的另一个终端。

如果您使用的是 Linux/Mac OS 或 Windows 中的 truffle -config.js ,现在转到truffle.js文件,然后将这个文件修改为如下所示:

 const path = require("path"); module.exports = { // to customize your Truffle configuration! contracts_build_directory: path.join(__dirname, "/build"), networks: { development: { host: "127.0.0.1", port: 8545, network_id: "*" //Match any network id } } };

基本上我们所做的是添加构建文件夹的路径,在该文件夹中将智能合约转换为 JSON 文件。 然后我们还指定了 Truffle 应该用于迁移的网络。

然后,同样在迁移文件夹中,创建一个名为2_migrate_inbox.js的新文件,并在文件中添加以下代码:

 var IPFSInbox = artifacts.require("./Inbox.sol"); module.exports = function(deployer) { deployer.deploy(IPFSInbox); };

我们这样做是为了获取合约文件并将其自动部署到 JSON,在 Truffle 迁移期间使用deployer功能。

在上述更改之后,我们运行:

 $ truffle compile

我们应该在最后看到一些显示编译成功的消息,例如:

 > Compiled successfully using: - solc: 0.5.16+commit.9c3226ce.Emscripten.clang

接下来,我们通过运行来迁移我们的合约:

 $ truffle migrate

一旦我们成功迁移了我们的合约,我们最后应该有这样的东西:

 Summary ======= > Total deployments: 1 > Final cost: 0.00973432 ETH

我们几乎完成了! 我们已经使用 Node.js 构建了我们的 API,并且还设置并构建了我们的智能合约。

我们还应该为我们的合约编写测试来测试我们的合约的行为并确保它是期望的行为。 测试通常被编写并放置在test文件夹中。 在 test 文件夹中创建的名为InboxTest.js的文件中编写的示例测试是:

 const IPFSInbox = artifacts.require("./Inbox.sol") contract("IPFSInbox", accounts =>{ it("emit event when you send a ipfs address", async()=>{ //ait for the contract const ipfsInbox = await IPFSInbox.deployed() //set a variable to false and get event listener eventEmitted = false //var event = () await ipfsInbox.ipfsSent((err,res)=>{ eventEmitted=true }) //call the contract function which sends the ipfs address await ipfsInbox.sendIPFS(accounts[1], "sampleAddress", {from: accounts[0]}) assert.equal(eventEmitted, true, "sending an IPFS request does not emit an event") }) })

所以我们通过运行以下命令来运行我们的测试:

 $ truffle test

它使用测试文件夹中的文件测试我们的合约,并显示通过和失败测试的数量。 对于本教程,我们应该得到:

 $ truffle test Using network 'development'. Compiling your contracts... =========================== > Compiling .\contracts\Inbox.sol > Artifacts written to C:\Users\Ademola\AppData\Local\Temp\test--2508-n0vZ513BXz4N > Compiled successfully using: — solc: 0.5.16+commit.9c3226ce.Emscripten.clang Contract: IPFSInbox √ emit event when you send an ipfs address (373ms) 1 passing (612ms)

使用 Web3 将智能合约集成到后端 API

大多数时候,当您看到教程时,您会看到为将前端直接集成到区块链而构建的去中心化应用程序。 但有时也需要与后端集成,例如在使用第三方后端 API 和服务时,或者在使用区块链构建 CMS 时。

Web3 的使用对此非常重要,因为它可以帮助我们访问远程或本地以太坊节点并在我们的应用程序中使用它们。 在继续之前,我们将讨论本地和远程以太坊节点。 本地节点是部署在我们系统上的节点,使用ganache-cli等模拟器,但远程节点是部署在ropstenrinkeby等在线水龙头/平台上的节点。 要深入了解,您可以按照关于如何在 ropsten 上部署的教程 5 分钟指南来使用 Truffle 和 Ropsten 部署智能合约,或者您可以使用 truffle 钱包提供商并通过更简单的方式部署您的智能合约。

我们在本教程中使用ganache-cli ,但如果我们在 ropsten 上部署,我们应该复制或存储我们的合约地址,例如在我们的 .env 文件中,然后继续更新server.js文件,导入 web3,导入迁移的合约并设置 Web3 实例。

 require('dotenv').config(); const express= require('express') const app =express() const routes = require('./routes') const Web3 = require('web3'); const mongodb = require('mongodb').MongoClient const contract = require('truffle-contract'); const artifacts = require('./build/Inbox.json'); app.use(express.json()) if (typeof web3 !== 'undefined') { var web3 = new Web3(web3.currentProvider) } else { var web3 = new Web3(new Web3.providers.HttpProvider('https://localhost:8545')) } const LMS = contract(artifacts) LMS.setProvider(web3.currentProvider) mongodb.connect(process.env.DB,{ useUnifiedTopology: true }, async(err,client)=>{ const db =client.db('Cluster0') const accounts = await web3.eth.getAccounts(); const lms = await LMS.deployed(); //const lms = LMS.at(contract_address) for remote nodes deployed on ropsten or rinkeby routes(app,db, lms, accounts) app.listen(process.env.PORT || 8082, () => { console.log('listening on port '+ (process.env.PORT || 8082)); }) })

server.js文件中,我们检查 web3 实例是否已经初始化。 如果没有,我们在我们之前定义的网络端口( 8545 )上对其进行初始化。 然后我们基于迁移的 JSON 文件和truffle-contract包构建一个合约,并将合约提供者设置为现在必须初始化的 Web3 实例提供者。

然后我们通过web3.eth.getAccounts获取帐户。 对于开发阶段,我们在合约类中调用已部署的函数,该函数要求ganache-cli (仍在运行)为我们提供要使用的合约地址。 但是,如果我们已经将合约部署到远程节点,我们会调用一个函数,将地址作为参数输入。 示例函数在上面代码中定义的lms变量下方注释。 然后我们调用routes函数,输入应用程序实例、数据库实例、合约实例 ( lms ) 和账户数据作为参数。 最后,我们监听端口8082上的请求。

另外,到目前为止,我们应该已经安装了 MongoDB 包,因为我们在 API 中使用它作为我们的数据库。 一旦我们有了这些,我们进入路由页面,我们使用合约中定义的方法来完成诸如保存和检索音乐数据之类的任务。

最后,我们的 routes.js 应该是这样的:

 const shortid = require('short-id') const IPFS =require('ipfs-api'); const ipfs = IPFS({ host: 'ipfs.infura.io', port: 5001,protocol: 'https' }); function routes(app, dbe, lms, accounts){ let db= dbe.collection('music-users') let music = dbe.collection('music-store') app.post('/register', (req,res)=>{ let email = req.body.email let idd = shortid.generate() if(email){ db.findOne({email}, (err, doc)=>{ if(doc){ res.status(400).json({"status":"Failed", "reason":"Already registered"}) }else{ db.insertOne({email}) res.json({"status":"success","id":idd}) } }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.post('/login', (req,res)=>{ let email = req.body.email if(email){ db.findOne({email}, (err, doc)=>{ if(doc){ res.json({"status":"success","id":doc.id}) }else{ res.status(400).json({"status":"Failed", "reason":"Not recognised"}) } }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.post('/upload', async (req,res)=>{ let buffer = req.body.buffer let name = req.body.name let title = req.body.title let id = shortid.generate() + shortid.generate() if(buffer && title){ let ipfsHash = await ipfs.add(buffer) let hash = ipfsHash[0].hash lms.sendIPFS(id, hash, {from: accounts[0]}) .then((_hash, _address)=>{ music.insertOne({id,hash, title,name}) res.json({"status":"success", id}) }) .catch(err=>{ res.status(500).json({"status":"Failed", "reason":"Upload error occured"}) }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.get('/access/:email', (req,res)=>{ if(req.params.email){ db.findOne({email: req.body.email}, (err,doc)=>{ if(doc){ let data = music.find().toArray() res.json({"status":"success", data}) } }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.get('/access/:email/:id', (req,res)=>{ let id = req.params.id if(req.params.id && req.params.email){ db.findOne({email:req.body.email},(err,doc)=>{ if(doc){ lms.getHash(id, {from: accounts[0]}) .then(async(hash)=>{ let data = await ipfs.files.get(hash) res.json({"status":"success", data: data.content}) }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) } module.exports = routes

At the beginning of the routes file, we imported the short-id package and ipfs-http-client and then initialized IPFS with the HTTP client using the backend URL ipfs.infura.io and port 5001 . This allowed us to use the IPFS methods to upload and retrieve data from IPFS (check out more here).

In the upload route, we save the audio buffer to IPFS which is better compared to just storing it on the blockchain for anyone registered or unregistered to use. Then we saved the address of the buffer in the blockchain by generating an ID and using it as an identifier in the sendIFPS function. Finally, then we save all the other data associated with the music file to our database. We should not forget to update our argument in the routes function since we changed it in the server.js file.

In the access route using id , we then retrieve our data by getting the id from the request, using the id to access the IPFS hash address, and then access the audio buffer using the address. But this requires authentication of a user by email which is done before anything else.

Phew, we're done ! Right now we have an API that can receive requests from users, access a database, and communicate to a node that has the software running on them. We shouldn't forget that we have to export our function with module.exports though!

As we have noticed, our app is a decentralized app . However, it's not fully decentralized as we only stored our address data on the blockchain and every other piece of data was stored securely in a centralized database which is the basis for semi-dApps . So the consumption of data can be done directly via request or using a frontend application in JavaScript to send fetch requests.

Our music store backend app can now safely store music data and provide access to anyone who needs to access it, provided it is a registered user. Using blockchain for music sharing makes it cheaper to store music data while focusing on connecting artists directly with users, and perhaps it could help them generate revenue that way. This wouldn't require a middleman that uses royalty; instead, all of the revenue would go to the artist as users request their music to either download or stream. A good example of a music streaming application that uses blockchain just like this is Opus OPUS: Decentralized music sharing platform. However, there are also a few others like Musicoin, Audius, and Resonate.

接下来是什么?

The final thing after coding is to start our server by running npm run start or npm run build and test our backend endpoints on either the browser or with Postman. After running and testing our API we could add more features to our backend and blockchain smart contract. If you'd like to get more guidance on that, please check the further reading section for more articles.

It's worth mentioning that it is critical to write unit and integration tests for our API to ensure correct and desirable behaviors. Once we have all of that done, we can deploy our application on the cloud for public use. This can be done on its own with or without adding a frontend (microservices) on Heroku, GCP, or AWS for public use. Happy coding!

Note : You can always check my repo for reference. Also, please note that the .env file containing the MongoDB database URI is included for security reasons.

Further Reading And Related Resources

  • “How to Build Ethereum Dapp with React.js: Complete Step-By-Step Guide,” Gregory McCubbin
  • “Ethereum + IPFS + React DApp Tutorial Pt. 1,” Alexander Ma
  • “Ethereum Development with Go,” Miguel Mota
  • “Create your first Ethereum dAPP with Web3 and Vue.JS (Part 1),” Nico Vergauwen
  • “Deploy a Smart Contract on Ethereum with Python, Truffle and web3py,” Gabriel Saldanha
  • “Why Use Blockchain Technology?,” Bernard Marr
  • “How To Build Your Own Blockchain Using Node.js,” DevTeam.Space
  • “How To Build A Blockchain App With Ethereum, Web3.js & Solidity Smart Contracts,” Gregory McCubbin
  • “How To Build A Simple Cryptocurrency Blockchain In Node.js,” Alfrick Opidi
  • “How Blockchain Technology Is Going To Revolutionize Ecommerce,” Sergii Shanin
  • “4 Ways Blockchain Will Transform Higher Education — Smarter With Gartner,” Susan Moore
  • “How To Learn Solidity: The Ultimate Ethereum Coding Tutorial,” Ryan Molecke
  • “Developing Ethereum Smart Contracts For Beginners,” Coursetro
  • “Learn about Ethereum,” Ethereum official site