实操教程:基于区块链的档案培训收费依据系统
环境搭建与依赖安装
本指南将使用 Hardhat 框架在以太坊区块链上构建一套档案培训收费依据系统。该系统通过智能合约自动计算培训费用,并将档案记录上链,确保收费透明且不可篡改。请确保本地已安装 Node.js(版本 v14.0.0 以上)。
在终端执行以下命令创建项目目录并初始化:
- 创建目录:mkdir blockchain-training-fee
- 进入目录:cd blockchain-training-fee
- 初始化项目:npm init -y
接着,安装开发所需的 Hardhat 核心依赖以及 Ethers.js 库:
npm install --save-dev hardhat @nomiclabs/hardhat-ethers ethers chai @nomiclabs/hardhat-waffle ethereum-waffle
安装完成后,在项目根目录执行 npx hardhat,在交互界面中选择 Create a JavaScript project(使用方向键选择并回车),这将自动生成标准的项目配置文件。
智能合约核心逻辑开发
在 contracts 目录下新建文件 TrainingFee.sol。该合约包含两个核心功能:一是根据培训时长计算费用的逻辑(即收费依据),二是存储培训档案的功能。
以下是完整的智能合约代码,请直接复制使用:
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract TrainingFee {
address public owner;
uint256 public baseFee = 0.01 ether; // 基础建档费
uint256 public hourlyRate = 0.005 ether; // 每课时收费单价
struct Archive {
uint256 id;
string studentName;
string courseName;
uint256 duration; // 培训时长(小时)
uint256 totalFee; // 实际支付费用
uint256 timestamp;
}
mapping(uint256 => Archive) public archives;
uint256 public archiveCount;
// 事件:用于前端监听上链成功
event ArchiveStored(uint256 indexed id, string studentName, uint256 fee);
constructor() {
owner = msg.sender;
}
// 核心功能:获取收费依据(根据时长计算总价)
function getFeeQuote(uint256 _duration) public view returns (uint256) {
require(_duration > 0, "Duration must be greater than 0");
return baseFee + (_duration hourlyRate);
}
// 核心功能:上传档案并支付费用
function uploadArchive(string memory _studentName, string memory _courseName, uint256 _duration) public payable {
uint256 requiredFee = getFeeQuote(_duration);
// 验证支付金额是否符合收费依据
require(msg.value >= requiredFee, "Insufficient payment based on fee calculation");
archiveCount++;
archives[archiveCount] = Archive({
id: archiveCount,
studentName: _studentName,
courseName: _courseName,
duration: _duration,
totalFee: msg.value,
timestamp: block.timestamp
});
emit ArchiveStored(archiveCount, _studentName, msg.value);
}
// 查询特定档案详情
function getArchive(uint256 _id) public view returns (uint256, string memory, string memory, uint256, uint256, uint256) {
require(_id > 0 && _id <= archiveCount, "Archive does not exist");
Archive memory a = archives[_id];
return (a.id, a.studentName, a.courseName, a.duration, a.totalFee, a.timestamp);
}
}
```
该合约定义了收费依据为:基础费用 + (时长 × 单价)。任何人都可以调用 getFeeQuote 查询预估费用,只有支付足够费用的用户才能调用 uploadArchive 将数据写入区块链。
配置编译与部署脚本
为了确保 Hardhat 能正确编译合约,需要修改根目录下的 hardhat.config.js 文件。请将原内容替换为以下配置:
```javascript
require("@nomiclabs/hardhat-waffle");
require("@nomiclabs/hardhat-ethers");
module.exports = {
solidity: {
version: "0.8.0",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
networks: {
hardhat: {
chainId: 1337 // 本地测试链ID
}
}
};
```
接下来,编写部署脚本。在 scripts 目录下新建 deploy.js:

```javascript
const hre = require("hardhat");
async function main() {
console.log("Deploying TrainingFee contract...");
const TrainingFee = await hre.ethers.getContractFactory("TrainingFee");
const feeContract = await TrainingFee.deploy();
await feeContract.deployed();
console.log("TrainingFee deployed to:", feeContract.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
```
在终端执行 npx hardhat compile 编译合约,随后执行 npx hardhat run scripts/deploy.js --network hardhat 进行部署测试。如果输出合约地址,则说明环境配置无误。
自动化测试与收费逻辑验证
为了确保“收费依据”逻辑的准确性,我们需要编写自动化测试用例。在 test 目录下新建 TrainingFee.test.js,写入以下完整测试代码:
```javascript
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("TrainingFee Contract", function () {
let feeContract;
let owner;
let addr1;
beforeEach(async function () {
const TrainingFee = await ethers.getContractFactory("TrainingFee");
feeContract = await TrainingFee.deploy();
[owner, addr1] = await ethers.getSigners();
});
it("Should return correct fee quote based on duration", async function () {
// 测试10小时的费用计算:0.01 ether (base) + 10 0.005 ether = 0.06 ether
const duration = 10;
const expectedFee = ethers.utils.parseEther("0.06");
const quote = await feeContract.getFeeQuote(duration);
expect(quote).to.equal(expectedFee);
});
it("Should fail if payment is less than calculated fee", async function () {
const duration = 5;
const feeQuote = await feeContract.getFeeQuote(duration);
// 故意少支付 1 wei
const insufficientPayment = feeQuote.sub(1);
await expect(
feeContract.connect(addr1).uploadArchive("Alice", "Blockchain101", duration, { value: insufficientPayment })
).to.be.revertedWith("Insufficient payment based on fee calculation");
});
it("Should store archive successfully with correct payment", async function () {
const duration = 2;
// 费用 = 0.01 + 20.005 = 0.02 ether
const payment = ethers.utils.parseEther("0.02");
await feeContract.connect(addr1).uploadArchive("Bob", "DevOps", duration, { value: payment });
const storedData = await feeContract.archives(1);
expect(storedData.studentName).to.equal("Bob");
expect(storedData.totalFee).to.equal(payment);
});
});
```
在终端运行 npx hardhat test。如果所有测试均通过,说明智能合约的收费计算逻辑和资金锁定机制完全符合设计要求。
前端交互页面实现
为了让非技术人员也能查询收费依据并上传档案,我们在项目根目录创建一个简单的 HTML 页面。新建 index.html:
```html
档案上传与收费查询
```
要运行此页面并使其真正与区块链交互,你需要在一个终端保持 Hardhat 节点运行:npx hardhat node。在另一个终端运行 npx hardhat run scripts/deploy.js --network localhost 部署合约,并将 HTML 代码中的 contractAddress 替换为终端输出的实际地址。
通过以上步骤,你已经完成了一个包含收费计算逻辑验证、资金锁定以及数据存证的完整区块链应用系统。用户上传的每一份档案,其支付金额都严格受链上代码控制,实现了真正的技术背书。