区块链技术的崛起,特别是以太坊平台的成熟,为去中心化应用(DApps)的开发提供了广阔的空间,DApp的核心在于用户身份管理和交互,而登录注册功能则是任何应用的基石,本文将以一个简单的示例,带你一步步了解如何在以太坊DApp中实现登录注册功能,主要涉及智能合约(后端)和前端交互。
核心概念简述
在开始之前,我们先明确几个关键概念:
- 智能合约 (Smart Contract):运行在以太坊区块链上的程序,负责业务逻辑的实现,如存储用户信息、验证身份等,我们通常使用Solidity语言编写。
- Web3.js / Ethers.js:JavaScript库,用于前端应用与以太坊节点(或用户钱包如MetaMask)进行交互,例如发送交易、读取合约状态等。
- MetaMask:一款流行的浏览器钱包插件,它允许用户管理以太坊账户,并与DApp进行安全交互,是DApp开发的必备工具。
- 用户身份:在以太坊DApp中,用户的身份通常由其以太坊地址(Address)唯一标识,私钥控制该地址下的资产和操作。
登录注册逻辑设计
在传统的Web应用中,登录注册涉及用户名、密码的存储和验证,在以太坊DApp中,由于区块链的透明性和不可篡改性,直接存储明文密码是极其危险的,我们通常采用以下逻辑:
-
注册 (Registration):
- 用户生成或导入以太坊地址(通过MetaMask等钱包)。
- 用户向智能合约提交其地址,并可能附带一些基本信息(如用户名,可选)。
- 智能合约将该地址标记为已注册用户,并存储相关信息。
-
登录 (Login):
- 用户通过MetaMask连接DApp,授权前端应用访问其地址。
- 前端应用获取用户的以太坊地址。
- 前端向智能合约查询该地址是否为已注册用户。

- 如果是,则登录成功;否则,提示用户先注册。
这里,“密码”的概念被以太坊的私钥/地址体系所取代,拥有私钥的人就能控制对应地址的资产和操作,登录”的本质就是证明用户拥有某个地址的私钥(通过MetaMask签名)。
开发步骤示例
我们将使用 Hardhat(以太坊开发环境)、Solidity(智能合约语言)、Ethers.js(交互库)和 React(前端框架,可选,也可用纯HTML/JS)来进行示例。
第一步:搭建开发环境
- 安装Node.js和npm/yarn。
- 创建一个新的项目目录,并初始化npm项目:
npm init -y - 安装Hardhat:
npm install --save-dev hardhat - 初始化Hardhat项目:
npx hardhat,选择 "Create a basic sample project"。 - 安装必要的依赖:
npm install --save-dev @nomicfoundation/hardhat-toolbox ethers
第二步:编写智能合约 (UserRegistry.sol)
在 contracts/ 目录下创建 UserRegistry.sol 文件:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract UserRegistry {
// 结构体存储用户信息
struct User {
address walletAddress;
string username;
bool isRegistered;
uint256 registeredAt;
}
// 地址到用户信息的映射
mapping(address => User) public users;
// 已注册用户地址数组
address[] public registeredUsers;
// 注册事件
event UserRegistered(address indexed walletAddress, string username, uint256 timestamp);
// 注册用户
function register(string memory _username) public {
// 检查用户是否已经注册
require(!users[msg.sender].isRegistered, "User is already registered");
// 存储用户信息
users[msg.sender] = User({
walletAddress: msg.sender,
username: _username,
isRegistered: true,
registeredAt: block.timestamp
});
registeredUsers.push(msg.sender);
emit UserRegistered(msg.sender, _username, block.timestamp);
}
// 获取用户信息
function getUser(address _userAddress) public view returns (address, string memory, bool, uint256) {
User storage user = users[_userAddress];
return (user.walletAddress, user.username, user.isRegistered, user.registeredAt);
}
// 检查地址是否已注册
function isRegistered(address _userAddress) public view returns (bool) {
return users[_userAddress].isRegistered;
}
}
第三步:编译和部署合约
-
在
scripts/目录下创建部署脚本,deploy.js:async function main() { const UserRegistry = await ethers.getContractFactory("UserRegistry"); const userRegistry = await UserRegistry.deploy(); await userRegistry.deployed(); console.log("UserRegistry deployed to:", userRegistry.address); } main().catch((error) => { console.error(error); process.exitCode = 1; }); -
在
hardhat.config.js中配置Solidity版本。 -
编译合约:
npx hardhat compile -
部署合约(假设你有一个本地测试节点如Ganache,或使用Hardhat Network):
npx hardhat run scripts/deploy.js --network <your_network_name>
部署成功后,记下合约地址。
第四步:创建前端交互界面
在 frontend/ 目录下(你可以单独创建一个React项目或简单的HTML文件)实现前端逻辑,这里以一个简单的HTML + Ethers.js为例:
创建 index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">以太坊DApp登录注册示例</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.container { max-width: 400px; margin: 0 auto; padding: 20px; border: 1px solid #ccc; border-radius: 5px; }
input, button { width: 100%; padding: 10px; margin: 5px 0; box-sizing: border-box; }
button { background-color: #4CAF50; color: white; border: none; cursor: pointer; }
button:hover { background-color: #45a049; }
#status { margin-top: 20px; padding: 10px; border-radius: 5px; }
.success { background-color: #dff0d8; color: #3c763d; }
.error { background-color: #f2dede; color: #a94442; }
</style>
</head>
<body>
<div class="container">
<h1>以太坊DApp登录注册</h1>
<div id="connectWallet">
<button id="connectButton">连接 MetaMask 钱包</button>
</div>
<div id="registerSection" style="display: none;">
<h2>注册</h2>
<input type="text" id="usernameInput" placeholder="输入用户名">
<button id="registerButton">注册</button>
</div>
<div id="loginSection" style="display: none;">
<h2>登录</h2>
<p>当前账户: <span id="currentAccount"></span></p>
<button id="loginButton">登录</button>
</div>
<div id="status"></div>
</div>
<script src="https://cdn.ethers.io/lib/ethers-5.7.2.umd.min.js" type="application/javascript"></script>
<script>
let contract;
let signer;
const contractAddress = "YOUR_DEPLOYED_CONTRACT_ADDRESS"; // 替换为你的合约地址
// 合约ABI (简化版,实际项目中应从编译文件中获取完整ABI)
const contractABI = [
"function register(string memory _username) external",
"function isRegistered(address _userAddress) external view returns (bool)",
"function getUser(address _userAddress) external view returns (address, string memory, bool, uint256)"
];
const connectButton = document.getElementById('connectWallet');
const registerSection = document.getElementById('registerSection');
const loginSection = document.getElementById('loginSection');
const currentAccountSpan = document.getElementById('currentAccount');
const usernameInput = document.getElementById('usernameInput');
const registerButton = document.getElementById('registerButton');
const loginButton = document.getElementById('loginButton');
const statusDiv = document.getElementById('status');
//