1. 程式人生 > >DAPP開發初探

DAPP開發初探

前言

最近DAPP的開發貌似很火,學習了區塊鏈的一些知識之後,相信有很多人和我一樣,也想了解開發一個DAPP是一個怎樣的流程。

下面將通過一個簡單的栗子來初識一下DAPP的開發流程,屆時,我們也將開發出第一個DAPP應用–《永存的留言》。

專案介紹

《永存的留言》是一個基於以太坊的線上留言平臺。它的功能十分簡單–使用者可以在平臺上進行留言,平臺每10s隨機的展示留言內容。
但是它的特點在於,利用區塊鏈的特性,保證了資料的真實性、完整性和安全性。
這裡寫圖片描述

  • 使用Solidity開發後端方法
  • 使用Truffle框架
  • 基於unbox react腳手架專案
  • 部署在以太坊測試網路上Ropoetn Test Network
  • 使用MetaMask錢包外掛釋出交易

開發步驟

下載react專案模板

確保本地已經準備好Truffle所需的環境,準備以下命令,下載react專案模板。
$ mkdir a && cd a
truffle unbox react
當看到 Unbox successful. Sweet!提示時,表明下載成功。

編寫智慧合約

這是我們唯一的實現合約,包含的傳送留言和讀取留言的方法。

pragma solidity ^0.4.19;

contract SimpleStorage {
    // 留言結構體
    struct Message {
        string word; // 留言
address from; // 留言者地址 string timestamp ; // 留言unix時間戳 } Message[] private wordArr; /** * 寫入留言的方法 */ function setWord(string s, string t) public { wordArr.push(Message({ word: s, from: msg.sender, timestamp: t })); } /** * 獲取隨機留言的方法 */
function getRandomWord(uint random) public view returns (uint, string, address, string) { if(wordArr.length==0){ return (0, "", msg.sender, ""); }else{ Message storage result = wordArr[random]; return (wordArr.length, result.word, result.from, result.timestamp); } } }

編譯、部署合約

修改釋出的指令碼。

var SimpleStorage = artifacts.require("./SimpleStorage.sol");
//var Words = artifacts.require("Words");

module.exports = function(deployer) {
  deployer.deploy(SimpleStorage);
  //deployer.deploy(Words);
};

執行truffle compile進行合約的編譯。

獲取合約地址。

  • 這裡我們開啟MetaMask錢包外掛,左上角選擇Ropoetn Test Network網路.
    這裡寫圖片描述
  • 利用Remix編譯,釋出合約到以太坊測試環境。
    這裡寫圖片描述
  • 通過MetaMask的交易hash查詢,獲取已經部署到以太坊測試網路中的合約地址。
    這裡寫圖片描述

編寫前端頁面

這個部分主要是編寫前端的展示效果和與合約互動的邏輯,這一部分最難編寫,也最耗時間。

  • 主要邏輯程式碼
const contractAddress = "0x39e5196750dcddb1aaf6dda7d6e8dbb633482905" // 合約地址(以太坊測試網路)
var simpleStorageInstance // 合約例項

class App extends Component {
  // 初始化構造
  constructor(props) {
    super(props)
    this.state = {
        word: null,
        from: null,
        timestamp: null,
        random: 0,
        count: 0,
        input: '',
        web3: null,
        emptyTip: "還沒有留言,快來建立全世界第一條留言吧~",
        firstTimeLoad: true,
        loading: false,
        loadingTip: "留言正在寫入,請耐心等待~",
        waitingTip: "留言正在寫入,請耐心等待~",
        successTip: "留言成功",
        animate: "",
        in: css(styles.in),
        out: css(styles.out)
    }
  }

  // 獲取Web3例項
  componentWillMount() {
    // Get network provider and web3 instance.
    getWeb3
    .then(results => {
      this.setState({
        web3: results.web3
      })
      // Instantiate contract once web3 provided.
      this.instantiateContract()
    })
    .catch(() => {
      console.log('Error finding web3.')
    })
  }

  // 獲取合約例項
  instantiateContract() {
     /*
     * SMART CONTRACT EXAMPLE
     *
     * Normally these functions would be called in the context of a
     * state management library, but for convenience I've placed them here.
     */
    const contract = require('truffle-contract')
    const simpleStorage = contract(SimpleStorageContract)
    simpleStorage.setProvider(this.state.web3.currentProvider)

    // Get accounts.
    this.state.web3.eth.getAccounts((error, accounts) => {
      simpleStorage.at(contractAddress).then(instance => {
        simpleStorageInstance = instance

        /*simpleStorage.deployed().then((instance) => {
        simpleStorageInstance = instance // 部署本地Ganache*/
        console.log("合約例項獲取成功")
      })
      .then(result => {
        return simpleStorageInstance.getRandomWord(this.state.random)
      })
      .then(result => {
                console.log("讀取成功", result)
                if(result[1]!=this.setState.word){
                    this.setState({
                        animate: this.state.out
                    })
                    setTimeout(() => {
                        this.setState({
                            count: result[0].c[0],
                            word: result[1],
                            from: result[2],
                            timestamp: result[3],
                            animate: this.state.in,
                            firstTimeLoad: false
                        })
                    }, 2000)
                }else{
                    this.setState({
                        firstTimeLoad: false
                    })
                }
        this.randerWord()
      })

    })
  }

  // 迴圈從區塊上隨機讀取留言
  randerWord() {
    setInterval(() => {
      let random_num = Math.random() * (this.state.count? this.state.count: 0)
      this.setState({
        random: parseInt(random_num)
      })
      console.log("setInterval讀取", this.state.random)
      simpleStorageInstance.getRandomWord(this.state.random)
      .then(result => {
                console.log("setInterval讀取成功", result)
                if(result[1]!=this.setState.word){
                    this.setState({
                        animate: this.state.out
                    })
                    setTimeout(() => {
                        this.setState({
                            count: result[0].c[0],
                            word: result[1],
                            from: result[2],
                            timestamp: result[3],
                            animate: this.state.in
                        })
                    }, 2000)
                }
      })
    }, 10000)
  }

   // 寫入區塊鏈
  setWord(){
        if(!this.state.input) return
        this.setState({
            loading: true
        })
    let timestamp = new Date().getTime()
    simpleStorageInstance.setWord(this.state.input, String(timestamp), {from: this.state.web3.eth.accounts[0]})
    .then(result => {
            this.setState({
                loadingTip: this.state.successTip
            })
            setTimeout(() => {
                this.setState({
                    loading: false,
                    input: '',
                    loadingTip: this.state.waitingTip
                })
            }, 1500)

        })
        .catch(e => {
            // 拒絕支付
            this.setState({
                loading: false
            })
        })
  }
  // 時間戳轉義
  formatTime(timestamp) {
      let date = new Date(Number(timestamp))
      let year = date.getFullYear()
      let month = date.getMonth() + 1
      let day = date.getDate()
      let hour = date.getHours()
      let minute = date.getMinutes()
      let second = date.getSeconds()
      let fDate = [year, month, day, ].map(this.formatNumber)
      return fDate[0] + '年' + fDate[1] + '月' + fDate[2] + '日' + ' ' + [hour, minute, second].map(this.formatNumber).join(':') 
  }
  /** 小於10的數字前面加0 */
  formatNumber(n) {
      n = n.toString()
      return n[1] ? n : '0' + n
  }

}

執行專案

使用npm start啟動專案,瀏覽器的3000埠執行。
效果如下(一定要連線到Ropoetn Test Network網路才可以)。
這裡寫圖片描述

總結

這樣我們就開發出了我們的第一個DAPP,體會了開發的基本流程。

  • 明確專案需求
  • 確定開發環境
  • 編寫、測試、部署合約
  • 設計前端頁面,使前後端互動
  • 釋出測試

專案原始碼

參考文章