以太坊学习路线——(五)DApp开发:简易版去中心化微博

这篇博客演示的基本操作系统环境是CentOS 7,参考书籍:以太坊开发实战——以太坊关键技术与案例分析 第十一章(吴寿鹤、冯翔、刘涛、周广益   著)。

项目地址,包含该项目所需大部分文件,前端因为依赖包太多,大家自己执行命令下载一下,博文末尾有相关执行命令。

我所上传的文件结构:(已通过审核)

eth-weibo/
├── app
│   ├── dist
│   ├── src
│   │   ├── css
│   │   │   ├── dist
│   │   │   │   ├── css
│   │   │   │   │   ├── bootstrap.css
│   │   │   │   │   ├── bootstrap.css.map
│   │   │   │   │   ├── bootstrap.min.css
│   │   │   │   │   ├── bootstrap.min.css.map
│   │   │   │   │   ├── bootstrap-theme.css
│   │   │   │   │   ├── bootstrap-theme.css.map
│   │   │   │   │   ├── bootstrap-theme.min.css
│   │   │   │   │   └── bootstrap-theme.min.css.map
│   │   │   │   ├── fonts
│   │   │   │   │   ├── glyphicons-halflings-regular.eot
│   │   │   │   │   ├── glyphicons-halflings-regular.svg
│   │   │   │   │   ├── glyphicons-halflings-regular.ttf
│   │   │   │   │   ├── glyphicons-halflings-regular.woff
│   │   │   │   │   └── glyphicons-halflings-regular.woff2
│   │   │   │   └── js
│   │   │   │       ├── bootstrap.js
│   │   │   │       ├── bootstrap.min.js
│   │   │   │       └── npm.js
│   │   │   └── style.css
│   │   ├── pages
│   │   │   └── index.html
│   │   └── scripts
│   │       ├── app.js
│   │       └── jQuery.js
│   └── webpack.config.js
├── contracts
│   ├── Migrations.sol
│   ├── WeiboAccount.sol
│   └── WeiboRegistry.sol
├── migrations
│   ├── 1_initial_migration.js
│   └── 2_weiboregistry_migration.js
└── truffle-config.js

一、DApps架构

DApps(decentralized applications)即去中心化应用。传统Web应用是运行在TCP/IP四层模型上的,在TCP/IP网络上传递的是数据。而区块链相当于在TCP/IP四层模型上又加上了一层价值传递层,使原有的四层模型变为五层。DApps运行在区块链上,所以应用天然具有价值转移的功能,这是DApps与Web应用最大的不同。Web需要一个中心化的服务机构来进行服务运作,而DApps的业务逻辑是以智能合约的方式部署在区块链上的,且智能合约产生的数据也是存储在区块链上的,这意味着DApps的业务逻辑、数据都是去中心化的。DApps与Web应用区别:

 Web应用DApps应用
前端HTML、CSS、JavascriptHTML、CSS、Javascript
逻辑Java等高级语言,部署在Web服务器上Solidity语言编写,部署在以太坊区块链上
数据存储在数据库中存储在以太坊上
可使用货币法比以太坊的内置货币或合约创建的货币

二、去中心化微博开发

这是一个运行在以太坊上的去中心化微博系统,去中心化意味着没有一个中心机构能够控制你发送的微博,你发送的微博是由你完全控制的,任何人无法删除、关闭你的微博。一旦你的微博发出去后,只有你自己能够删除它。微博系统功能如下:

微博内容长度限制在160个字符以下。

微博账户可以接收打赏、捐赠,货币是以太币。

WeiboRegistry合约相当于一个平台,一个展示微博帐号的平台。我们自己创建了一个微博合约后需要到一个平台上注册一下,这样其他人就可以通过平台找到我们了,就可以和我们互动。即使平台被删除、屏蔽后我们自己的微博合约还是存在的。

如下图,平台信息部分可以看到平台地址和打赏,已注册微博用户部分可以看到所有注册过的用户信息。注册部分可以通过在weibo name对应的输入框输入id来注册,成功后,会在右侧的weibo adddress对应的输入框显示注册成功所得到的地址,并且刷新已注册用户区域的数据。

WeiboAccount是一个比较重要的合约,每当我们需要创建一个微博帐号时就需要部署一个WeiboAccount合约。合约部署完成后会返回一个合约地址,那就是我们的微博帐号。我们想发送微博时就调用WeiboAccount里的方法,这样我们发送的所有微博就会存在WeiboAccount合约中。

如下图,我的微博页面,需要先登录才能使用,将微博平台所注册的地址输入到登录输入框,然后点击登录,即可成功登录(非法地址将无法登录成功)。账户信息部分由用户的id、用户地址、以及用户打赏金额。发微博部分只有登录后才能使用,否则点击发送按钮会提醒先登录。我的微博部分也是只有登录后才能看到用户所有已发布的微博信息。

 环境:该项目在linux系统所编写并测试成功的,需要node环境,请自行搜索安装搭建node环境。

            需要:Truffle安装

            需要:Testrpc

1.创建项目

//创建项目
[root@localhost opt]# mkdir eth-weibo
[root@localhost opt]# cd eth-weibo/

//通过truffle命令初始化dapp,前端使用webpack
[root@localhost eth-weibo]# truffle unbox webpack

这需要等待一段时间来下载相关文件和库,完成后显示:

✔ Preparing to download
✔ Downloading
✔ Cleaning up temporary files
✔ Setting up box

Unbox successful. Sweet!

Commands:

  Compile:              truffle compile
  Migrate:              truffle migrate
  Test contracts:       truffle test
  Run dev server:       cd app && npm run dev
  Build for production: cd app && npm run build

哦!!!注意你的相关环境和版本,我的Truffle安装相关版本信息如下:

//我的truffle版本
[root@localhost myproject]# truffle version
Truffle v5.0.10 (core: 5.0.10)
Solidity v0.5.0 (solc-js)
Node v11.13.0
Web3.js v1.0.0-beta.37

项目根目录结构如下: 

[root@localhost eth-weibo]# tree -L 1 ../eth-weibo/
../eth-weibo/
├── app                  //存放前端应用相关文件
├── contracts            //存放所编写的solidity文件
├── migrations           //存放合约文件的迁移脚本
├── test                 //存放测试文件,可通过truffle test命令进行合约测试
└── truffle-config.js    //truffle项目的配置文件

由于该版本使用truffle命令初始化后会默认存在一个metaCoin的应用所以需要作相关文件删除,大家也可以去运行测试该项目案例,网上教程很多,大家自己上网学习。我写这个应用的时候也借鉴了该metaCoin应用相关的处理方式,看看别人的应用源码,可以学习相关环境连接处理方式和别人的编码风格:

//1.删除contracts目录下其他文件,只保留迁移合约Migrations.sol
contracts/
└── Migrations.sol

//2.删除migrates目录下其他文件,只保留迁移合约的迁移脚本1_initial_migration.js
migrations/
└── 1_initial_migration.js

//3.删除test目录下所有测试文件
test/

0 directories, 0 files

 而对于truffle-config.js,该文件中有好几种运行环境的配置templete,在本地测试的话,只需要如下内容就够了:

module.exports = {
  networks: {
     development: {
      host: "127.0.0.1",     // Localhost (default: none)
      port: 8545,            // Standard Ethereum port (default: none)
      network_id: "*",       // Any network (default: none)
     },
  },
}

 2.合约

(1).WeiboAccount合约

WeiboAccount合约存储我们发送的微博内容,每一个微博账户对应一个WeiboAccount合约,WeiboAccount合约的所有者是合约创建者,所以你的微博只能由你自己管理,没有任何一个机构能够删除你的微博合约。WeiboAccount合约主要包含以下几个功能:

pragma solidity >=0.4.21 <0.6.0;

/**
    微博账户
*/
contract WeiboAccount {
	
	struct Weibo {
		uint timestamp;
		string weiboString;
	}

        //这个微博账户的所有微博,weiboID映射微博内容
	mapping (uint => Weibo) _weibos;

        //账户发的微博数量
	uint _numberOfWeibos;

        //微博账户的所有者
	address _adminAddress;

        //权限控制,被这个修饰符修饰的方法,表示该方法只能被微博所有者操作
	modifier onlyAdmin {
		require(msg.sender == _adminAddress);
		_;
	}

        //微博合约的构造方法
	constructor() public {
		_numberOfWeibos = 0;
		_adminAddress = msg.sender;
	}

        //发新微博
	function weibo(string memory weiboString) onlyAdmin public {
		require(bytes(weiboString).length <= 160);

		_weibos[_numberOfWeibos].timestamp = now;
		_weibos[_numberOfWeibos].weiboString = weiboString;
		_numberOfWeibos++;
	}

        //根据ID查找微博
	function getWeibo(uint weiboId) view public returns (string memory weiboString, uint timestamp) {
		weiboString = _weibos[weiboId].weiboString;
		timestamp = _weibos[weiboId].timestamp;
	}

        //返回最新一条微博
	function getLatestWeibo() view public returns (string memory weiboString, uint timestamp, uint numberOfWeibos) {
		weiboString = _weibos[_numberOfWeibos - 1].weiboString;
		timestamp = _weibos[_numberOfWeibos - 1].timestamp;
		numberOfWeibos = _numberOfWeibos;
	}

        //返回账户所有者
	function getOwnerAddress() view public returns (address adminAddress) {
		return _adminAddress;
	}

        //返回微博总数
	function getNumberOfWeibos() view public returns (uint numberOfWeibos) {
		return _numberOfWeibos;
	}

        //取回打赏
	function adminRetrieveDonations(address payable receiver) public {
		assert(receiver.send(address(this).balance));
	}

        //摧毁合约
	function adminDeleteAccount() onlyAdmin public {
		selfdestruct(msg.sender);
	}

        //记录每条打赏记录
	event LogDonate(address indexed from, uint256 _amount);

        //接受别人的打赏
	function() external payable {
		emit LogDonate(msg.sender, msg.value);
	}
}

 (2).WeiboRegistry合约

WeiboRegistry合约为我们提供了一个展示微博帐号的平台,在WeiboRegistry维护着账户昵称、账户ID到WeiboRegistry合约之间的映射关系,这样其他人就可以通过平台找到我们,可以和我们进行互动。即便平台被删除后,我们自己的微博合约还是存在的。WeiboRegistry合约:

pragma solidity >=0.4.21 <0.6.0;

/**
    微博管理平台
*/
contract WeiboRegistry {

        //根据账户昵称、ID、地址查找微博账户
	mapping (address => string) _addressToAccountName;
	mapping (uint => address) _accountIdToAccountAddress;
	mapping (string => address) _accountNameToAddress;
    
        //平台上的注册账户数量
	uint _numberOfAccounts;
        //微博平台管理员
	address _registryAdmin;

        //权限控制,被这个修饰符修饰的方法,表示该方法只能被微博所有者操作
	modifier onlyRegistryAdmin {
		require(msg.sender == _registryAdmin);
		_;
	}

        //微博平台构造函数
	constructor() public {
		_registryAdmin = msg.sender;
		_numberOfAccounts = 0;
	}

        //微博平台上注册微博:用户名,微博帐号
	function register(string memory name, address accountAddress) public {
            //帐号之前未注册过
		require(_accountNameToAddress[name] == address(0));
            //昵称之前未注册过
		require(bytes(_addressToAccountName[accountAddress]).length == 0);
            //昵称不能超过64个字符
		require(bytes(name).length < 64);

		_addressToAccountName[accountAddress] = name;
		_accountNameToAddress[name] = accountAddress;
		_accountIdToAccountAddress[_numberOfAccounts] = accountAddress;
		_numberOfAccounts++;		
	}

        //返回已注册账户数量
	function getNumberOfAccounts() view public returns (uint numberOfAccounts) {
		numberOfAccounts = _numberOfAccounts;
	}

        //返回昵称对应的微博账户地址
	function getAddressOfName(string memory name) view public returns (address addr) {
		addr = _accountNameToAddress[name];
	}

        //返回与微博账户地址对应的昵称
	function getNameOfAddress(address addr) view public returns (string memory name) {
		name = _addressToAccountName[addr];
	}
	
        //根据ID返回账户
	function getAddressOfId(uint id) view public returns (address addr) {
		addr = _accountIdToAccountAddress[id];
	}

        //取回打赏
	function adminRetrieveDonations(address payable receiver) public onlyRegistryAdmin {
		assert(receiver.send(address(this).balance));
	}

        //摧毁合约
	function adminDeleteRegistry() public onlyRegistryAdmin {
		selfdestruct(msg.sender);
	}

        //记录每条打赏记录
	event LogDonate(address indexed from,uint256 _amount);

        //接受别人的打赏
	function() external payable {
		emit LogDonate(msg.sender,msg.value);
	}
}

 智能合约是区块链技术体系中非常重要的一环。将这两个sol文件放在contracts目录下。完成后contracts目录结构如下:

contracts/
├── Migrations.sol
├── WeiboAccount.sol
└── WeiboRegistry.sol

 (3).部署合约

这个应用在Truffle中只需要部署WeiRegistry合约就可以了,WeiboAccount合约可以通过前端页面部署。在部署WeiRegistry合约需要编写其迁移即脚本:(存放在migrations目录下)

$ cat migrations/2_weiboregistry_migration.js 

//迁移脚本如下:
var WeiboRegistry = artifacts.require("WeiboRegistry");

module.exports = function(deployer) {
  deployer.deploy(WeiboRegistry);
};

完成后migrations目录结构如下:

migrations/
├── 1_initial_migration.js
└── 2_weiboregistry_migration.js

然后通过truffle compile 命令编译合约,编译成功后会在项目根目录下新建build文件夹,其中的contracts目录里面存放着我们的合约被编译后生成的json文件。build目录结构如下:

build/
└── contracts
    ├── Migrations.json
    ├── WeiboAccount.json
    └── WeiboRegistry.json

 然后先重新打开一个终端,运行一个Testrpc测试服务(也可使用Ganache服务或truffle develop命令,但要注意端口不同),testrpc服务启动后,在之前的终端执行truffle migrate进行合约部署,部署成功的话,会打印部署信息,testrpc服务终端也会打印相关日志。如下图,左上角窗口运行truffle migrate命令,右上角运行testrpc服务。

如果以上步骤运行成功,那么整个项目目录结构如下图,其中test目录存放测试文件,由于没有编写任何测试文件所以海目录为空。所以至此就剩下app目录需要处理,app目录下存放的便是前端应用文件,由于node_modules目录下有大量创建项目时下载的包,下图中省略了其大部分内容。

./
├── app
│   ├── node_modules
│   │   ├── accepts
 ……  ……  ……
│   │   └── yauzl
│   ├── package.json
│   ├── package-lock.json
│   ├── src
│   │   ├── index.html
│   │   └── index.js
│   └── webpack.config.js
├── build
│   └── contracts
│       ├── Migrations.json
│       ├── WeiboAccount.json
│       └── WeiboRegistry.json
├── contracts
│   ├── Migrations.sol
│   ├── WeiboAccount.sol
│   └── WeiboRegistry.sol
├── migrations
│   ├── 1_initial_migration.js
│   └── 2_weiboregistry_migration.js
├── test
└── truffle-config.js

3.前端应用 

至此,我们只需要关注app目录下的前端应用部分,app目录结构如下:

./
├── dist                     //项目编译成功后,运行文件存放在该目录下(由
|                            //webpack.config.js文件中的配置所决定)。
├── node_modules             //前端所需要的各种依赖包
├── package.json             //npm包管理配置文件
├── package-lock.json        //npm包管理文件
├── src                      //前端页面文件宝括html、css、js文件
└── webpack.config.js        //webpack项目配置文件

 (1)、webpack.config.js文件:

首先配置文件webpack.config.js如下图所视设置,设置了入口文件(./src/scripts/app.js)、输出文件(bundle.js)、前端项目输出目录(dist)、主html文件(index.html),添加了相关模块:文件加载(file-loader)、css模块、js、jQuery、字体……

const path = require("path");
const CopyWebpackPlugin = require("copy-webpack-plugin");
//const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'development',
  entry: "./src/scripts/app.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
  plugins: [
    new CopyWebpackPlugin([{ from: "./src/pages/index.html", to: "index.html" }]),
  ],
  resolve: {extensions: ['.js', '.vue', '.json'],
    alias: {'vue$': 'vue/dist/vue.esm.js', '@': './src'}
  },
  module: {
        rules: [
            { test: /\.(png|svg|jpg|gif)$/, use: ['file-loader']},
            { test: /\.(woff|woff2|eot|ttf|otf)$/, use: ['file-loader']},
            { test: /\.css$/, use: ['style-loader', 'css-loader']},
            { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader" },
            { test: /\.(woff|woff2)$/, loader: "file-loader?prefix=font/&limit=5000" },
            { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader?limit=10000&mimetype=application/octet-stream" },
            { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader?limit=10000&mimetype=image/svg+xml" },
	    { test: require.resolve('./src/scripts/jQuery.js'), 
              	use: [{loader: 'expose-loader', options: 'jQuery'}, {loader: 'expose-loader', options: '$'}]
            },
            { test: /\.(html|html)$/i, use: ['html-withimg-loader']},
        ]
  },
  devServer: { contentBase: path.join(__dirname, "dist"), compress: true },
};

 (2)、node_modules包目录

有些模块未下载需要使用下面的命令来下载:(注意执行命令时要和package.json、node_modules在同一目录下)

npm install expose-loader --save -dev     //下载expose-loader包
npm install file-loader --save -dev       //下载file-loader包
npm install style-loader --save -dev      //下载style-loader包
npm install css-loader --save -dev        //下载css-loader包
npm install truffle-contract --save -dev  //下载truffle-contract包

若大家在测试时有这样的错误:Module not found: Error: Can't resolve 'css-loader' in……
就按照上面的命令来下载对应的包,基本可以解决问题。

(3)、src目录结构如下。限于篇幅,只能打包上传到资源,包含该项目所需大部分文件,因为前端依赖包太多,大家自己执行上述命令下载一下。

src/
├── css
│   ├── dist
│   │   ├── css
│   │   │   ├── bootstrap.css
│   │   │   ├── bootstrap.css.map
│   │   │   ├── bootstrap.min.css
│   │   │   ├── bootstrap.min.css.map
│   │   │   ├── bootstrap-theme.css
│   │   │   ├── bootstrap-theme.css.map
│   │   │   ├── bootstrap-theme.min.css
│   │   │   └── bootstrap-theme.min.css.map
│   │   ├── fonts
│   │   │   ├── glyphicons-halflings-regular.eot
│   │   │   ├── glyphicons-halflings-regular.svg
│   │   │   ├── glyphicons-halflings-regular.ttf
│   │   │   ├── glyphicons-halflings-regular.woff
│   │   │   └── glyphicons-halflings-regular.woff2
│   │   └── js
│   │       ├── bootstrap.js
│   │       ├── bootstrap.min.js
│   │       └── npm.js
│   └── style.css
├── pages
│   └── index.html
└── scripts
    ├── jQuery.js
    └── app.js

4.项目测试 

按照上面的步骤处理完成后,就可以在app目录下执行npm run dev命令来启动web服务,在打印的信息中复制网页地址:

在 chrome浏览器(貌似只有chrome才能运行成功)中打开该网址,就可以看到如下内容(大家应该可以执行到这里,我在远程服务器上按照上面的步骤又重新操作了一遍,可以运行成功):

该项目运行机制大概可以概括为:先运行一个testrpc测试连的交互服务,然后将你编写的智能合约编译并部署到区块链服务(例如testrpc)中。并将合约编译结果json对象提供给前端,实则提供可调用API给前端。前端由于web3 API、抽象合约truffle-contract的存在,封装并提供给前端页面应用,供其交互。

三、问题与BUG

问题1:Error:base fee exceeds gas limit at runCall 或者Out of Gas

解决:原因是因为合约代码量较多,导致out of gas,可通过显式发送一个较大的gas

2000000,使用了(testrpc 默认gas:20000000000,默认gasLimit:90000)

问题2:then链内给变量所赋的值,在then链外调用该值时发现并未赋值?

解决:若需要then链方式对变量赋值,可以将所需要的值在then链最后return出来,赋值给该变量,调用时,在另一个同步函数中,使用await 的方式获取同步后该变量的值。比如:在函数fun1中需要通过then链对temp赋值,在函数fun2中获取该变量所赋的值。代码如下:

var temp = null;

function fun1() {
    temp = a.then(function(b) {
        return b;
    });
}

async function fun2() {
    var c = await temp;        //c就是同步后temp的值
}

问题3:火狐浏览器建立HttpProvider时,

警告:Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://127.0.0.1:8545/. (Reason: missing token ‘user-agent’ in CORS header ‘Access-Control-Allow-Headers’ from CORS preflight channel).

错误:Error: Invalid JSON RPC response: ""

解决:还未解决……

  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值