完整指南:以太坊 Dapp 开发实战教程(从前端到区块链投票应用)

·

想在本地一步步跑起一个真正的 以太坊 dapp?这篇实战教程带你用 2~3 小时完成一个全栈投票应用,涵盖 Solidity 智能合约、Truffle 框架、前端交互及链上事件监听。所有代码示例均可直接抄用。

目录

  1. 区块链和去中心化应用基础
  2. 开发工具与环境准备
  3. 部署第一个智能合约(Smoke Test)
  4. 定义候选人数据结构
  5. 为合约添加投票功能
  6. 前端页面与 Metamask 交互
  7. 用事件监听实时刷新投票结果
  8. 常见问题解答

1. 区块链和去中心化应用基础

概念一句话解释
区块链由众多节点共同维护、不可篡改的分布式数据库。
以太坊支持「智能合约」的去中心化计算平台,我们只用到它的本地私链。
dapp前端 + 智能合约的「无后端」应用,数据全部在链上。

传统应用把数据存在中心数据库,管理员可以偷偷改票。把投票逻辑写在以太坊智能合约,任何人都能验证票数,合约不可篡改,这就解决了信任问题。
👉 想快速了解以太坊入门要点,点这里


2. 开发工具与环境准备

2.1 Node & NPM

$ node -v         # 只要版本 ≥ v14 即可

2.2 全局安装 Truffle

$ npm install -g truffle

Truffle 帮你编译、部署、测试合约,配合前端开发一条龙。

2.3 Ganache 本地私链

2.4 Metamask 插件

2.5(可选)Solidity 语法高亮


3. 部署第一个智能合约(Smoke Test)

3.1 项目初始化

$ mkdir election && cd election
$ truffle unbox pet-shop      # 提供目录骨架

得到目录:

3.2 第一个合约 contracts/Election.sol

pragma solidity ^0.4.2;

contract Election {
    string public candidate;

    function Election() public {
        candidate = "Candidate 1";
    }
}

3.3 部署脚本 migrations/2_deploy_contracts.js

var Election = artifacts.require("./Election.sol");
module.exports = deployer => { deployer.deploy(Election); };

3.4 一键部署并测试

$ truffle migrate
$ truffle console
truffle(development)> Election.deployed().then(c => app=c)
truffle(development)> app.candidate()   // 'Candidate 1'
恭喜你,第一个合约运行成功!

4. 定义候选人数据结构

把「候选人」升级为完整对象:包含编号、姓名、票号。

pragma solidity ^0.4.2;

contract Election {
    struct Candidate {
        uint    id;
        string  name;
        uint    voteCount;
    }

    mapping(uint => Candidate) public candidates;
    uint public candidatesCount;

    function Election() public {
        addCandidate("候选人 Alice");
        addCandidate("候选人 Bob");
    }

    function addCandidate(string _name) private {
        candidatesCount++;
        candidates[candidatesCount] = Candidate(candidatesCount, _name, 0);
    }
}

要点:


FAQ 1|太长不看版

Q:为什么用 --reset
A:truffle migrate --reset 强制重新部署,解决旧合约状态问题。


5. 为合约添加投票功能

5.1 防刷票映射

mapping(address => bool) public voters;

5.2 投票函数

function vote(uint _candidateId) public {
    require(!voters[msg.sender]);                    // 限制重投
    require(_candidateId > 0 && _candidateId <= candidatesCount); // 有效候选人

    voters[msg.sender] = true;                       // 标记已投
    candidates[_candidateId].voteCount++;            // 计票
}

5.3 单元测试

测试文件:test/election.js

it("allows a voter to cast a vote", () =>
  Election.deployed()
    .then(inst => inst.vote(1, {from: accounts[0]}))
    .then(() => Election.deployed())
    .then(inst => inst.voters(accounts[0]))
    .then(voted => assert(voted)));

执行 $ truffle test,全部绿钩=安全上链。


6. 前端页面与 Metamask 交互

6.1 简单界面

6.2 Web3 注入与获取账户

web3.eth.getCoinbase((err, acc) => {
  if(!err) App.account = acc;
});

6.3 页面布局 & 加载候选人

// 伪代码
for(let i = 1; i <= count; i++) {
  instance.candidates(i).then(candidate => {
    // 展示 <tr><td>编号</td><td>姓名</td><td>票数</td></tr>
  });
}

6.4 表单提交

const candidateId = $('#candidatesSelect').val();
Election.deployed()
  .then(inst => inst.vote(candidateId, {from: App.account}))
  .then(() => { $("#loader").show(); $("#content").hide(); });

提交后在前端隐藏表单,防止「二次投票」状态显示混乱。

👉 掌握开发效率提升的10个小技巧


7. 用事件监听实时刷新投票结果

7.1 定义事件

event votedEvent(uint indexed _candidateId);

7.2 在 vote 函数末尾触发

votedEvent(_candidateId);

7.3 前端监听

App.contracts.Election.deployed().then(function(instance) {
  instance.votedEvent({}, {fromBlock: 0, toBlock: 'latest'})
         .watch((err, event) => { if(!err) App.render(); });
});

监听后,页面无需手动刷新即可实时显示新票号。


8. 常见问题解答

Q1:手机钱包能用这套 dapp 吗?
A:只要把合约部署到公网测试链(Rinkeby 或 Goerli),再用手机钱包访问前端即可。

Q2:Ganache 浏览器列表里账户余额显示为 0?
A:大概率是你把助记词导入错了,重启 Ganache 并重新关联 Metamask。

Q3:truffle test 报错 Error: VM Exception while processing transaction: revert
A:99% 是 require 不通过,对照测试代码的参数、账户、合约状态再检查。

Q4:如何把项目部署到真正主网?
A:准备主网 ETH → 配置 truffle-config.js → 添加 Infura/Alchemy 节点 → truffle migrate --network mainnet,但强烈建议先用测试网演练。

Q5:一定要学 JavaScript 吗?
A:前端可使用 React、Vue、纯 HTML 都可以,核心不过是调用 Web3.js 或 Ethers.js。

Q6:项目跑完后如何接着练习?
A:试着添加「候选人头像」「管理员」角色、使用 IPFS 存图片,逐步进阶到 NFT、DAO。