在以太坊Dapp终极教程——如何构建一个完整的全栈去中心化应用(一)中,我们已经完成了一切所需的设置,让我们通过列出将在选举中运行的候选人来继续构建智能联系。我们需要一种方法来存储多个候选者,并存储关于每个候选者的多个属性。我们希望跟踪候选人的身份,姓名和投票计数。以下是我们如何为候选人建模:
contract Election {
// Model a Candidate
struct Candidate {
uint id;
string name;
uint voteCount;
}
// ...
}
我们使用Solidity Struct为候选人建模。Solidity允许我们创建自己的结构类型,就像我们在这里为候选人所做的那样。我们指定此结构具有无符号整数类型的id,字符串类型的名称和无符号整数类型的voteCount
。简单地声明这个结构实际上不会给我们一个候选人。我们需要实例化它并将其分配给变量,然后才能将其写入存储。
接下来我们需要的是存放候选人的地方。我们需要一个地方来存储我们刚刚创建的结构类型之一。我们可以使用Solidity mapping来完成此操作。Solidity中的映射类似于关联数组或散列,它将键值对关联起来。我们可以像这样创建这个映射:
contract Election {
// Model a Candidate
struct Candidate {
uint id;
string name;
uint voteCount;
}
// Read/write Candidates
mapping(uint => Candidate) public candidates;
// ...
}
在这种情况下,映射的关键是无符号整数,值是我们刚刚定义的候选结构类型。这基本上为我们提供了基于身份的每个候选人的查找。由于此映射已分配给状态变量,因此只要我们为其分配新的键值对,我们就会将数据写入区块链。接下来,我们将此映射的可见性设置为public
以获取getter
函数,就像我们在冒烟测试中使用候选名称一样。
接下来,我们使用计数器缓存状态变量跟踪选举中存在多少候选者,如下所示:
contract Election {
// Model a Candidate
struct Candidate {
uint id;
string name;
uint voteCount;
}
// Read/write Candidates
mapping(uint => Candidate) public candidates;
// Store Candidates Count
uint public candidatesCount;
// ...
}
在Solidity中,无法确定映射的大小,也无法迭代它。这是因为尚未赋值的映射中的任何键都将返回默认值(在这种情况下为空候选)。例如,如果我们在这次选举中只有2名候选人,并且我们尝试查找候选人#99,那么映射将返回空的候选人结构。此行为使得无法知道存在多少候选项,因此我们必须使用计数器缓存。
接下来,让我们创建一个函数来将候选添加到我们创建的映射中,如下所示:
contract Election {
// ...
function addCandidate (string _name) private {
candidatesCount ++;
candidates[candidatesCount] = Candidate(candidatesCount, _name, 0);
}
}
我们已经声明了函数addCandidate
,它接受一个表示候选者名称的字符串类型参数。在函数内部,我们递增候选计数器缓存以表示已添加新候选者。然后我们使用当前候选计数作为关键字,用新的Candidate
结构更新映射。使用当前候选计数中的候选ID
,函数参数中的名称以及初始投票计数来初始化此Candidate
结构。请注意,此函数的可见性是私有的,因为我们只想在合约中调用它。
现在我们可以通过在构造函数中调用两次addCandidate
函数来添加两个候选者,如下所示:
contract Election {
// ...
function Election () public {
addCandidate("Candidate 1");
addCandidate("Candidate 2");
}
// ...
}
当我们将合约部署到区块链时,将执行此迁移,并使用两个候选人填充我们的选举。此时,你的完整合约代码应如下所示:
pragma solidity ^0.4.2;
contract Election {
// Model a Candidate
struct Candidate {
uint id;
string name;
uint voteCount;
}
// Read/write candidates
mapping(uint => Candidate) public candidates;
// Store Candidates Count
uint public candidatesCount;
function Election () public {
addCandidate("Candidate 1");
addCandidate("Candidate 2");
}
function addCandidate (string _name) private {
candidatesCount ++;
candidates[candidatesCount] = Candidate(candidatesCount, _name, 0);
}
}
现在让我们像这样迁移我们的合约:
$ truffle migrate --reset
现在尝试与控制台内的候选人进行交互。
现在让我们编写一些测试来确保我们的智能合约正确初始化。首先,让我解释为什么在开发智能合约时测试非常重要。我们希望确保合约没有错误,原因如下:
现在让我们写一些测试。确保你先运行Ganache。然后,从项目的根目录在命令行中创建一个新的测试文件,如下所示:
$ touch test/election.js
我们将使用Mocha测试框架和Chai断言库在此文件中的Javascript中编写所有测试。这些与Truffle框架捆绑在一起。我们将在Javascript中编写所有这些测试,以模拟与智能合约的客户端交互,就像我们在控制台中所做的那样。以下是测试的所有代码:
var Election = artifacts.require("./Election.sol");
contract("Election", function(accounts) {
var electionInstance;
it("initializes with two candidates", function() {
return Election.deployed().then(function(instance) {
return instance.candidatesCount();
}).then(function(count) {
assert.equal(count, 2);
});
});
it("it initializes the candidates with the correct values", function() {
return Election.deployed().then(function(instance) {
electionInstance = instance;
return electionInstance.candidates(1);
}).then(function(candidate) {
assert.equal(candidate[0], 1, "contains the correct id");
assert.equal(candidate[1], "Candidate 1", "contains the correct name");
assert.equal(candidate[2], 0, "contains the correct votes count");
return electionInstance.candidates(2);
}).then(function(candidate) {
assert.equal(candidate[0], 2, "contains the correct id");
assert.equal(candidate[1], "Candidate 2", "contains the correct name");
assert.equal(candidate[2], 0, "contains the correct votes count");
});
});
});
让我解释一下这段代码。首先,我们要求合约并将其分配给变量,就像我们在迁移文件中所做的那样。接下来,我们调用合约函数contract
,并在回调函数中编写所有测试。此回调函数提供了一个帐户Accounts
变量,表示我们的区块链上的所有帐户,由Ganache提供。
第一次测试通过检查候选人数等于2来检查合约是否已使用正确数量的候选人进行初始化。
下一个测试检查每个候选人在选举中的价值,确保每个候选人都有正确的身份证,姓名和投票计数。
现在让我们从命令行运行测试,如下所示:
$ truffle test
是的,他们通过了!
现在让我们开始构建将与智能合约对话的客户端应用程序。我们将通过修改我们在上一节中安装的Truffle Pet Shop框附带的HTML和Javascript文件来完成此操作。我们将使用此现有代码开始。我们还要注意Truffle Pet Shop盒子附带的一些其他东西,比如Bootstrap框架,它将使我们不必在本教程中编写任何CSS。我们还有lite-server,它将为我们的资产提供服务以用于开发目的。
你不必是前端专家就可以按照本教程的这一部分进行操作。我故意保持HTML和Javascript代码非常简单,我们不会花太多时间专注于它。我想继续专注于开发dApp的智能合约部分!
继续使用以下代码替换index.html
文件的所有内容:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Election Results</title>
<!-- Bootstrap -->
<link href="css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container" style="width: 650px;">
<div class="row">
<div class="col-lg-12">
<h1 class="text-center">Election Results</h1>
<hr/>
<br/>
<div id="loader">
<p class="text-center">Loading...</p>
</div>
<div id="content" style="display: none;">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Votes</th>
</tr>
</thead>
<tbody id="candidatesResults">
</tbody>
</table>
<hr/>
<p id="accountAddress" class="text-center"></p>
</div>
</div>
</div>
</div>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="js/bootstrap.min.js"></script>
<script src="js/web3.min.js"></script>
<script src="js/truffle-contract.js"></script>
<script src="js/app.js"></script>
</body>
</html>
接下来,使用以下代码替换app.js
文件的所有内容:
App = {
web3Provider: null,
contracts: {},
account: '0x0',
init: function() {
return App.initWeb3();
},
initWeb3: function() {
if (typeof web3 !== 'undefined') {
// If a web3 instance is already provided by Meta Mask.
App.web3Provider = web3.currentProvider;
web3 = new Web3(web3.currentProvider);
} else {
// Specify default instance if no web3 instance provided
App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
web3 = new Web3(App.web3Provider);
}
return App.initContract();
},
initContract: function() {
$.getJSON("Election.json", function(election) {
// Instantiate a new truffle contract from the artifact
App.contracts.Election = TruffleContract(election);
// Connect provider to interact with contract
App.contracts.Election.setProvider(App.web3Provider);
return App.render();
});
},
render: function() {
var electionInstance;
var loader = $("#loader");
var content = $("#content");
loader.show();
content.hide();
// Load account data
web3.eth.getCoinbase(function(err, account) {
if (err === null) {
App.account = account;
$("#accountAddress").html("Your Account: " + account);
}
});
// Load contract data
App.contracts.Election.deployed().then(function(instance) {
electionInstance = instance;
return electionInstance.candidatesCount();
}).then(function(candidatesCount) {
var candidatesResults = $("#candidatesResults");
candidatesResults.empty();
for (var i = 1; i <= candidatesCount; i++) {
electionInstance.candidates(i).then(function(candidate) {
var id = candidate[0];
var name = candidate[1];
var voteCount = candidate[2];
// Render candidate Result
var candidateTemplate = "<tr><th>" + id + "</th><td>" + name + "</td><td>" + voteCount + "</td></tr>"
candidatesResults.append(candidateTemplate);
});
}
loader.hide();
content.show();
}).catch(function(error) {
console.warn(error);
});
}
};
$(function() {
$(window).load(function() {
App.init();
});
});
让我们注意一下这段代码所做的一些事情:
initWeb3
函数中配置web3。现在让我们在浏览器中查看客户端应用程序。首先,确保你已按照以下方式迁移合约:
$ truffle migrate --reset
接下来,从命令行启动你的开发服务器,如下所示:
$ npm run dev
这应该会自动使用客户端应用程序打开一个新的浏览器窗口。
[图片上传失败...(image-81a2d0-1559275088329)]
请注意你的应用程序显示正在加载loading...
。那是因为我们还没有登录到区块链!为了连接到区块链,我们需要将其中一个帐户从Ganache导入Metamask。
与Metamask连接后,你应该会看到所有合约和帐户数据都已加载。
[图片上传失败...(image-fed964-1559275088329)]
======================================================================
分享一些比特币、以太坊、EOS、Fabric等区块链相关的交互式在线编程实战教程:
- java比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Java代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Java工程师不可多得的比特币开发学习课程。
- php比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Php代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Php工程师不可多得的比特币开发学习课程。
- c#比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在C#代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是C#工程师不可多得的比特币开发学习课程。
- java以太坊开发教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。
- python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
- php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和交易等内容。
- 以太坊入门教程,主要介绍智能合约与dapp应用开发,适合入门。
- 以太坊开发进阶教程,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
- ERC721以太坊通证实战,课程以一个数字艺术品创作与分享DApp的实战开发为主线,深入讲解以太坊非同质化通证的概念、标准与开发方案。内容包含ERC-721标准的自主实现,讲解OpenZeppelin合约代码库二次开发,实战项目采用Truffle,IPFS,实现了通证以及去中心化的通证交易所。
- C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和交易等。
- EOS入门教程,本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。
- 深入浅出玩转EOS钱包开发,本课程以手机EOS钱包的完整开发过程为主线,深入学习EOS区块链应用开发,课程内容即涵盖账户、计算资源、智能合约、动作与交易等EOS区块链的核心概念,同时也讲解如何使用eosjs和eosjs-ecc开发包访问EOS区块链,以及如何在React前端应用中集成对EOS区块链的支持。课程内容深入浅出,非常适合前端工程师深入学习EOS区块链应用开发。
- Hyperledger Fabric 区块链开发详解,本课程面向初学者,内容即包含Hyperledger Fabric的身份证书与MSP服务、权限策略、信道配置与启动、链码通信接口等核心概念,也包含Fabric网络设计、nodejs链码与应用开发的操作实践,是Nodejs工程师学习Fabric区块链开发的最佳选择。
- Hyperledger Fabric java 区块链开发详解,课程面向初学者,内容即包含Hyperledger Fabric的身份证书与MSP服务、权限策略、信道配置与启动、链码通信接口等核心概念,也包含Fabric网络设计、java链码与应用开发的操作实践,是java工程师学习Fabric区块链开发的最佳选择。
- tendermint区块链开发详解,本课程适合希望使用tendermint进行区块链开发的工程师,课程内容即包括tendermint应用开发模型中的核心概念,例如ABCI接口、默克尔树、多版本状态库等,也包括代币发行等丰富的实操代码,是go语言工程师快速入门区块链开发的最佳选择。
汇智网原创翻译,转载请标明出处。这里是以太坊Dapp终极教程——如何构建一个完整的全栈去中心化应用