想在本地一步步跑起一个真正的 以太坊 dapp?这篇实战教程带你用 2~3 小时完成一个全栈投票应用,涵盖Solidity智能合约、Truffle框架、前端交互及链上事件监听。所有代码示例均可直接抄用。
目录
- 区块链和去中心化应用基础
- 开发工具与环境准备
- 部署第一个智能合约(Smoke Test)
- 定义候选人数据结构
- 为合约添加投票功能
- 前端页面与 Metamask 交互
- 用事件监听实时刷新投票结果
- 常见问题解答
1. 区块链和去中心化应用基础
| 概念 | 一句话解释 |
|---|---|
| 区块链 | 由众多节点共同维护、不可篡改的分布式数据库。 |
| 以太坊 | 支持「智能合约」的去中心化计算平台,我们只用到它的本地私链。 |
| dapp | 前端 + 智能合约的「无后端」应用,数据全部在链上。 |
传统应用把数据存在中心数据库,管理员可以偷偷改票。把投票逻辑写在以太坊智能合约,任何人都能验证票数,合约不可篡改,这就解决了信任问题。
👉 想快速了解以太坊入门要点,点这里
2. 开发工具与环境准备
2.1 Node & NPM
$ node -v # 只要版本 ≥ v14 即可2.2 全局安装 Truffle
$ npm install -g truffleTruffle 帮你编译、部署、测试合约,配合前端开发一条龙。
2.3 Ganache 本地私链
- 官网 一键安装即可。
- 启动后自动给出 10 个账户,每个账户 100 枚测试 ETH。
2.4 Metamask 插件
- Chrome 商店搜索「Metamask」安装。
- 首次导入 Ganache 提供的助记词,就能在浏览器里完成链上签名。
2.5(可选)Solidity 语法高亮
- VS Code / Sublime 安装 Solidity/Ethereum 生长包即可。
3. 部署第一个智能合约(Smoke Test)
3.1 项目初始化
$ mkdir election && cd election
$ truffle unbox pet-shop # 提供目录骨架得到目录:
- contracts/ 存
.sol文件 - migrations/ 部署脚本
- src/ 前端资源
- test/ 单元测试
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);
}
}要点:
mapping读写结构体private禁止任何人调新增候选人,主网生产环境可通过治理 DAO 或管理员控制。
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 简单界面
- 表格展示候选人
- Drop-down 选择谁投票
- 提交后等待链上确认
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(); });提交后在前端隐藏表单,防止「二次投票」状态显示混乱。
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。