为什么80%的码农都做不了架构师?>>>
1、开发完成后页面预览如下
用户可以查看到当前参与投票的候选人名单已经他们各自的目前所得票数,选择其中一名进行投票,被投票的人所得票数对应增加。
2、开发准备工作
- 本地NodeJs环境
- Ganache测试环境
- 熟悉基础的Web3-Api操作,安装命令为: npm install web3
- NodeJs安装Solc编译环境,安装命令为:npm install solc
3、编写智能合约
创建工作目录 Voting-Node,在此目录下新建 Voting.sol 智能合约文件,并在Remix中对此文件进行编辑,最后编辑完成后的文件内容如下:
pragma solidity ^0.4.18;
contract Voting {
mapping (bytes32 => uint8) public votesReceived;
bytes32[] public candidateList;
constructor (bytes32[] candidateNames) public {
candidateList = candidateNames;
}
function totalVotesFor(bytes32 candidate) view public returns (uint8) {
require(validCandidate(candidate));
return votesReceived[candidate];
}
function voteForCandidate(bytes32 candidate) public {
require(validCandidate(candidate));
votesReceived[candidate] += 1;
}
function validCandidate(bytes32 candidate) view public returns (bool) {
for(uint i = 0; i < candidateList.length; i++) {
if (candidateList[i] == candidate) {
return true;
}
}
return false;
}
}
4、编译、部署、测试 智能合约
在Voting-Node目录打开命令行并输入node 命令,可以进入REPL环境,分步执行编译部署命令,不过这种方式比较麻烦,不方便维护和修改,此处直接在Voting-Node目录中编写好了一个编译、部署、测试 的 depoy.js文件,直接在Nodejs中运行就可以看到结果,内容如下:
//引入web3模块
let Web3 = require('web3');
//初始化 web3
let web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:7545"));
//输出初始化结果
console.log('Initialization web3 complete,the first account is '+ web3.eth.accounts[0]);
let fs = require('fs');
let code = fs.readFileSync('Voting.sol').toString();
let solc = require('solc');
//编译合约为ABI文件
let compiledCode = solc.compile(code);
console.log('Compile Voting.sol complete');
//部署合约至区块链节点
let abiDefinition = JSON.parse(compiledCode.contracts[':Voting'].interface);
//写入ABI文件至本地文件目录
fs.writeFile('Voting.json',JSON.stringify(abiDefinition), {}, function(err) {
console.log('write ABI file [Voting.json] complete . ');
});
let VotingContract = web3.eth.contract(abiDefinition);
let byteCode = compiledCode.contracts[':Voting'].bytecode;
//调用VotingContract对象的new()方法来将投票合约部署到区块链。new()方法参数列表应当与合约的 构造函数要求相一致。对于投票合约而言,new()方法的第一个参数是候选人名单。
let deployedContract = VotingContract.new(['Rama','Nick','Jose'],{data: byteCode, from: web3.eth.accounts[0], gas: 4700000});
//输出合约 地址,如果此处没有返回地址,可以在Ganache日志中查看到
console.log('deploy complete,deploy address is '+ deployedContract.address);
//let contractInstance = VotingContract.at(deployedContract.address);
let contractInstance = VotingContract.at('0x99bdfb1f4c5d0c227d6cd98cf7a254bfc27c35cc');
//测试合约调用
contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]});
contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]});
contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]});
contractInstance.voteForCandidate('Nick', {from: web3.eth.accounts[0]});
contractInstance.voteForCandidate('Jose', {from: web3.eth.accounts[0]});
contractInstance.voteForCandidate('Jose', {from: web3.eth.accounts[0]});
console.log("--------------finish----------------");
let RamaVote=contractInstance.totalVotesFor.call('Rama');
let NickVote=contractInstance.totalVotesFor.call('Nick');
let JoseVote=contractInstance.totalVotesFor.call('Jose');
console.log("Rama's vote is "+RamaVote);
console.log("Nick's vote is "+NickVote);
console.log("Jose's vote is "+JoseVote);
执行结果如下:
PS C:\Workspace\Ruoli-Code\Eth-Node> node .\deploy.js
Initialization web3 complete,the first account is 0xa0b4360847bfe6c45f78103e97678dfa79aa9975
Compile Voting.sol complete
deploy complete,deploy address is undefined
--------------finish----------------
Rama's vote is 3
Nick's vote is 1
Jose's vote is 2
write ABI file complete .
最后结果符合预期,说明测试调用已经成功。
如果部署中出现 问题:TypeError: web3.eth.contract is not a function
则需要安装 0.19版本的 web3,命令如下:
npm install web3@^0.19.0 --save
安装完成后,问题修复。
5、网页交互
在完成上面的编写、编译、部署、测试环节后,添加网页交互那就很简单了。
在Voting-Node目录 打开命令行,执行初始化命令,如下:
npm init -y
会在此目录下生成 package.json 文件,修改此文件内容如下:
{
"name": "Voting-Node",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"solc": "^0.4.23",
"web3": "^0.19.0"
},
"devDependencies": {},
"scripts": {
"dev": "node index.js"
},
"keywords": [],
"author": "Ruoli",
"license": "ISC"
}
在目录下新建 index.js 、 index.html、app.js 文件,如下所示。
index.js 内容如下:
var http = require('http');
var fs = require('fs');
var url = require('url');
// 创建服务器
http.createServer( function (request, response) {
// 解析请求,包括文件名
var pathname = url.parse(request.url).pathname;
// 输出请求的文件名
console.log("Request for " + pathname + " received.");
// 从文件系统中读取请求的文件内容
fs.readFile(pathname.substr(1), function (err, data) {
if (err) {
console.log(err);
// HTTP 状态码: 404 : NOT FOUND
// Content Type: text/plain
response.writeHead(404, {'Content-Type': 'text/html'});
}else{
// HTTP 状态码: 200 : OK
// Content Type: text/plain
response.writeHead(200, {'Content-Type': 'text/html'});
// 响应文件内容
response.write(data.toString());
}
// 发送响应数据
response.end();
});
}).listen(3000);
// 控制台会输出以下信息
console.log('Server running at http://127.0.0.1:3000/index.html');
index.html 内容如下:
Voting DApp
简易投票 DApp
app.js 内容如下:
注意:此处需要将之前保存的 ABI文件内容引入。
let web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:7545"));
//导入合约的ABI文件
let abi = JSON.parse('[{"constant":true,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"totalVotesFor","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"validCandidate","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"votesReceived","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"candidateList","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"voteForCandidate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"candidateNames","type":"bytes32[]"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]')
let VotingContract = web3.eth.contract(abi);
let contractInstance = VotingContract.at('0x99bdfb1f4c5d0c227d6cd98cf7a254bfc27c35cc');
let candidates = {"Rama": "candidate-1", "Nick": "candidate-2", "Jose": "candidate-3"}
$(document).ready(function() {
//初始化余额
candidateNames = Object.keys(candidates);
for (var i = 0; i < candidateNames.length; i++) {
let name = candidateNames[i];
let val = contractInstance.totalVotesFor.call(name).toString()
$("#"+candidates[name]).html(val);
}
//初始化事件
$(".vote-btn").click(function(){
//获取投票人名称
let voteName=$(this).prev().prev().text();
contractInstance.voteForCandidate(voteName, {from: web3.eth.accounts[0]}, function() {
let div_id = candidates[voteName];
let vote_num=contractInstance.totalVotesFor.call(voteName).toString();
$("#"+div_id).fadeOut(400);
$("#"+div_id).html(vote_num).fadeIn(800);
});
});
});
至此所有开发已经完成。
6、网页访问与测试
执行如下命令启动服务:
PS C:\Workspace\Ruoli-Code\Voting-Node> npm run dev
> [email protected] dev C:\Workspace\Ruoli-Code\Voting-Node
> node index.js
Server running at http://127.0.0.1:3000/index.html
执行完成后 在浏览器中 访问:http://127.0.0.1:3000/index.html
即可看到最开始的界面,至此所有开发完成。