如何构建一个以太坊网址缩短器去中心化应用程序(dApp)

  • QuickNode
  • 发布于 2024-10-30 23:38
  • 阅读 24

本文详细介绍了如何在以太坊上构建一个去中心化的URL缩短应用程序(dApp),包括必要的准备工作、智能合约的编写与解析、前端开发以及后端服务器的设置。文章不仅提供了完整的步骤和代码示例,还强调了去中心化应用在数据安全性和抗审查方面的优势,适合希望深入了解以太坊dApp开发的开发者。

概述

dApps(去中心化应用程序)是 Ethereum 开发生态系统的一个重要部分。已经有成千上万的 dApps 居住在 Ethereum 区块链上。在本指南中,我们将学习如何构建一个将短版 URL 存储在 Ethereum 上的 dApp。

什么是 dApp?

dApps 或去中心化应用程序与传统应用程序非常相似,它们也有前端和后端。不同之处在于,dApps 的后端运行在一个去中心化的点对点网络上,而传统应用程序的后端运行在中心化服务器上。

Ethereum dApps 由前端和智能合约组成。我们可以用任何能够与后端通信的语言构建前端/用户界面。前端也可以是去中心化的,并托管在 IPFS 或 Swarm 等存储服务上。

dApp 的后端,即智能合约,主要用 Solidity 编写——一种高级面向对象的语言,然后部署到 Ethereum 区块链网络上。前端随后可以通过执行底层智能合约代码的某些部分与智能合约进行交互。

要了解更多关于如何编写和部署智能合约的信息,请查看我们的 Solidity 指南

为什么要在 Ethereum 上构建 URL 缩短器?

自互联网诞生以来,它所处理的数据量持续以指数级别不断增加。所有这些信息——个人数据、财务数据、媒体等,均存在并由中央服务器处理。运营这些中央服务器的公司对这些数据拥有完全控制权,可以滥用、出售或完全拒绝这些他们在技术上并不拥有的数据的访问权。中心化服务器的方法对审查或其他形式的疏忽没有抵抗力。一个很好的例子是 谷歌关闭他们的 URL 缩短服务。在这一点上,dApps 相较于传统应用程序具有显著的优势;dApps 有助于去中心化信息,使互联网对所有人更安全和可访问。在任何时候,都不会有某个公司拥有你的数据。数据只是分散在许多位置(去中心化)。

现在很清楚为什么 dApps 如此有用,是时候在 Ethereum 上构建一个替代的 URL 缩短器了:

先决条件

  • 系统上安装 NodeJS

  • 系统上安装 Git

  • 一个文本编辑器

  • 终端,即命令行

  • Metamask 和一些 ETH 用于Gas费

设置你的 QuickNode 终端

为了本指南的目的,我们可以使用任何 Ethereum 客户端,如 GethOpenEthereum(前称 Parity)。由于启动这些客户端的过程相当复杂且耗时,我们将创建一个 免费的 QuickNode 账户 并创建一个 ETH 主网终端,以使这一过程变得非常简单。

创建完免费的 Ethereum 终端后,复制并保存你的 HTTP 提供程序终端,因为稍后你将需要它:

QuickNode Ethereum Endpoint 截图

制作我们的 dApp

确保在你的计算机上安装了 NodeJS 和 git。

要检查系统上是否安装了 NodeJS 10 及以上版本,请在终端/命令提示符中键入以下内容:

$ node -v

如果未安装,可以从官方 网站 下载 LTS 版本的 NodeJS。

要检查系统上是否安装了 git,请在终端/命令提示符中键入以下内容:

$ git --version

如果未安装,可以从 官方网站 下载 git。

我们将制作一个名为 0xsu 的 URL 缩短器,其代码仓库位于 GitHub

Solidity 合约 + 说明

在构建 dApp 之前,首先让我们了解构建 dApp 的智能合约。

pragma solidity ^0.5.2;

contract URLShortner {
  struct URLStruct {
    address owner;
    string url;
    bool exists;
    bool paid;
  }
  mapping (bytes => URLStruct) lookupTable;
  mapping (address => bytes[]) public shortenedURLs;
  address[] accts;
  address payable owner;
  event URLShortened(string url, bytes slug, address owner);

  constructor() public { owner = msg.sender; }

  function shortenURLWithSlug(string memory _url, bytes memory _short, bool paid) public payable {
    bool paidDefault = false;
    if (!lookupTable[_short].exists){
      lookupTable[_short] = URLStruct(msg.sender, _url, true, paid||paidDefault);
      shortenedURLs[msg.sender].push(_short);
      if(shortenedURLs[msg.sender].length < 1) {
        accts.push(msg.sender);
      }
      emit URLShortened(_url, _short, msg.sender);
    }
  }

  function shortenURL(string memory url, bool paid) public payable {
    bool paidDefault = false;
    bytes memory shortHash = getShortSlug(url);
    return shortenURLWithSlug(url, shortHash, paid||paidDefault);
  }

  function listAccts() public view returns (address[] memory){
    return accts;
  }

  function getURL(bytes memory _short) public view returns (string memory) {
    URLStruct storage result = lookupTable[_short];
    if(result.exists){
      return result.url;
    }
    return "FAIL";
  }

  function kill() public {
    if (msg.sender == owner) selfdestruct(owner);
  }

  // 私有函数
  function getShortSlug(string memory str) internal pure returns (bytes memory) {
    bytes32 hash = sha256(abi.encodePacked(str));
    uint main_shift = 15;
    bytes32 mask = 0xffffff0000000000000000000000000000000000000000000000000000000000;
    return abi.encodePacked(bytes3(hash<<(main_shift*6)&mask));
  }
}

上述代码的说明:

第 1 行: 声明 Solidity 版本

第 3-9 行: 声明我们的智能合约为 URLShortner,然后创建一个结构 URLStruct 来存储/分组所需的缩短 URL 相关详细信息;owner 是地址类型,存储缩短 URL 的人的地址,url 是字符串类型,存储要缩短的 URL,exists 是布尔类型,存储 URL 是否存在,paid 是布尔类型,检查缩短 URL 的交易是否已支付。

第 10 行: 创建一个映射,将 URL 的短版本映射到 URLStruct。

第 11 行: 创建一个映射,其中特定所有者的地址(缩短 URL 的人)将映射到该人缩短的 URL 数组。因此,每一个缩短网址的个人都会将一个缩短的 URL 数组映射到他们的地址。

第 12 行: 创建一个名为 accts 的地址类型数组,存储通过缩短 URL 与智能合约交互的账户的地址。

第 13 行: 声明可支付的地址类型的 owner,这将允许该所有者访问管理以太币的基本工具。

第 14 行: 创建一个事件 URLShortened,这将通过全球 Ethereum 节点的 RPC 发出——任何前端都可以监听此事件。

第 16 行: 创建一个构造函数,并指定智能合约创建者的地址——此地址具有特殊权限。

第 18-28 行:

  • 创建一个函数 shortenURLWithSlug,允许个人指定 URL 的缩短版本,该函数的状态为 public,意味着它可以被外部范围以及其他合约访问。

  • 将 paidDefault 设置为 false。

  • 通过 if 条件检查给定 URL 的短版本是否存在,如果不存在,则使用 URLStruct 将其添加到 lookupTable。

  • 将短 URL(slug)添加到缩短 URL 的人到 shortenedURLs。

  • 检查缩短 URL 的人/个人所缩短的 URL 数量是否少于一个,如果此人/个人是第一次缩短 URL,则将其添加到我们在第 12 行看到的 accts 数组中。

  • 发出事件 URLShortened。

第 30-34 行: 创建一个函数 shortenURL,该函数与之前的函数相同,但个人没有指定短版本,他们粘贴 URL,并将通过我们稍后看到的 getShortSlug 方法进行缩短。

第 36-38 行: 创建一个函数 listAccts,方便我们从 accts 数组中获取账户,因为 accts 不是 public 的。

第 40-46 行: 创建一个函数 getURL,检查短 URL 是否存在于 lookupTable 中,如果存在则返回缩短的 URL,如果不存在则返回“FAIL”。

第 48-50 行: 创建一个函数 kill 和其中的 if 条件,检查当前发送方(第 14 行中的人)是否连接到合约,如果是,则将其停止连接/关联。

第 53-58 行: 创建一个函数 getShortSlug,这是一个私有方法,用于生成短 URL。

为了节省时间和 ETH,我们已经部署了这个智能合约,其地址嵌入在我们的帮助库中,代码如下。

前端

首先为我们的 dApp 创建一个目录,然后将所需的前端代码库克隆到该目录中:

$ mkdir 0xsu
$ cd 0xsu
$ git clone https://github.com/dU4E/0xsu-front-end/
$ cd 0xsu-front-end

我们的应用程序的前端是用 React 制作的,所以让我们安装所有必要的依赖。

$ npm i react
$ npm install -g create-react-app

前端将在 3000 端口运行,3001 端口将用于下面一步的 express 服务器运行。

现在让我们了解前端 React 应用的主文件中的一些重要部分:

import React from "react";
import Du4e from "@sahilsen/0xsu-js-lib";
import "./App.css";
import Navbar from "react-bootstrap/Navbar";
import { Badge, Container, Form, Modal, Button, Col } from "react-bootstrap";

class App extends React.Component {
  state = {
    shortURL: "",
    copied: null,
    urls: [],
    show: false
  };

  constructor(props) {
    super(props);
    this.shortenUrl = this.shortenUrl.bind(this);
    this.copy = this.copy.bind(this);
    this.handleClose = this.handleClose.bind(this);
    this.viewAcct = this.viewAcct.bind(this);
    this.shortener = new Du4e();
    this.shortener.onTxSend = txid => {
      console.log("TXID", txid);
      this.setState({ shortURL: "Waiting for tx to confirm" });
    };
    this.shortener.onURLShortened = (err, result) => {
      console.log("result", result);
      this.setState({ shortURL: result.args.slug });
    };
  }

  copy() {
    let el = document.createElement("textarea");
    el.value = this.state.longURL;
    el.setAttribute("readonly", "");
    el.style.position = "absolute";
    el.style.left = "-9999px";
    document.body.appendChild(el);
    el.select();
    document.execCommand("copy");
    document.body.removeChild(el);
    this.setState({ copied: true });
  }

  handleClose() {
    this.setState({ show: false });
  }

  viewAcct() {
    this.shortener.listOfUrls(list => {
      console.log(list);
      this.setState({ urls: list });
    });
  }

  shortenUrl(evt) {
    evt.preventDefault();
    this.shortener.shortenUrl(this.refs.short.value, { cb: () => {} });
  }

  render() {
    let { shortURL } = this.state;
    return (
      <div>
        <Navbar bg="light">
          <Navbar.Collapse className="justify-content-end">
            <a href="#" onClick={this.viewAcct}>
              账户
            </a>
          </Navbar.Collapse>
        </Navbar>
        <Container className="text-center">
          <br />
          <h1 className="App">0xSU.co</h1>
          <p>
            你身边的去中心化 Ethereum 短网址生成器
          </p>
          <br />
          <form onSubmit={this.shortenUrl}>
            <Form.Row>
              <Col sm={9}>
                <Form.Control
                  size="lg"
                  type="text"
                  ref="short"
                  placeholder="在这里粘贴你想要缩短的长 URL"
                />
              </Col>
              <Col sm={3}>
                <Button variant="primary" size="lg" type="submit">
                  缩短
                </Button>
              </Col>
            </Form.Row>
          </form>

          <div className={shortURL ? "" : "hidden"}>
            <br />
            <br />
            <span className={shortURL.startsWith("Waiting") ? "loading" : ""}>
              {shortURL.startsWith("Waiting")
                ? shortURL
                : `http://localhost:3001/${shortURL}`}
            </span>
             
            {!shortURL.startsWith("Waiting") && (
              <Badge variant="success" onClick={this.copy} className="copy">
                {this.state.copied ? "已复制!" : "复制"}
              </Badge>
            )}
          </div>
          <br />
          <br />
          <br />
          <br />

          <a href="https://github.com/du4e">了解更多 / 源代码</a>
        </Container>
        <Modal
          show={this.state.urls.length > 0 || this.state.show}
          onHide={this.handleClose}
        >
          <Modal.Header closeButton>
            <Modal.Title>你的缩短 URL</Modal.Title>
          </Modal.Header>
          <Modal.Body>
            {this.state.urls.map(x => {
              return (
                <p key={x}>
                  <a href={`http://localhost:3001/${x}`}>{x}</a>
                </p>
              );
            })}
          </Modal.Body>
        </Modal>
      </div>
    );
  }
}

export default App;

第 1-5 行: 导入必要的库和资源。

第 7 行: 建立一个名为 App 的组件。

第 8-13 行: 初始化状态以帮助获取来自合约的远程数据。

第 15-19 行: 绑定要使用的方法,以便它们可以使用正确的“this”。

第 20 行: 设置 Du4e 库的对象,这是一个用于处理 URLs 的库。

第 21-28 行: 附加两个事件处理器,onTxSend 将监听交易,onURLShortened 将监听来自合约的事件 URLShortened,当发出 URLShortened 事件时,它将更改状态并使 shortURL 可用于 React 组件 App。

第 30-41 行: copy() 在页面上建立一个临时元素,允许复制缩短 URL 的文本。

第 43-45 行: handleClose() 关闭模态框,这是一个带有功能的信息框。

第 47-52 行: viewAcct() 打开一个模态框,显示当前用户的缩短 URL 列表,并设置该用户的 URL 状态。

第 54-57 行: shortenUrl 使用 Du4e 库缩短 URL。

第 59-143 行: 一段 JSX 代码,附加事件并渲染应用程序。

后端

现在,我们需要设置服务器和智能合约(我们之前已经看到了)。我们需要返回到父目录 /0xsu,将智能合约代码库和 express 服务器代码库克隆到它。

注意:我们正在使用 express 服务器,但你也可以从 这里 选择其他选项,如 Sinatra 和 Flask。

$ git clone https://github.com/dU4E/0xsu-smart-contracts

注意:这个应用程序的智能合约已经在 Ethereum 区块链上部署,我们可以使用它的 ABI 和合约地址查询合约函数。

现在,从一个新的终端/命令窗口返回到父目录 0xsu,并克隆 express-server 代码库。

$ git clone https://github.com/dU4E/0xsu-express-server
$ cd 0xsu-express-server

我们还需要安装 express;在你的终端/命令提示符中输入以下内容:

$ npm i express

在文本编辑器中打开 .env 文件,并在第二行用你之前保存的 QuickNode HTTPS URL 替换 ADD_YOUR_QUICKNODE_URL

让我们通过查看 0xsu-express-server 代码库中的 index.js 文件来了解我们的 express 服务器实际做了什么。

const fs = require('fs')
const bodyParser = require('body-parser')
const express = require('express')
const Web3 = require('web3')
const path = require('path')
const dotenv = require('dotenv').config({ path: path.resolve('.env')})
const app = express()
const port = 3001
const abi = JSON.parse(fs.readFileSync(process.env.ABI_PATH))
const contractAddr = process.env.CONTRACT_ADDRESS
const web3 = new Web3(Web3.givenProvider || process.env.PROVIDER_URL)

app.set('view engine', 'ejs')

app.get('/:short', async function (req, res) {
  const contract = new web3.eth.Contract(abi, contractAddr)
  let slug = req.params.short
  // 获取 URL 以及其是否已支付的信息
  let destination = await contract.methods.getURL(slug).call()
  res.redirect(destination !== '"FAIL' ? destination : "/")
})

app.listen(port, () => console.log(`示例应用在端口 ${port} 上侦听!`))

上述代码的说明:

第 1-6 行: 导入必要的软件包/库。

第 7-11 行: 创建一个新的 express 应用,声明运行我们 express 应用的端口号,声明一个 abi 变量,用于存储从 .env 文件中获取的智能合约 ABI,声明一个 contractAddr 变量,用于存储从 .env 文件中获取的智能合约地址,一个 web3 变量,用于连接我们在 .env 文件中粘贴的 QuickNode 终端。

第 13 行: 将 EJS 设置为我们 express 应用的视图引擎。

第 15-21 行: 获取 URL 的短版本,使用 abi 和 contractAddr 设置 web3,通过 slug 获取短 URL 的信息,从智能合约获取 URL 信息,如果提供的短 URL 存在就会重定向到实际 URL。

第 23 行: 在控制台打印一条信息,这条信息将在成功执行 express 应用的 index.js 代码时打印。

你应该现在已经拥有了安装完成的所有代码部分,并打开两个终端窗口,一个在 0xsu-front-end 目录,另一个在 0xsu-express-server。

整合所有内容

  1. 运行服务器

切换到打开了 /express-server 目录的窗口,通过输入以下命令启动 express 服务器:

$ node index

输出看起来像这样:

现在,切换到另一个终端窗口,通过输入以下命令在你的终端/命令提示符中启动 react 应用:

$ npm start

如果一切正常,你应该能够在浏览器中打开应用,地址为 http://localhost:3000/,前提是你安装了 Metamask 浏览器插件,窗口将打开以请求你授予应用程序权限。

  1. 测试你的 dApp

在文本字段中放入你希望缩短的任何 URL,并点击缩短。这将打开 Metamask 来确认需要Gas费用的交易。由于我们正在与部署在 Ethereum 区块链网络上的智能合约进行交互,我们需要为此支付费用。你可以在本指南中了解有关交易和Gas费用的更多信息 - [ 如何使用 ethers-js 以更高的Gas费用重新发送交易]

在交易完成后,你将看到一条消息,带有缩短后的 URL 显示在文本字段下方:

你也可以从右上角的账户部分进入,点击链接以打开实际链接 https://localhost:3001/shortenedURL

生成的缩短 URL 将保留在 Ethereum 网络上,随时可以访问。

结论

恭喜你,已经构建了一个真正的去中心化应用!在 QuikNode 的帮助下,我们希望你的过程顺利无痛。

订阅我们的 通讯 以获取更多有关 Ethereum 的文章和指南。如果你有任何反馈,请通过 Twitter 联系我们。你也可以随时在我们的 Discord 社区服务器上与我们聊天,那里有着一些你见过的最酷的开发者 :)

  • 原文链接: quicknode.com/guides/eth...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
QuickNode
QuickNode
江湖只有他的大名,没有他的介绍。