FISCO BCOS搭建
搭建WeBASE
部署文档地址:https://webasedoc.readthedocs.io/zh-cn/latest/docs/WeBASE/install.html
①配置环境
1.1 检查Java
配置JAVA_HOME:
cd /usr/lib/jvm
ls
vim /etc/profile
export JAVA_HOME=/usr/lib/jvm/java-1.11.0-openjdk-amd64 # 此处为自己的jdk版本号
export JRE_HOME=$JAVA_HOME/jre
export CLASSPATH=$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATH
export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH
source /etc/profile
echo $JAVA_HOME
1.2 检查mysql
Ubuntu安装:
注:这里没有安装官方文档给的mysql 5.7 。因为在安装时发生报错,所以采用了Ubuntu的默认mysql 8版本:
sudo apt update
sudo apt install mysql-server
查看mysql版本:8.0.36-0ubuntu0.22.04.1
初始化root密码:
mysql
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root';
设置远程登陆:
mysql -uroot -p
use 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.6
sudo apt-get install -y python3.6
sudo apt-get install -y python3-pip
python --version
# python3时
python3 --version
1.4 PyMySQL部署
sudo apt-get install -y python3-pip
sudo pip3 install PyMySQL
②安装WeBASE
2.1 部署WeBASE
获取部署安装包:
wget https://osp-1257653870.cos.ap-guangzhou.myqcloud.com/WeBASE/releases/download/v1.5.5/webase-deploy.zip
解压安装包:
unzip webase-deploy.zip
进入目录:
cd webase-deploy
修改配置:
更改数据库用户以及密码:
设置节点数量为4:
# 部署并启动所有服务
python3 deploy.py installAll
WeBASE管理平台:
- 一键部署完成后,打开浏览器(Chrome Safari或Firefox)访问
http://{deployIP}:{webPort}
示例:http://localhost:5000
2.2 问题(已解决)(与官网解决方法不一致)
验证码失效问题:在WeBASE官方文档中给出了解决方案:
查WeBASE-Node-Manager后台服务是否已启动成功。若启动成功,检查后台日志:
- 进入
webase-node-mgr
目录下,执行bash status.sh
检查服务是否启动,如果服务没有启动,运行bash start.sh
启动服务; - 如果服务已经启动,按照如下修改日志级别
cd webase-node-mgr
bash 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.1
mysql.port=3306
mysql.user=dbUsername
mysql.password=dbPassword
mysql.database=webasenodemanager
# 签名服务子系统mysql数据库配置
sign.mysql.ip=localhost
sign.mysql.port=3306
sign.mysql.user=dbUsername
sign.mysql.password=dbPassword
sign.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 fisco
cd ~/fisco && curl -LO https://github.com/FISCO-BCOS/console/releases/download/v2.9.2/download_console.sh && bash download_console.sh
cp -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());
}
}
}
}
测试查询:
测试更改:
测试查询:
评论