以太坊学习路线——(五)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、Javascript | HTML、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 boxUnbox successful. Sweet!Commands:Compile: truffle compileMigrate: truffle migrateTest contracts: truffle testRun dev server: cd app && npm run devBuild 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.js3.前端應用?
至此,我們只需要關注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.js4.項目測試?
按照上面的步驟處理完成后,就可以在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: ""
解決:還未解決……
總結
以上是生活随笔為你收集整理的以太坊学习路线——(五)DApp开发:简易版去中心化微博的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 项目实训 - 智能车系统 - 第七周记录
- 下一篇: 模电学习02:晶体三极管