區(qū)塊鏈技術(shù)的浪潮中,去中心化應(yīng)用(DApp)正逐漸從概念走向現(xiàn)實,以太坊,作為最具影響力的智能合約平臺,為DApp的開發(fā)提供了強大的基礎(chǔ)設(shè)施,本文將通過一個簡單但完整的實例,帶你一步步了解以太坊DApp的開發(fā)流程,從智能合約編寫到前端交互,讓你對DApp開發(fā)有一個直觀的認(rèn)識。

DApp概述與核心組成部分

一個典型的以太坊DApp通常由以下幾個核心部分組成:

  1. 智能合約(Smart Contract):運行在以太坊區(qū)塊鏈上的后端代碼,負(fù)責(zé)定義應(yīng)用的業(yè)務(wù)邏輯和數(shù)據(jù)規(guī)則,使用Solidity語言編寫,部署在以太坊網(wǎng)絡(luò)上。
  2. 前端(Frontend):用戶與DApp交互的界面,通常使用Web技術(shù)(HTML, CSS, JavaScript)構(gòu)建,它通過調(diào)用智能合約的方法與區(qū)塊鏈進(jìn)行交互。
  3. 區(qū)塊鏈(Blockchain):提供去中心化、不可篡改的數(shù)據(jù)存儲和交易執(zhí)行環(huán)境。

開發(fā)環(huán)境準(zhǔn)備

在開始之前,我們需要準(zhǔn)備以下開發(fā)工具和環(huán)境:

  1. Node.js 和 npm:JavaScript運行時環(huán)境和包管理器。
  2. Truffle Suite:流行的以太坊開發(fā)框架,包含Truffle(開發(fā)環(huán)境、測試框架、構(gòu)建管道)、Ganache(個人區(qū)塊鏈,用于本地測試)和Drizzle(與前端交互的庫)。
  3. MetaMask:瀏覽器錢包插件,允許用戶與以太坊區(qū)塊鏈交互,管理私鑰和進(jìn)行交易簽名。
  4. 代碼編輯器:如Visual Studio Code。

安裝步驟相對簡單,請訪問各工具的官方網(wǎng)站下載并安裝相應(yīng)版本。

DApp開發(fā)實例:一個簡單的“任務(wù)清單”(Todo List)DApp

我們將開發(fā)一個簡單的Todo List DApp,用戶可以添加任務(wù)、標(biāo)記任務(wù)完成和查看所有任務(wù)。

步驟1:創(chuàng)建項目并初始化

  1. 創(chuàng)建一個新的項目目錄,例如ethereum-dapp-tutorial。
  2. 在終端中進(jìn)入該目錄,并使用Truffle初始化項目:
    truffle init
  3. 這將創(chuàng)建以下標(biāo)準(zhǔn)目錄結(jié)構(gòu):
    • contracts/:存放智能合約文件。
    • migrations/:存放部署腳本文件。
    • test/:存放測試文件。
    • truffle-config.js:Truffle配置文件。

步驟2:編寫智能合約

  1. contracts目錄下,創(chuàng)建一個新的Solidity文件,命名為TodoList.sol。

  2. 編寫智能合約代碼:

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    contract TodoList {
        // 定義一個結(jié)構(gòu)體來表示任務(wù)
        struct Task {
            uint id;
            string content;
            bool completed;
        }
        // 任務(wù)數(shù)組
        Task[] public tasks;
        // 任務(wù)計數(shù)器
        uint public taskCount = 0;
        // 添加任務(wù)的函數(shù)
        function createTask(string memory _content) public {
            taskCount++;
            tasks.push(Task(taskCount, _content, false));
        }
        // 切換任務(wù)完成狀態(tài)的函數(shù)
        function toggleCompleted(uint _id) public {
            Task storage task = tasks[_id - 1]; // 數(shù)組索引從0開始
            task.completed = !task.completed;
        }
        // 可選:獲取任務(wù)數(shù)量
        function getTaskCount() public view returns (uint) {
            return taskCount;
        }
    }

    這個合約定義了任務(wù)的結(jié)構(gòu),提供了創(chuàng)建任務(wù)和切換任務(wù)完成狀態(tài)的方法。

步驟3:編譯智能合約

  1. 在終端中,確保在項目根目錄下,運行:
    truffle compile
  2. 如果編譯成功,Truffle會在build/contracts目錄下生成對應(yīng)的ABI(Application Binary Interface)和字節(jié)碼文件,ABI是前端與智能合約交互的橋梁。

步驟4:編寫部署腳本

  1. migrations目錄下,創(chuàng)建一個新的遷移腳本文件,命名為2_deploy_contracts.js(數(shù)字前綴表示部署順序)。

  2. 編寫部署腳本:

    const TodoList = artifacts.require("TodoList");
    module.exports = function (deployer) {
      deployer.deploy(TodoList);
    };

步驟5:部署到本地測試網(wǎng)絡(luò)(Ganache)

  1. 確保Ganache已經(jīng)啟動并運行在默認(rèn)端口(7545)。
  2. truffle-config.js中配置本地網(wǎng)絡(luò):
    module.exports = {
      networks: {
        development: {
          host: "127.0.0.1",
          port: 7545, // Ganache默認(rèn)端口
          network_id: "*", // 匹配任何network_id
        },
      },
      compilers: {
        solc: {
          version: "0.8.0", // 指定Solidity編譯器版本
        },
      },
    };
  3. 在終端中運行部署命令:
    truffle migrate --network development
  4. 如果部署成功,你會在Ganache界面上看到新的交易和地址變化,部署的合約地址也會顯示在終端中。

步驟6:構(gòu)建前端界面

  1. 在項目根目錄下創(chuàng)建一個src目錄(或public目錄),用于存放前端文件。

  2. src目錄下創(chuàng)建以下文件:

    • index.html:主頁面
    • style.css:樣式文件(可選,用于美化界面)
    • app.js:前端邏輯
  3. index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>以太坊Todo List DApp</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <h1>以太坊Todo List</h1>
            <div class="form">
                <input type="text" id="taskInput" placeholder="輸入新任務(wù)...">
                <button id="addTaskBtn">添加任務(wù)</button>
            </div>
            <ul id="taskList">
                <!-- 任務(wù)列表將在這里動態(tài)生成 -->
            </ul>
        </div>
        <script src="https://cdn.ethers.io/lib/ethers-5.2.umd.min.js" type="application/javascript"></script>
        <script src="app.js"></script>
    </body>
    </html>
  4. app.js 內(nèi)容(核心交互邏輯):

    // 聲明變量
    let todoList;
    const todoListAddress = "0x..."; // 替換為你的合約部署地址
    const todoListABI = [/* 這里粘貼你的TodoList合約的ABI */]; // 從build/contracts/TodoList.json中獲取
    // 初始化函數(shù)
    async function init() {
        // 連接到以太坊網(wǎng)絡(luò)(通過MetaMask)
        if (typeof window.ethereum !== 'undefined') {
            console.log('MetaMask is installed!');
            try {
                // 請求賬戶訪問
                await window.ethereum.request({ method: 'eth_requestAccounts' });
                const provider = new ethers.providers.Web3Provider(window.ethereum);
                const signer = provider.getSigner();
                todoList = new ethers.Contract(todoListAddress, todoListABI, signer);
                // 監(jiān)聽事件并刷新任務(wù)列表
                todoList.on('TaskCreated', (id, content, completed) => {
                    console.log('Task created:', id, content, completed);
                    loadTasks();
                });
                todoList.on('TaskCompleted', (id, completed) => {
                    console.log('Task completed:', id, completed);
                    loadTasks();
                });
                loadTasks();
            } catch (error) {
                console.error('User denied account access', error);
            }
        } else {
            console.error('MetaMask is not installed. Please install it to use this DApp.');
            alert('請安裝MetaMask錢包!');
        }
    }
    // 加載任務(wù)列
    隨機配圖
    表 async function loadTasks() { const taskCount = await todoList.getTaskCount(); const taskListElement = document.getElementById('taskList'); taskListElement.innerHTML = ''; // 清空現(xiàn)有列表 for (let i = 1; i <= taskCount; i++) { const task = await todoList.tasks(i - 1); // 數(shù)組索引從0開始 const li = document.createElement('li'); li.innerHTML = ` <span class="${task.completed ? 'completed' : ''}">${task.content}</span> <button onclick="toggleTaskCompleted(${task.id})">${task.completed ? '未完成' : '完成'}</button> `; taskListElement.appendChild(li); } }