以太坊实践整理(五)DApp开发全过程记录(下)

说明:本篇所涉及的DAPP众筹需求及合约实现来自登链社区
在前面的众筹案例中,创作者没有办法查看到所有参与者,有两个办法可以实现查案所有参与者:
  1. 加入一个状态变量:address[] joinAccounts,这是一个数组,用来记录所有参与者的地址,每当有新的参与者进来时,往数组中加入参与者地址。
  2. 通过触发事件把参与者地址记录到日志中,然后启动一个服务程序监听事件,当事件触发时,把参与者地址记录到数据库中,并提供一个后端服务,把数据库中的参与者列表返回给前端。
两种方法各有优缺点:方法1的gas消耗会远高于方法2,优点是不需要额外引入服务器;方法2则相反,使用事件的方法2其实还有一个好处,就是可以实时监听到事件的变化(通常对应着链上状态的变化),这在一些场合非常有用。
下面主要介绍方法2,通过后台服务,监听事件变化。我们使用Node.js及Express框架作为后台服务。
安装服务端环境
进入crowdfunding工程目录,创建server目录作为后端工程目录,并安装环境:
mkdir server cd server npm init npm install express --save

新建一个文件index.js,编写服务端程序:
const express = require('express') const app = express()app.get('/', (req, res) => res.send('Hello World'))app.listen(3000, () => console.log('Start Server, listening on port 3000!'))

上面代码,引入了express模块,它在后台常驻运行,并监听3000端口,当客户端发起请求后,响应“Hello World”字符串。
启动后端服务:
node index.js

启动后,在浏览器访问http://localhost:3000,就可以看到Hello World!
常驻服务监听合约事件
先修改Crowdfunding合约,加入一个事件。然后在receive函数中触发该事件:
contract Crowdfunding { ...// address[] joinAccouts; event Join(address indexed user, uint price); ...// 用户向合约转账时,触发的回调函数 receive() external payable { ...emit Join(msg.sender, msg.value); //48820gas }...}

修改完合约后,使用truffle migrate --reset重新编译部署。
回到后端,在server目录下安装web3:
npm install web3 --save

然后修改index.js,加入监听Join事件的代码:
// express 启动代码 ...// 引入web库 var Web3 = require('web3'); // 使用WebSocket协议 连接节点 let web3 = new Web3(new Web3.providers.WebsocketProvider('ws://localhost:7545')); // 获取合约实例 var Crowdfunding = require('../build/contracts/Crowdfunding.json'); const crowdFund = new web3.eth.Contract( Crowdfunding.abi, Crowdfunding.networks[5777].address ); //监听Join事件 crowdFund.events.Join(function(error, event) { if (error) { console.log(error); }// 打印出交易hash 及区块号 console.log("交易hash:" + event.transactionHash); console.log("区块高度:" + event.blockNumber); // 获得监听到的数据: console.log("参与地址:" + event.returnValues.user); console.log("参与金额:" + event.returnValues.price); });

在初始化web3时,使用了WebsocketProvider,通过WebSocket通信协议与节点通信,如果是使用Geth节点,需要使用--ws开启服务,开发使用的Ganache默认开启了WebSocket服务。
重新启动后台服务:
node index.js

启动前端服务:
npm run serve

在浏览器里访问http://localhost:8080,进入前端页面,点击“参与众筹”(如果当前账户参与过,就切换不同账号参与众筹),切换到后端的命令行控制台窗口,可以看到打印出日志:
交易hash:0xe45848494c6990469db6de4485708ffb72d2d27bbc64cde92ff67d11394176ad 区块高度:6 参与地址:0xa75a4da940a2dDcE9db7e4FbCC62e236558DbEaA 参与金额:20000000000000000

为了方便管理,我们可以把这部分业务数据写入到中心化的数据库里。
MySql数据库环境准备
安装Mysql数据库,并创建以下数据库和表来存储众筹数据:
CREATE DATABASE crowdfund; use crowdfund; CREATE TABLE joins( id INT UNIQUE AUTO_INCREMENT, address VARCHAR(42) UNIQUE, price FLOAT NOT NULL, tx VARCHAR(66) NOT NULL, block_no INT NOT NULL, created_at datetime, PRIMARY KEY (id) );

监听数据入库
给后台安装node-mysql驱动:
cd server npm install mysql --save

修改index.js,引入mysql,并加入一个插入数据库函数:
var mysql= require('mysql'); function getConn() { return mysql.createConnection({ host: 'localhost', user: 'root', password : '123456', port: '3306', database : 'crowdfund' }); }function insertJoins(address, price, tx, blockNo) { // 连接数据库 var connection = getConn(); connection.connect(); // 构建插入语句 const query = `INSERT into joins ( address, price, tx, block_no, created_at ) Values (?,?,?,?,NOW())`; const params = [address, price, tx, blockNo]; // 执行插入操作 connection.query(query, params, function (error, results) { if (error) throw error; // console.log('results=> ' + results); }); connection.end(); }

并在监听打印日志的后面调用insertJoins()函数:
//监听Join事件 crowdFund.events.Join(function(error, event) { ...insertJoins(event.returnValues.user, // 把以wei为单位的价格转为ether单位 web3.utils.fromWei(event.returnValues.price), event.transactionHash, event.blockNumber)});

重启后台服务:
node index.js

在DAPP前端页面单击“参与众筹”后(如果当前账户参与过,就切换不同账号参与众筹),如果一切正常,就可以在数据库查询到相应的众筹记录:
以太坊实践整理(五)DApp开发全过程记录(下)
文章图片

为前端提供众筹记录显示
通过读取数据库的数据,并在Express加入一个路由,接受前端请求后返回读取到的数据,在index.js先加入一个getJoins()函数来读取数据库数据:
// 通过一个回调函数把结果返回出去 function getJoins(callback) { // 获取数据库链接 var connection = getConn(); connection.connect(); // 查询 SQL const query = `SELECT address, price from joins`; const params = []; // 查询数据库 connection.query(query, params, (err, rows)=>{ if(err){ return callback(err); } console.log(`result=>`, rows); callback(rows); }); connection.end(); }

加入一个新路由/joins:
app.get('/joins', (req, res) => { getJoins( rows=> { //设置允许跨域访问 res.set({'Access-Control-Allow-Origin': '*'}) .send(rows) }); })

重启后端服务,浏览器访问http://localhost:3000/joins,可以看到返回参与众筹的JSON数据:
以太坊实践整理(五)DApp开发全过程记录(下)
文章图片

后端准备好了后,接下来我们让前端通过Ajax请求访问http://localhost:3000/joins接口获取众筹数据列表。
在前端项目根目录下,安装axios:
npm install axios

修改CrowdFund.vue,加入获取众筹列表的getJoins()函数:
... import axios from 'axios'; export default { name: 'CrowdFund', data() { return { ... joinList: [], }; },// 当前Vue组件被创建时回调的hook 函数 async created() { ... this.getJoins() },methods: { ... // 获取众筹列表 getJoins() { axios.get('http://localhost:3000/joins') .then(response => { this.joinList = response.data }) .catch(function (error) { // Ajax请求失败处理 console.log(error); }); },} }

最后还需要在HTML模板中加入joinList的渲染:
金额: {{ item.price }}

启动前端应用:
npm run serve

浏览器访问http://localhost:8000,在MetaMask中切换到创作者账号,刷新浏览器后可以看到输出的众筹列表:
以太坊实践整理(五)DApp开发全过程记录(下)
文章图片

【以太坊实践整理(五)DApp开发全过程记录(下)】在众筹DAPP案例中,我们通过Vue.js开发应用前端,使用Node.js后端监控合约事件,并将一些核心业务数据用中心化数据库进行了存储。这就囊括了所有在DAPP开发中涉及的内容了。

    推荐阅读