为了账号安全,请及时绑定邮箱和手机立即绑定

实战-动手开发第一个区块链投票DAPP

标签:
区块链

第一个区块链投票DAPP

前言
  • 我是一个菜鸟,所以在代码质量上可能不太好,欢迎指点。
  • 阅读本文可能需要一定的基础,有疑问欢迎留言。

本篇文章,将带读者用Truffle框架在ganache环境上
ganache

搭建一个属于自己的投票DAPP雏形,你可以在这基础上进行扩展。这里如果你对ganache不熟悉的可以使用testrpc环境也是一样的。

开发包对应版本
  • web3.js v0.20.5
  • Truffle v4.1.3
  • Solidity v0.4.2
  • Ganache 1.1.0
初始化工程

这里我们需要提前安装好 nodeTruffle,如果不会的,可自行翻阅文档,网上很多资料。

  • 新建一个文件夹
  • 在新建的文件下执行下面的命令
    truffle unbox pet-shop

    这个命令是通过 truffleunbox 工具初始化一个宠物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`文件里面的配置
![](https://upload-images.jianshu.io/upload_images/2275747-6919f8c10773060c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

## 编写html页面

由于我们是在 `pet-shop` 项目上进行修改的,所以我们只需要去修改 `src/index.html` 文件的内容就可以了。

我们把内容修改成如下内容:

```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>区块链投票</title>
    <link href="css/bootstrap.min.css" rel="stylesheet">

  </head>
  <body>
    <div class="container">
      <div class="row">
        <div class="col-xs-12 col-sm-8 col-sm-push-2">

          <h1 class="text-center">区块链投票</h1>
          <hr/>
          <br/>

          <div id="loader">
            <p class="test-center">Loading...</p>
          </div>

          <div id="content" style="display: none;">
            <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/>

            <!-- 投票 -->
            <form onsubmit="App.castVote();return false;">
              <div class="form-group">
                <label for="cadidatesSelect">选择你要投的名字:</label>
                <select class="form-control" id="cadidatesSelect">
                </select>
              </div>
              <button type="submit" class="btn btn-primary">投票</button>
            </form>

          </div>

          <hr/>
          <p id="accountAddress" class="test-center"></p>

        </div>
      </div>

    </div>

    <script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="js/bootstrap.min.js"></script>
    <script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="js/web3.min.js"></script>
    <script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="js/truffle-contract.js"></script>
    <script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="js/app.js"></script>
  </body>
</html>

<style>
  .test-center{
    text-align: center;
  }
  table{
    width: 100%;
  }
  table tr{
    border-bottom: 2px solid #efefef;
    height: 40px;
  }
</style>

代码很简单,不用解释应该都能看懂哈。

编写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 = "<tr><th>"+id+"</th><td>"+name+"</td><td>"+voteCount+"</td></tr>";
          $candidatesResults.append(candidateTemplate);

          //投票
          var cadidateOption = "<option value='"+id+"'>"+name+"</option>";
          $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文件读取成功后,再调用 TruffleTruffleContract 方法进行合约初始化。
    • 初始化合约后,通过 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充当的是一个桥梁作用,当我我们需要用到签名时,他会出现一个签名的界面让你确认,如下图:

其实就是一个轻钱包。

[获取授权]

点击查看更多内容
10人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
全栈工程师
手记
粉丝
1.6万
获赞与收藏
399

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消