前言
- 我是一个菜鸟,所以在代码质量上可能不太好,欢迎指点。
- 阅读本文可能需要一定的基础,有疑问欢迎留言。
本篇文章,将带读者用Truffle框架在ganache环境上
搭建一个属于自己的投票DAPP雏形,你可以在这基础上进行扩展。这里如果你对ganache不熟悉的可以使用testrpc环境也是一样的。
开发包对应版本
- web3.js v0.20.5
- Truffle v4.1.3
- Solidity v0.4.2
- Ganache 1.1.0
初始化工程
这里我们需要提前安装好 node
和 Truffle
,如果不会的,可自行翻阅文档,网上很多资料。
- 新建一个文件夹
- 在新建的文件下执行下面的命令
truffle unbox pet-shop
这个命令是通过 truffle
的 unbox
工具初始化一个宠物DAPP,我们这里偷个懒,在官方的 pet-shop
项目上进行修改。
编写合约
在contracts
文件夹下面新建一个 Election.sol
的合约文件,在这里我们进行合约编写。
合约内容如下:
pragma solidity ^0.4.2;
contract Election {
//结构体
struct Candidate {
uint id;
string name;
uint voteCount;
}
//事件
event votedEvent(
uint indexed _candidateId
);
//存储结构体
mapping (uint => Candidate) public candidates;
//是否已经投票了
mapping (address=>bool) public voters;
//总数量
uint public candidateCount;
//构造函数
function Election () public {
addCandidate("张三");
addCandidate("李四");
}
//添加候选人
function addCandidate(string _name) private {
candidateCount ++;
candidates[candidateCount] = Candidate(candidateCount, _name, 0);
}
//投票
function vote(uint _candidateId) public {
//过滤
require(!voters[msg.sender]);
require(_candidateId > 0 && _candidateId <= candidateCount);
//记录用户已经投票了
voters[msg.sender] = true;
candidates[_candidateId].voteCount ++;
votedEvent(_candidateId);
}
}
合约部分不赘述,如果有疑问可以留言。
部署合约
Truffle框架部署合约很简单,可以通过他的 migration
功能直接部署。
- 在
migrations
文件夹下面,新建一个名为2_deploy_contract.js
的文件。 - 在里面我们把我们的合约给加到
migration
里面,代码如下:
var Election = artifacts.require("./Election.sol");
module.exports = function(deployer) {
deployer.deploy(Election);
};
这里说明下,如果你想偷懒,也可以直接在他下面的
1_initial_migration.js
文件里面导入Election.sol
文件再加到migration
里面。
- 在一切准备就绪,只需要在终端上执行下面的命令
truffle migrate --reset
就能部署好了。为啥要加 --reset
如果你的合约第一次部署,可以不加,如果是迭代覆盖部署,就的加这个参数,好从新给你分配一个地址,否者会出现莫名其妙的问题,具体详细介绍,请查阅官方文档。
如果你这里使用的tesrpc环境,服务地址的端口是
8545
你需要去 修改下项目里面的truffle.js
文件里面的配置
编写html页面
由于我们是在 pet-shop
项目上进行修改的,所以我们只需要去修改 src/index.html
文件的内容就可以了。
我们把内容修改成如下内容:
区块链投票
区块链投票
Loading...
代码很简单,不用解释应该都能看懂哈。
编写js文件
js部分是DAPP比较麻烦的地方,也是最初学者迷惑的地方,我这里先把最终代码粘贴过来再解释:
App = {
web3Provider: null,
contracts: {},
account: '0x0',
init: function() {
return App.initWeb3();
},
initWeb3: function() {
if (typeof web3 !== 'undefined') {
App.web3Provider = web3.currentProvider;
console.warn("Meata");
}else{
App.web3Provider = new Web3.providers.HttpProvider('https://localhost:7545');
}
web3 = new Web3(App.web3Provider);
return App.initContract();
},
initContract: function() {
$.getJSON("Election.json",function(election){
App.contracts.Election = TruffleContract(election);
App.contracts.Election.setProvider(App.web3Provider);
App.listenForEvents();
return App.reander();
})
},
reander: function(){
var electionInstance;
var $loader = $("#loader");
var $content = $("#content");
$loader.show();
$content.hide();
//获得账号信息
web3.eth.getCoinbase(function(err,account){
if(err === null){
App.account = account;
$("#accountAddress").html("您当前的账号: " + account);
}
});
//加载数据
App.contracts.Election.deployed().then(function(instance){
electionInstance = instance;
return electionInstance.candidateCount();
}).then(function(candidatesCount){
var $candidatesResults = $("#candidatesResults");
$candidatesResults.empty();
var $cadidatesSelect = $("#cadidatesSelect");
$cadidatesSelect.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];
var candidateTemplate = ""+id+" "+name+" "+voteCount+" ";
$candidatesResults.append(candidateTemplate);
//投票
var cadidateOption = "";
$cadidatesSelect.append(cadidateOption);
});
}
return electionInstance.voters(App.account);
}).then(function(hasVoted){
if(hasVoted){
$('form').hide();
}
$loader.hide();
$content.show();
}).catch(function(err){
console.warn(err);
});
},
//投票
castVote: function(){
var $loader = $("#loader");
var $content = $("#content");
var candidateId = $('#cadidatesSelect').val();
App.contracts.Election.deployed().then(function(instance){
return instance.vote(candidateId,{from: App.account});
}).then(function(result){
$content.hide();
$loader.show();
}).catch(function(err){
console.warn(err);
});
},
//监听事件
listenForEvents: function(){
App.contracts.Election.deployed().then(function(instance){
instance.votedEvent({},{
formBlock:0,
toBlock: 'latest'
}).watch(function(error,event){
console.log("event triggered",event);
App.reander();
});
})
}
};
$(function() {
$(window).load(function() {
App.init();
});
});
-
initWeb3
方法里面主要是对web3.js进行初始化,应该都能看懂。 -
initContract
方法中-
getJSON
方法是从本地读取json文件,在json文件读取成功后,再调用Truffle
的TruffleContract
方法进行合约初始化。 - 初始化合约后,通过
setProvider
方法我这里理解是设置代理。
-
其他的都是调取的web3.js
提供的api,除了api之外我觉得最有必要解释的是 App.contracts.Election.deployed().then(function(instance)...
这一串代码,这是实例化Election
合约后会调取后面then
里面的方法同时,把实例化的变量通过 instance
带入到方法的参数里面。
同时在then
里面有返回了一个方法 return instance.vote(candidateId,{from: App.account});
这个方法又会执行,执行完后,又把执行的结果待会给下一个 then
,依次类推,这貌似是es6的链式语法。
如果我解释得不太明白,可以留言。
运行起来
上面的代码啥的一切准备就绪,现在只需要执行
npm run dev
项目就启动了,由于需要和testrpc
或者Ganache
交互,所有我们需要用到 MetaMask
插件,所以得要用谷歌浏览器,打开我们的项目,同时需要MetaMask
插件连接到我们的测试环境。
就能看到出来的效果了。
如果对MetaMask
不了解的,可以在网上查阅相关资料。这里我的理解是,在DAPP中MetaMask
充当的是一个桥梁作用,当我我们需要用到签名时,他会出现一个签名的界面让你确认,如下图:
其实就是一个轻钱包。
[获取授权]