本篇文章深入探讨了如何搭建FISCO BCOS区块链平台的WeBase管理工具,详细说明了从环境配置到模块部署的全过程。此外,文章也探讨了将部署好的智能合约与现有的若依(Ruoyi)框架进行整合的方法。
搭建WeBASE
部署文档地址:https://webasedoc.readthedocs.io/zh-cn/latest/docs/WeBASE/install.html
①配置环境
1.1 检查Java
java -version
apt install openjdk-11-jre-headless
配置JAVA_HOME:
cd /usr/lib/jvmls
vim /etc/profile
export JAVA_HOME=/usr/lib/jvm/java-1.11.0-openjdk-amd64 # 此处为自己的jdk版本号export JRE_HOME=$JAVA_HOME/jreexport CLASSPATH=$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATHexport PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH
source /etc/profileecho $JAVA_HOME
1.2 检查mysql
Ubuntu安装:
注:这里没有安装官方文档给的mysql 5.7 。因为在安装时发生报错,所以采用了Ubuntu的默认mysql 8版本:
sudo apt updatesudo apt install mysql-server查看mysql版本:8.0.36-0ubuntu0.22.04.1
初始化root密码:
mysqlALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root';
设置远程登陆:
mysql -uroot -puse mysql;select user,host from user;update user set host='%' where user='root' and host='localhost';
修改 /etc/mysql/mysql.conf.d/mysqld.cnf 配置文件:
vim /etc/mysql/mysql.conf.d/mysqld.cnf
重启mysql服务:
service mysql restart尝试远程连接:
1.3 检查python3
// 添加仓库,回车继续sudo add-apt-repository ppa:deadsnakes/ppa// 安装python 3.6sudo apt-get install -y python3.6sudo apt-get install -y python3-pippython --version# python3时python3 --version1.4 PyMySQL部署
sudo apt-get install -y python3-pipsudo pip3 install PyMySQL②安装WeBASE
2.1 部署WeBASE
获取部署安装包:
wget https://github.com/WeBankBlockchain/WeBASELargeFiles/releases/download/v1.5.5/webase-deploy.zip
# 网络访问失败,则可以尝试直接git clone WeBASE的仓库# 其中deploy目录即webase-deploy的目录git clone https://github.com/WeBankBlockchain/WeBASE.git# 若因网络问题导致长时间下载失败,可尝试以下命令git clone https://gitee.com/WeBank/WeBASE.gitwget方式直接解压安装包:
unzip webase-deploy.zip进入目录:
cd webase-deploygit方式:
无需解压:

修改common.properties配置:
更改数据库用户以及密码:
设置节点数量为4:
# 部署并启动所有服务python3 deploy.py installAll
WeBASE管理平台:
- 一键部署完成后,打开浏览器(Chrome Safari或Firefox)访问
http://{deployIP}:{webPort}示例:http://localhost:5000
默认账号为admin,默认密码为Abcd1234。首次登陆要求重置密码 添加节点前置WeBASE-Front到WeBASE管理平台;一键部署时,节点前置与节点管理服务默认是同机部署,添加前置则填写IP为127.0.0.1,默认端口为5002。参考上文中common.properties的配置项front.port={frontPort}
2.2 问题(已解决)(与官网解决方法不一致)
验证码失效问题:在WeBASE官方文档中给出了解决方案:
查WeBASE-Node-Manager后台服务是否已启动成功。若启动成功,检查后台日志:
- 进入
webase-node-mgr目录下,执行bash status.sh检查服务是否启动,如果服务没有启动,运行bash start.sh启动服务; - 如果服务已经启动,按照如下修改日志级别
cd webase-node-mgrbash status.sh
发现确实是5001端口服务没启动。
bash start.sh
启动成功后再次确认是否成功启动,发现还是没有进程号。
查看日志信息,看看报了什么错!告诉我们去看看log
Server com.webank.webase.node.mgr.Application Port 5001 ...PID(4982) [Starting]. Please message check through the log file (default path:./log/).
查看日志:
vim WeBASE-Node-Manager.log发现问题:
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure很熟悉,这不是mysql的错误吗?
查询之后发现是SSL问题,当我们写项目时,配JDBC连接的时候常常出现:
jdbc:mysql://127.0.0.1:3306/yxaqgl?verifyServerCertificate=true&useSSL=False&requireSSL=False但这个配置文件中并没有连接属性:
# 节点管理子系统mysql数据库配置mysql.ip=127.0.0.1mysql.port=3306mysql.user=dbUsernamemysql.password=dbPasswordmysql.database=webasenodemanager
# 签名服务子系统mysql数据库配置sign.mysql.ip=localhostsign.mysql.port=3306sign.mysql.user=dbUsernamesign.mysql.password=dbPasswordsign.mysql.database=webasesign那就彻底关闭mysql的SSL属性:
先检查一下是否开启了SSL:
mysql -uroot -p
SHOW VARIABLES LIKE '%ssl%';如下图,SSL确实开启:
使用exit离开,进入mysql的安装目录:
进入mysql.cnf文件发现这只是个引用?
进入mysql.conf.d里面:添加skip_ssl
重启服务!(一定要重启)再次查询
service mysql restart
此时SSL已经关闭,再次启动WeBASE尝试是否有问题:
exit
su root
cd webase-deploy
python3 deploy.py stopAll
python3 deploy.py startAll
填写默认端口:5002
服务部署后,需要对各服务进行启停操作,可以使用以下命令:
# 一键部署部署并启动所有服务 python3 deploy.py installAll停止一键部署的所有服务 python3 deploy.py stopAll启动一键部署的所有服务 python3 deploy.py startAll# 各子服务启停启动FISCO-BCOS节点: python3 deploy.py startNode停止FISCO-BCOS节点: python3 deploy.py stopNode启动WeBASE-Web: python3 deploy.py startWeb停止WeBASE-Web: python3 deploy.py stopWeb启动WeBASE-Node-Manager: python3 deploy.py startManager停止WeBASE-Node-Manager: python3 deploy.py stopManager启动WeBASE-Sign: python3 deploy.py startSign停止WeBASE-Sign: python3 deploy.py stopSign启动WeBASE-Front: python3 deploy.py startFront停止WeBASE-Front: python3 deploy.py stopFront# 可视化部署部署并启动可视化部署的所有服务 python3 deploy.py installWeBASE停止可视化部署的所有服务 python3 deploy.py stopWeBASE启动可视化部署的所有服务 python3 deploy.py startWeBASE③编写智能合约
3.1 编写智能合约
智能合约开发文档地址:https://fisco-bcos-documentation.readthedocs.io/zh-cn/latest/docs/manual/smart_contract.html 注意:以下是本人所需业务而书写的智能合约,请不要和本人一致,请根据你自己的业务来进行命名以及书写相应的方法。
pragma solidity ^0.4.25;pragma experimental ABIEncoderV2;
import "./Table.sol";
contract Record { event InsertResult(int256 count); event UpdateResult(int256 count); event GetResult(string description,string remark);
TableFactory tableFactory; string constant TABLE_NAME = "record"; constructor() public { tableFactory = TableFactory(0x1001); //The fixed address is 0x1001 for TableFactory // the parameters of createTable are tableName,keyField,"vlaueFiled1,vlaueFiled2,vlaueFiled3,..." tableFactory.createTable(TABLE_NAME, "recordid", "description,remark"); }
//select records function getResult(string memory recordid) public view returns (string memory,string memory) { Table table = tableFactory.openTable(TABLE_NAME);
Condition condition = table.newCondition(); condition.EQ("recordid",recordid);
Entries entries = table.select(recordid, condition);
require(entries.size() > 0, "Record does not exist");
Entry entry = entries.get(0);
string memory description = entry.getString("description");
string memory remark = entry.getString("remark");
emit GetResult(description,remark);
return (description,remark); }
//insert records function insert(string memory recordid, string memory description, string memory remark) public returns (int256) { Table table = tableFactory.openTable(TABLE_NAME);
Entry entry = table.newEntry(); entry.set("recordid", recordid); entry.set("description",description); entry.set("remark", remark);
int256 count = table.insert(recordid, entry); emit InsertResult(count);
return count; } //update records function update(string memory recordid, string memory description, string memory remark) public returns (int256) { Table table = tableFactory.openTable(TABLE_NAME);
Entry entry = table.newEntry(); entry.set("description", description); entry.set("remark", remark);
Condition condition = table.newCondition(); condition.EQ("recordid", recordid);
int256 count = table.update(recordid, entry, condition); emit UpdateResult(count);
return count; }}智能合约编写完成后,点击部署,将智能合约部署到WeBASE平台:
pragma solidity ^0.4.24;
contract TableFactory { /** * 打开表,返回Table合约地址 * @param tableName 表的名称 * @return 返回Table的地址,当表不存在时,将会返回空地址即address(0x0) */ function openTable(string tableName) public constant returns (Table);
/** * 创建表,返回是否成功 * @param tableName 表的名称 * @param key 表的主键名 * @param valueFields 表的字段名,多个字段名以英文逗号分隔 * @return 返回错误码,成功为0,错误则为负数 */ function createTable(string tableName,string key,string valueFields) public returns(int);}
// 查询条件contract Condition { //等于 function EQ(string, int) public; function EQ(string, string) public;
//不等于 function NE(string, int) public; function NE(string, string) public;
//大于 function GT(string, int) public; //大于或等于 function GE(string, int) public;
//小于 function LT(string, int) public; //小于或等于 function LE(string, int) public;
//限制返回记录条数 function limit(int) public; function limit(int, int) public;}
// 单条数据记录contract Entry { function getInt(string) public constant returns(int); function getAddress(string) public constant returns(address); function getBytes64(string) public constant returns(byte[64]); function getBytes32(string) public constant returns(bytes32); function getString(string) public constant returns(string);
function set(string, int) public; function set(string, string) public; function set(string, address) public;}
// 数据记录集contract Entries { function get(int) public constant returns(Entry); function size() public constant returns(int);}
// Table主类contract Table { /** * 查询接口 * @param key 查询主键值 * @param cond 查询条件 * @return Entries合约地址,合约地址一定存在 */ function select(string key, Condition cond) public constant returns(Entries); /** * 插入接口 * @param key 插入主键值 * @param entry 插入字段值 * @return 插入影响的行数 */ function insert(string key, Entry entry) public returns(int); /** * 更新接口 * @param key 更新主键值 * @param entry 更新字段值 * @param cond 更新条件 * @return 更新影响的行数 */ function update(string key, Entry entry, Condition cond) public returns(int); /** * 删除接口 * @param key 删除的主键值 * @param cond 删除条件 * @return 删除影响的行数 */ function remove(string key, Condition cond) public returns(int);
function newEntry() public constant returns(Entry); function newCondition() public constant returns(Condition);}3.2 测试智能合约
点击发交易进行智能合约测试:
①测试insert方法:
②测试查询方法
④调用SDK
4.1 WeBase导出Java项目
导出智能合约:
导出的Java项目如下图所示:
发现导出的项目没有生成Table类!!!于是我们采用配置控制台来获取Table的相关信息。
4.2 配置控制台
cd ~mkdir fiscocd ~/fisco && curl -LO https://github.com/FISCO-BCOS/console/releases/download/v2.9.2/download_console.sh && bash download_console.shcp -r /root/webase-deploy/nodes/127.0.0.1/sdk/* /root/fisco/console/conf/cd ~/fisco/console && bash start.sh
出现这个问题进入:
根据图中相关命令进行:
4.3 结合若依项目
创建控制台之后:
获取Record.java、Table.java、Record.bin、Table.bin这四个文件:
其余文件均可在导出的Java项目中找到:如下,在若依项目中创建相应的文件夹,将所需要的文件统一复制过去。
其中config-example.toml:
若有更新去:https://github.com/FISCO-BCOS/java-sdk/blob/master-2.0/src/test/resources/config-example.toml
[cryptoMaterial]certPath = "conf" # The certification path
# The following configurations take the certPath by default if commented# caCert = "conf/ca.crt" # CA cert file path # If connect to the GM node, default CA cert path is ${certPath}/gm/gmca.crt
# sslCert = "conf/sdk.crt" # SSL cert file path # If connect to the GM node, the default SDK cert path is ${certPath}/gm/gmsdk.crt
# sslKey = "conf/sdk.key" # SSL key file path # If connect to the GM node, the default SDK privateKey path is ${certPath}/gm/gmsdk.key
# enSslCert = "conf/gm/gmensdk.crt" # GM encryption cert file path # default load the GM SSL encryption cert from ${certPath}/gm/gmensdk.crt
# enSslKey = "conf/gm/gmensdk.key" # GM ssl cert file path # default load the GM SSL encryption privateKey from ${certPath}/gm/gmensdk.key
[network]peers=["127.0.0.1:20200"] # The peer list to connect
# AMOP configuration# You can use following two methods to configure as a private topic message sender or subscriber.# Usually, the public key and private key is generated by subscriber.# Message sender receive public key from topic subscriber then make configuration.# But, please do not config as both the message sender and the subscriber of one private topic, or you may send the message to yourself.
# Configure a private topic as a topic message sender.# [[amop]]# topicName = "PrivateTopic"# publicKeys = [ "conf/amop/consumer_public_key_1.pem" ] # Public keys of the nodes that you want to send AMOP message of this topic to.
# Configure a private topic as a topic subscriber.# [[amop]]# topicName = "PrivateTopic"# privateKey = "conf/amop/consumer_private_key.p12" # Your private key that used to subscriber verification.# password = "123456"
[account]keyStoreDir = "account" # The directory to load/store the account file, default is "account"# accountFilePath = "" # The account file path (default load from the path specified by the keyStoreDir)accountFileFormat = "pem" # The storage format of account file (Default is "pem", "p12" as an option)
# accountAddress = "" # The transactions sending account address # Default is a randomly generated account # The randomly generated account is stored in the path specified by the keyStoreDir
# password = "" # The password used to load the account file
[threadPool]# channelProcessorThreadSize = "16" # The size of the thread pool to process channel callback # Default is the number of cpu cores
# receiptProcessorThreadSize = "16" # The size of the thread pool to process transaction receipt notification # Default is the number of cpu cores
maxBlockingQueueSize = "102400" # The max blocking queue size of the thread pool
导入Fisco依赖:
<!--FiSCO依赖--><dependency> <groupId>org.fisco-bcos.java-sdk</groupId> <artifactId>fisco-bcos-java-sdk</artifactId> <version>2.9.1</version></dependency>创建测试类:
package com.ruoyi.fisco;
import org.fisco.bcos.sdk.BcosSDK;import org.fisco.bcos.sdk.client.Client;import org.fisco.bcos.sdk.config.Config;import org.fisco.bcos.sdk.config.ConfigOption;import org.fisco.bcos.sdk.crypto.keypair.CryptoKeyPair;import org.fisco.bcos.sdk.transaction.manager.AssembleTransactionProcessor;import org.fisco.bcos.sdk.transaction.manager.TransactionProcessorFactory;import org.fisco.bcos.sdk.transaction.model.dto.TransactionResponse;import org.junit.Test;
import java.util.ArrayList;import java.util.List;
/** * @Author: liuchang * @CreateTime: 2024-03-17 11:07 * @Description: TODO * @Version: 1.0 */public class RecordTest { public final String configFile = "src/main/resources/config-example.toml";
@Test public void testAddRecord() throws Exception { ConfigOption configOption = Config.load(configFile); // 初始化BcosSDK对象 BcosSDK sdk = new BcosSDK(configOption); // 获取Client对象,此处传入的群组ID为1 Client client = sdk.getClient(Integer.valueOf(1)); // 构造AssembleTransactionProcessor对象,需要传入client对象,CryptoKeyPair对象和abi、binary文件存放的路径。abi和binary文件需要在上一步复制到定义的文件夹中。 CryptoKeyPair keyPair = client.getCryptoSuite().createKeyPair();
AssembleTransactionProcessor transactionProcessor = TransactionProcessorFactory.createAssembleTransactionProcessor(client, keyPair, "src/main/resources/abi/", "src/main/resources/bin");
String recordid = "2"; String description = "{\"name\": \"张三\", \"age\": 24, \"gender\": true}"; String remark = "2024年3月17 11.22";
// 创建调用交易函数的参数,此处为传入一个参数 List<Object> params = new ArrayList<>(); params.add(recordid); params.add(description); params.add(remark); // 调用HelloWorld合约,合约地址为helloWorldAddress, 调用函数名为『set』,函数参数类型为params TransactionResponse transactionResponse = transactionProcessor.sendTransactionAndGetResponseByContractLoader("Record", "0x34a9d8a36b69c3a7a9b0fd373385b26886dc8c22", "insert", params); List<Object> returnValues = transactionResponse.getReturnObject(); if (returnValues != null) { for (Object value : returnValues) { System.out.println("主键返回值:"+value.toString()); } } }
@Test public void testSelectRecord() throws Exception { ConfigOption configOption = Config.load(configFile); // 初始化BcosSDK对象 BcosSDK sdk = new BcosSDK(configOption); // 获取Client对象,此处传入的群组ID为1 Client client = sdk.getClient(Integer.valueOf(1)); // 构造AssembleTransactionProcessor对象,需要传入client对象,CryptoKeyPair对象和abi、binary文件存放的路径。abi和binary文件需要在上一步复制到定义的文件夹中。 CryptoKeyPair keyPair = client.getCryptoSuite().createKeyPair();
AssembleTransactionProcessor transactionProcessor = TransactionProcessorFactory.createAssembleTransactionProcessor(client, keyPair, "src/main/resources/abi/", "src/main/resources/bin");
String recordid = "2"; // 创建调用交易函数的参数,此处为传入一个参数 List<Object> params = new ArrayList<>(); params.add(recordid); // 调用HelloWorld合约,合约地址为helloWorldAddress, 调用函数名为『set』,函数参数类型为params TransactionResponse transactionResponse = transactionProcessor.sendTransactionAndGetResponseByContractLoader("Record", "0x34a9d8a36b69c3a7a9b0fd373385b26886dc8c22", "getResult", params); List<Object> returnValues = transactionResponse.getReturnObject(); if (returnValues != null) { for (Object value : returnValues) { System.out.println("主键返回值:"+value.toString()); } } }
@Test public void testUpdateRecord() throws Exception { ConfigOption configOption = Config.load(configFile); // 初始化BcosSDK对象 BcosSDK sdk = new BcosSDK(configOption); // 获取Client对象,此处传入的群组ID为1 Client client = sdk.getClient(Integer.valueOf(1)); // 构造AssembleTransactionProcessor对象,需要传入client对象,CryptoKeyPair对象和abi、binary文件存放的路径。abi和binary文件需要在上一步复制到定义的文件夹中。 CryptoKeyPair keyPair = client.getCryptoSuite().createKeyPair();
AssembleTransactionProcessor transactionProcessor = TransactionProcessorFactory.createAssembleTransactionProcessor(client, keyPair, "src/main/resources/abi/", "src/main/resources/bin");
String recordid = "2"; String description = "{\"name\": \"张三\", \"age\": 24, \"gender\": true}"; String remark = "2024年3月17 14:26";
// 创建调用交易函数的参数,此处为传入一个参数 List<Object> params = new ArrayList<>(); params.add(recordid); params.add(description); params.add(remark); // 调用HelloWorld合约,合约地址为helloWorldAddress, 调用函数名为『set』,函数参数类型为params TransactionResponse transactionResponse = transactionProcessor.sendTransactionAndGetResponseByContractLoader("Record", "0x34a9d8a36b69c3a7a9b0fd373385b26886dc8c22", "update", params); List<Object> returnValues = transactionResponse.getReturnObject(); if (returnValues != null) { for (Object value : returnValues) { System.out.println("主键返回值:"+value.toString()); } } }
}测试查询:
测试更改:
测试查询: