需求:使用区块链实现数据村存储,记录一些不可篡改的交互信息,网络环境为内外网均需要部署;
1.准备工作(软件安装)
1.1 安装 Node.js 和 npm
1.2 安装 Ganache
地址如下:windows有可视化界面 ,本文章使用windows版
Ganache - Truffle Suite
点击“Quickstart”创建一个本地以太坊区块链网络
1.3 安装 Truffle
打开命令提示符(CMD)或 PowerShell,运行以下命令安装 Truffle:
npm install -g truffle
安装后验证
truffle version
类似如下则安装成功:
1.4 安装 Web3.js(前端和ganache连接需要,后端的话直接跳过即可)
npm install web3
2. 创建和配置区块链项目
2.1 初始化 Truffle 项目
打开命令提示符(CMD)或 PowerShell
找到合适的文件夹 下运行以下命令创建一个新目录并初始化 Truffle 项目:
mkdir my-blockchain-project
cd my-blockchain-project
truffle init
2.2 配置 Truffle
-
在项目目录中找到
truffle-config.js
文件,用文本编辑器(如 Notepad++ 或 VSCode)打开。
-
修改配置文件,配置 Ganache 作为开发网络:
module.exports = {networks: {development: {host: "127.0.0.1", // Ganache 的默认地址port: 7545, // Ganache 的默认端口network_id: "*", // 匹配任何网络ID},},compilers: {solc: {version: "0.8.0", // 使用合适的 Solidity 版本},},
};
3. 编写和部署智能合约
3.1 编写智能合约
创建 Solidity 合约文件:
-
在
contracts
目录下创建一个新的 Solidity 合约文件,例如DataStorage.sol
:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;contract DataStorage {struct Data {string jsonData;}mapping(uint256 => Data) public dataMap;uint256 public dataCount;function storeData(string memory _jsonData) public {dataMap[dataCount] = Data(_jsonData);dataCount++;}function getData(uint256 _id) public view returns (string memory) {return dataMap[_id].jsonData;}
}
Solidity 合约定义了一个简单的数据存储和检索机制
**合约声明
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
// SPDX-License-Identifier: UNLICENSED
: 这是一个 SPDX 声明,用于指明代码的许可证类型。UNLICENSED
表示该代码没有许可条款。pragma solidity ^0.8.0(一定要和truffle-config.js
文件中的compilers的version相同,不然会报错)
: 指定该合约使用的 Solidity 编译器版本是0.8.0
或更高版本。^
表示向上兼容。
**合约定义
contract DataStorage {
contract DataStorage {
: 定义了一个名为DataStorage
的合约,所有的存储和检索功能都将在这个合约内实现。
**数据结构定义struct Data {string jsonData;}
struct Data
: 定义了一个名为Data
的结构体,它包含一个字段jsonData
,该字段是一个string
类型,用来存储 JSON 格式的数据,表示使用此合约存储的是json类数据;
**状态变量mapping(uint256 => Data) public dataMap;uint256 public dataCount;
mapping(uint256 => Data) public dataMap;
: 定义了一个mapping
,它将一个uint256
类型的键映射到一个Data
结构体。public
关键字使得该映射可以通过合约外部访问(自动生成 getter 函数)。dataMap
用来存储每个数据条目,数据是通过dataCount
作为键存储的。uint256 public dataCount;
: 定义了一个计数器dataCount
,用来记录当前存储的Data
的数量。每当存储新数据时,dataCount
会自增,用于将来遍历查询数据。
**存储函数function storeData(string memory _jsonData) public {dataMap[dataCount] = Data(_jsonData);dataCount++;}
function storeData(string memory _jsonData) public
: 定义了一个公开函数storeData
,它接受一个string
类型的参数_jsonData
,用于存储数据。string memory _jsonData
: 这是函数的输入参数,表示传入的 JSON 数据。
dataMap[dataCount] = Data(_jsonData);
: 将传入的 JSON 数据_jsonData
存储在dataMap
中,以dataCount
作为键,值是一个包含该 JSON 数据的Data
结构体。dataCount++;
: 每次调用storeData
函数时,dataCount
计数器会增加 1,确保下一个数据存储使用新的键。- 将来每次取值都是通过健访问
dataMap的值,得以拿到数据;
**获取函数function getData(uint256 _id) public view returns (string memory) {return dataMap[_id].jsonData;}
这个合约实现了以下功能:
storeData
函数允许将 JSON 格式的数据存储到区块链中。getData
函数允许根据存储时生成的id
(由dataCount
自动递增)获取对应的 JSON 数据。dataMap
是一个映射,它将数据的id
映射到存储的数据jsonData
。dataCount
记录了当前存储的数据数量,并且用于为每个数据条目生成唯一的id
。
3.2 编译智能合约
接下来命令都是在项目根目录下运行 , 运行以下命令编译合约:
truffle compile
3.3 部署智能合约
在 migrations
目录下创建一个新的迁移文件,例如 2_deploy_contracts.js
:
const DataStorage = artifacts.require("DataStorage");module.exports = function (deployer) {deployer.deploy(DataStorage);
};
运行以下命令部署合约:
truffle migrate --network development
-
部署成功后,控制台输出如下,记下合约地址(在终端输出中查找
contract address
)。
Starting migrations...
======================
> Network name: 'development'
> Network id: 5777
> Block gas limit: 6721975 (0x6691b7)
2_deploy_contracts.js
=====================Deploying 'DataStorage'
-----------------------
> transaction hash: 0x779d63bdaa8e9afb1ce4ff56751a923f861ce006d03028183570e1130a326dca
> Blocks: 0 Seconds: 0
> contract address: 0x9E6954C2B46ae3B7C1e6676964a2Cc5e4477Fedf
> block number: 1
> block timestamp: 1739346849
> account: 0xEF8625527393F19118803b027631F215a6eE10c8
> balance: 99.99854643475
> gas used: 430686 (0x6925e)
> gas price: 3.375 gwei
> value sent: 0 ETH
> total cost: 0.00145356525 ETH> Saving artifacts
-------------------------------------
> Total cost: 0.00145356525 ETHSummary
=======
> Total deployments: 1
> Final cost: 0.00145356525 ETH
4. 使用 Java 与区块链交互
4.1 安装 Web3j
下载 Web3j:github中其地址如下
Releases · hyperledger-web3j/web3j (github.com)
下载.zip版本并解压
解压后目录如下:
在本文件夹下使用powershell
.\gradlew build
4.2. 生成 Java 合约文件
已经有了 Solidity 合约文件(.sol
文件),可以使用 Web3j 提供的工具来生成 Java 类。
在 PowerShell 中执行以下命令来安装 Web3j CLI 工具:
Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/hyperledger/web3j-installer/main/installer.ps1'))
执行以下命令检查是否安装成功:
web3j --version
在项目目录下运行以下命令生成 Java 合约包装器
web3j generate truffle --truffle-json ./build/contracts/DataStorage.json -o ./src/main/java -p com.example.contract
-
生成的 Java 文件将位于
src/main/java/com/example/contract
目录下。
4.3 编写 Java 程序
创建项目,将生成的合约包装器文件复制到项目的 src/main/java/com/example/contract
目录下
添加 Web3j 依赖:
<dependency><groupId>org.web3j</groupId><artifactId>core</artifactId><version>4.9.4</version>
</dependency>
编写java程序:
@RestController
@RequestMapping("/myBlockChain")
public class BlockChainController {@AutowiredDataStorageExample dataStorageExample;@PostMapping("/storeData")public String addMsg(@RequestBody Transaction transaction) throws Exception {// 将接收到的消息对象转化为 JSON 字符串String jsonData = String.format("{\"tid\":\"%s\",\"data\":%s}", transaction.getTid(), transaction.getData());String msg = DataStorageExample.storeData(jsonData);return msg;}@PostMapping("/getAllData")public String getMsg() throws Exception {// 将接收到的消息对象转化为 JSON 字符串String msg = DataStorageExample.getData();return msg;}
}
/*** 数据存储示例*/
@Service
public class DataStorageExample {private static final String NODE_URL = "http://localhost:7545";private static final String PRIVATE_KEY = "******************************";private static Web3j web3j;private static Credentials credentials;private static DataStorage dataStorage;// 静态代码块初始化 Web3j 和 Credentialsstatic {web3j = Web3j.build(new HttpService(NODE_URL));credentials = Credentials.create(PRIVATE_KEY);}/*** 获取单一的 DataStorage 合约实例* @return DataStorage* @throws Exception*/public static DataStorage getDataStorage() throws Exception {if (dataStorage == null) {// 如果合约尚未部署,则进行部署,确保单例,不然不同合约下的数据不互通dataStorage = deployContract(web3j, credentials);}return dataStorage;}/*** 部署合约* @param web3j* @param credentials* @return* @throws Exception*/private static DataStorage deployContract(Web3j web3j, Credentials credentials) throws Exception {System.out.println("正在部署合约...");DataStorage dataStorage = DataStorage.deploy(web3j, credentials, new DefaultGasProvider()).send();System.out.println("合约部署在地址: " + dataStorage.getContractAddress());return dataStorage;}/*** 存储JSON数据* @param jsonData* @throws Exception*/public static String storeData(String jsonData) throws Exception {if (dataStorage == null) {getDataStorage(); // 确保合约已部署}System.out.println("存储的数据: " + jsonData);TransactionReceipt receipt = dataStorage.storeData(jsonData).send();System.out.println("交易收据: " + receipt.getTransactionHash());return receipt.getTransactionHash();}/*** 获取数据* @return String* @throws Exception*/public static String getData() throws Exception {if (dataStorage == null) {getDataStorage(); // 确保合约已部署}BigInteger dataCount = dataStorage.dataCount().send(); // 获取数据的总条目数Map<BigInteger, String> allData = new HashMap<>();// 遍历所有数据,按照ID获取并存储for (BigInteger i = BigInteger.ZERO; i.compareTo(dataCount) < 0; i = i.add(BigInteger.ONE)) {String data = dataStorage.getData(i).send(); // 获取每条数据allData.put(i, data); // 将数据存入Map}return allData.toString();}
}
PRIVATE_KEY的值为任意ACCOUNT的 PRIVATE_KEY的值,表示哪个账户发起交易
每次交易都会生成一个新的Block存储数据
点击Transactions可查看每次交易的相关数据
运行示例:
至此,实现本地Ganache区块链私有化部署并使json数据上链