1、开发完成后页面预览如下
用户可以查看到当前参与投票的候选人名单已经他们各自的目前所得票数,选择其中一名进行投票,被投票的人所得票数对应增加。
2、开发准备工作
- 本地NodeJs环境
- Ganache测试环境
- 熟悉基础的Web3-Api操作,安装命令为: npm install web3@^0.20.0 --save
- NodeJs安装Solc编译环境,安装命令为:npm install sol
- 期间遇坑,安装Windows构建工具,安装命令为:npm install --global --production windows-build-tools
- python27
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('0xa167fddc1c6d3d6187a748947b8f00b0dc4fc8db');///这里改为你自己的deployedContract.address
//测试合约调用
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);
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 express = require('express');
var app = express();
var server = require('http').createServer(app);
var Web3 = require("web3");
app.use(express.static('.'));
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('0xcadcaf0cf38ad0adc259b8426b723bb31faa899d');
app.get("/totalVotesFor", function(req, res) {
var voteName = req.query.voteName;
var vote_num=contractInstance.totalVotesFor.call(voteName).toString();
console.log(vote_num);
res.send(vote_num);
});
app.get("/voteForCandidate", function(req, res) {
var voteName = req.query.voteName;
contractInstance.voteForCandidate(voteName, {from: web3.eth.accounts[0]});
var vote_num=contractInstance.totalVotesFor.call(voteName).toString();
res.send(vote_num);
});
server.listen(3000);
// 控制台会输出以下信息
console.log('Server running at http://127.0.0.1:3000/index.html');
index.html 内容如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Voting DApp</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" >
<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
<style type="text/css">
.vote-row{
margin-top: 45px;
margin-bottom: 45px;
}
.vote-person{
height: 150px;
width: 150px;
border: 1px solid #ccc;
border-radius: 10px;
margin:0 auto;
}
.vote-person:hover{
background-color: #eee;
}
.vote-btn{
margin-top: 14px;
width:100%;
border-radius: 10px;
border-top-left-radius:0em;
border-top-right-radius:0em;
}
.vote-name{
font-size: 18px;
margin-top:7px;
}
.vote-num{
width:50px;
height:50px;
border: 1px solid #ccc;
border-radius: 50%;
text-align: center;
margin:10px auto 6px auto;
padding-top: 10px;
font-size: 18px;
}
</style>
</head>
<body class="container">
<h2 class="text-center">简易投票 DApp</h2>
<div class="row vote-row">
<div class="col-md-3 col-md-offset-1">
<div class="vote-person">
<div class="text-center vote-name">Rama</div>
<div id="candidate-1" class="text-center vote-num"></div>
<a href="#" class="btn btn-primary vote-btn">为TA投票</a>
</div>
</div>
<div class="col-md-3">
<div class="vote-person">
<div class="text-center vote-name">Nick</div>
<div id="candidate-2" class="text-center vote-num"></div>
<a href="#" class="btn btn-primary vote-btn">为TA投票</a>
</div>
</div>
<div class="col-md-3">
<div class="vote-person">
<div class="text-center vote-name">Jose</div>
<div id="candidate-3" class="text-center vote-num"></div>
<a href="#" class="btn btn-primary vote-btn">为TA投票</a>
</div>
</div>
</div>
</body>
<!--<script src="./web3.js"></script>-->
<script src="./app.js"></script>
</html>
app.js 内容如下:
注意:此处需要将之前保存的 ABI文件内容引入。
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 voteName = candidateNames[i];
totalVotesFor(voteName);
}
//初始化事件
$(".vote-btn").click(function(){
//获取投票人名称
let voteName=$(this).prev().prev().text();
voteForCandidate(voteName);
});
});
function totalVotesFor(voteName) {
$.get("/totalVotesFor?voteName=" + voteName, function(data) {
if(data == "Error") {
alert('提示', '500');
} else {
$("#"+candidates[voteName]).html(data);
}
});
}
function voteForCandidate(voteName) {
$.get("/voteForCandidate?voteName=" + voteName, function(data) {
if(data == "Error") {
alert('提示', '500');
} else {
let div_id = candidates[voteName];
var vote_num = totalVotesFor(voteName);
$("#"+div_id).html(data);//.fadeIn(800);
$("#"+div_id);//.fadeOut(400);
}
});
}
至此所有开发已经完成。
6、网页访问与测试
执行如下命令启动服务:
PS C:\Workspace\Ruoli-Code\Voting-Node> npm run dev
> Voting-Node@1.0.0 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
即可看到最开始的界面,至此所有开发完成。
参考:
https://my.oschina.net/ruoli/blog/1806474
https://blog.csdn.net/loy_184548/article/details/79517264
https://blog.csdn.net/baidu_34217927/article/details/78899510