使用寶塔搭建自己的區塊鏈私鏈
不要裝到 root 文件夾下面!
使用 WeBASE 一鍵部署可直接跳到第二節
0x00 虛擬機上的安裝寶塔
建議使用 xshell 和 xftp 進行輔助搭建,具體使用教程請自行百度。
在虛擬機上部署寶塔環境,這里我用的是 CentOS 7 版本,安裝命令如下:
yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh
其他版本的系統參見 寶塔官網
安裝完成后會出現

此時,就可以使用相應的網址在瀏覽器中訪問寶塔的面板了,初次會需要登錄,賬號密碼如上圖所示

0x01 搭建 FISCO BCOS 區塊鏈網絡
這里主要摘要 CentOS 版本系統的安裝命令,其他版本系統簡潔明了詳細的參考文檔 FISCO BCOS中文文檔 ,這里的一切都是基於未安裝過 FISCO BCOS 的條件完成的,如果是想要升級版本的話,請移步官方文檔。
1、搭建單節點聯盟鏈
1.1 搭建 FISCO BCOS 環境
使用命令 sudo yum install -y openssl openssl-devel 安裝依賴

創建操作目錄,下載安裝腳本文件,雖然名字任意,但是還是建議命名為 fisco
## 創建操作目錄
cd ~ && mkdir -p fisco && cd fisco
## 下載腳本
curl -#LO https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/v2.7.2/build_chain.sh && chmod u+x build_chain.sh

如果因為網絡問題導致長時間無法下載build_chain.sh腳本,請嘗試 curl -#LO https://osp-1257653870.cos.ap-guangzhou.myqcloud.com/FISCO-BCOS/FISCO-BCOS/releases/v2.7.2/build_chain.sh && chmod u+x build_chain.sh
在 fisco 目錄下執行下面的指令,生成一條單群組4節點的 FISCO 鏈。請確保機器的30300~30303,20200~20203,8545~8548端口沒有被占用。
bash build_chain.sh -l 127.0.0.1:4 -p 30300,20200,8545

成功后會提示 All completed。
啟動 FISCO BCOS 鏈
bash nodes/127.0.0.1/start_all.sh

## 檢查進程是否成功
## 正常情況會有類似下面的輸出; 如果進程數不為4,則進程沒有啟動(一般是端口被占用導致的)
ps -ef | grep -v grep | grep fisco-bcos
## 檢查日志輸出
## 正常情況會不停地輸出連接信息,從輸出可以看出node0與另外3個節點有連接。
tail -f nodes/127.0.0.1/node0/log/log* | grep connected
## 檢查是否在共識
## 正常情況會不停輸出++++Generating seal,表示共識正常。
tail -f nodes/127.0.0.1/node0/log/log* | grep +++

2、配置及使用控制台
2.1 配置控制台
安裝 java
#centos系統安裝java
sudo yum install -y java java-devel
獲取控制台並回到 fisco 目錄
cd ~/fisco && curl -LO https://github.com/FISCO-BCOS/console/releases/download/v2.7.2/download_console.sh && bash download_console.sh
## 速度慢的話用這個
cd ~/fisco && curl -#LO https://gitee.com/FISCO-BCOS/console/raw/master/tools/download_console.sh

拷貝控制台配置文件
## 最新版本控制台使用如下命令拷貝配置文件
## 若節點未采用默認端口,請將文件中的20200替換成節點對應的channel端口。
cp -n console/conf/config-example.toml console/conf/config.toml
配置控制台證書
cp -r nodes/127.0.0.1/sdk/* console/conf/
2.2 使用控制台
## 啟動
cd ~/fisco/console && bash start.sh

輸出這樣就算成功
## 查看版本
getNodeVersion
## 查看節點
getPeers

0x02 WeBase 的安裝配置
WeBASE 的運行原理

1、環境安裝
需要的環境
| 環境 | 版本 |
|---|---|
| Java | JDK 8 至JDK 14 |
| MySQL | MySQL-5.6及以上 |
| Python | Python3.6及以上 |
| PyMySQL |
-
Java 剛才我們已經安裝了
-
MySQL 可以在寶塔的面板里直接快捷安裝


-
PyMySQL 安裝
sudo yum -y install python36-pip sudo pip3 install PyMySQL
2、WeBASE 的部署
新建一個文件夾用於存放 WeBASE
mkdir webase
獲取部署安裝包:
# 建議直接掛個梯子在本機上下好后拖進虛擬機,不然可能會很慢,快的話當我沒說
wget https://github.com/WeBankFinTech/WeBASELargeFiles/releases/download/v1.4.3/webase-deploy.zip
解壓安裝包:
unzip webase-deploy.zip
進入目錄:
cd webase-deploy
解壓剛才下好的壓縮包
unzip webase-deploy.zip
進入解壓后的目錄
cd webase-deploy
Nginx 修改參見第三節,務必修改完畢再部署!
修改 common.properties 文件 vi common.properties
# 服務端口不能小於 1024
# WeBASE子系統的最新版本(v1.1.0或以上版本)
webase.web.version=v1.4.3
webase.mgr.version=v1.4.3
webase.sign.version=v1.4.3
webase.front.version=v1.4.3
# 節點管理子系統mysql數據庫配置
# 將設置里的 user 和 password 改成自己的
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
# 節點前置子系統h2數據庫名和所屬機構
front.h2.name=webasefront
front.org=fisco
# WeBASE管理平台服務端口
web.port=5000
# 節點管理子系統服務端口
mgr.port=5001
# 節點前置子系統端口
front.port=5002
# 簽名服務子系統端口
sign.port=5004
# 節點監聽Ip
node.listenIp=127.0.0.1
# 節點p2p端口
node.p2pPort=30300
# 節點鏈上鏈下端口
node.channelPort=20200
# 節點rpc端口
node.rpcPort=8545
# Encrypt type (0: standard, 1: guomi)
encrypt.type=0
# ssl encrypt type (0: standard ssl, 1: guomi ssl)
# only guomi type support guomi ssl
encrypt.sslType=0
# 是否使用已有的鏈(yes/no)
if.exist.fisco=no
# 使用已有鏈時需配置
# 已有鏈的路徑,start_all.sh腳本所在路徑
# 路徑下要存在sdk目錄
# 當使用非國密鏈,或者使用國密鏈,但是sdk和節點使用非國密ssl連接時,sdk目錄里存放非國密sdk證書(ca.crt、node.crt和node.key)
# 當使用國密鏈,並且sdk和節點使用國密ssl連接時,需在sdk目錄里創建gm目錄,gm目錄存放國密sdk證書(gmca.crt、gmsdk.crt、gmsdk.key、gmensdk.crt和gmensdk.key)
fisco.dir=/data/app/nodes/127.0.0.1
# 前置所連接節點的絕對路徑
# 路徑下要存在conf文件夾,conf里存放節點證書(ca.crt、node.crt和node.key)
node.dir=/data/app/nodes/127.0.0.1/node0
# 搭建新鏈時需配置
# FISCO-BCOS版本
fisco.version=2.7.0
# 搭建節點個數(默認兩個)
node.counts=nodeCounts
配置完成后執行 installAll 命令,部署服務將自動部署FISCO BCOS節點,並部署 WeBASE 中間件服務,包括簽名服務(sign)、節點前置(front)、節點管理服務(node-mgr)、節點管理前端(web)
- 部署腳本會拉取相關安裝包進行部署,需保持網絡暢通
- 首次部署需要下載編譯包和初始化數據庫,重復部署時可以根據提示不重復操作
- 部署過程中出現報錯時,可根據錯誤提示進行操作,或根據本文檔中的常見問題進行排查
- 不要用sudo執行腳本,例如
sudo python3 deploy.py installAll(sudo會導致無法獲取當前用戶的環境變量如JAVA_HOME),如果沒有用 sudo 命令還是出現了無法獲取 JAVA_HOME 環境變量的情況,請參見 Linux 下無法獲取 JAVA_HOME 環境變量的解決方案
# 部署並啟動所有服務
python3 deploy.py installAll
執行過程中可能會報錯端口正在被占用,那么只需要執行命令 vi common.properties 進行相應的端口更換即可,全部部署完畢后

后續使用命令
# 一鍵部署
部署並啟動所有服務 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、檢查執行
執行命令
$ ps -ef | grep node

$ ps -ef | grep webase.front

## 檢查節點管理服務 webase-node-manager 的進程
$ ps -ef | grep webase.node.mgr

。。。。其余的參照官網上的進行檢查
0x03 Nginx 的問題
當我們使用寶塔安裝 Nginx 后,需要在 webase-deploy/comm/temp.conf 中修改 /etc/nginx/mime.types 的文件位置為我們自己的nginx 目錄,一般來說用寶塔安裝的都在根目錄下的 www 文件夾下

在 webase-deploy/comm/ 文件夾下執行命令 vi temp.conf 進行更改

之后回到 webase-deploy 目錄下執行命令 python3 deploy.py installAll 重新安裝部署整個項目
之后若無問題應該就可以直接訪問了,初始賬號為 admin ,密碼為 Abcd1234

之后的配置就參見官方文檔了 WeBASE使用手冊
0x04 智能合約的部署及應用
1、新建合約

pragma solidity>=0.4.24 <0.6.11;
// 導入 kv 表的合約
import "./Table.sol";
contract Modeus {
event SetResult(int256 count);
KVTableFactory tableFactory;
string constant TABLE_NAME = "t_proof";
constructor() public {
//The fixed address is 0x1010 for KVTableFactory
tableFactory = KVTableFactory(0x1010);
// the parameters of createTable are tableName,keyField,"vlaueFiled1,vlaueFiled2,vlaueFiled3,..."
// 創建 kv 表單,id:序號,sid:數據庫所做修改對應的 id ,base 修改前,newbase 修改后
tableFactory.createTable(TABLE_NAME, "id", "sid,base,newbase");
}
// 獲取鏈上的信息
function get(string memory id) public view returns (bool, string memory, string memory,string memory) {
KVTable table = tableFactory.openTable(TABLE_NAME);
bool ok = false;
Entry entry;
(ok, entry) = table.get(id);
string memory sid;
string memory base;
string memory newbase;
if (ok) {
sid= entry.getString("sid");
base = entry.getString("base");
newbase = entry.getString("newbase");
}
return (ok, sid, base,newbase);
}
// 數據庫的修改上鏈
function set(string memory id, string memory sid, string memory base,string memory newbase)
public
returns (int256)
{
KVTable table = tableFactory.openTable(TABLE_NAME);
Entry entry = table.newEntry();
// the length of entry's field value should < 16MB
entry.set("id", id);
entry.set("sid", sid);
entry.set("base", base);
entry.set("newbase", newbase);
// the first parameter length of set should <= 255B
int256 count = table.set(id, entry);
emit SetResult(count);
return count;
}
}
官方的 table.sol 代碼如下:
contract TableFactory {
function openTable(string memory) public view returns (Table) {} //open table
function createTable(string memory, string memory, string memory) public returns (int256) {} //create table
}
//select condition
contract Condition {
function EQ(string memory, int256) public {}
function EQ(string memory, string memory) public {}
function NE(string memory, int256) public {}
function NE(string memory, string memory) public {}
function GT(string memory, int256) public {}
function GE(string memory, int256) public {}
function LT(string memory, int256) public {}
function LE(string memory, int256) public {}
function limit(int256) public {}
function limit(int256, int256) public {}
}
//one record
contract Entry {
function getInt(string memory) public view returns (int256) {}
function getUInt(string memory) public view returns (int256) {}
function getAddress(string memory) public view returns (address) {}
function getBytes64(string memory) public view returns (bytes1[64] memory) {}
function getBytes32(string memory) public view returns (bytes32) {}
function getString(string memory) public view returns (string memory) {}
function set(string memory, int256) public {}
function set(string memory, uint256) public {}
function set(string memory, string memory) public {}
function set(string memory, address) public {}
}
//record sets
contract Entries {
function get(int256) public view returns (Entry) {}
function size() public view returns (int256) {}
}
//Table main contract
contract Table {
function select(string memory, Condition) public view returns (Entries) {}
function insert(string memory, Entry) public returns (int256) {}
function update(string memory, Entry, Condition) public returns (int256) {}
function remove(string memory, Condition) public returns (int256) {}
function newEntry() public view returns (Entry) {}
function newCondition() public view returns (Condition) {}
}
contract KVTableFactory {
function openTable(string memory) public view returns (KVTable) {}
function createTable(string memory, string memory, string memory) public returns (int256) {}
}
//KVTable per permiary key has only one Entry
contract KVTable {
function get(string memory) public view returns (bool, Entry) {}
function set(string memory, Entry) public returns (int256) {}
function newEntry() public view returns (Entry) {}
}
編寫完成后進行編譯,編譯成功后進行部署

部署完成后進行本地測試

交易發起后去首頁查看是否有交易記錄,若有則成功
2、智能合約的本地調用
這里我們選擇使用 rest api 來調用智能合約,使用 post 方式接口 http://localhost:5002/WeBASE-Front/trans/handleWithSign
這里可以看一下官方文檔中的相關部分接口文檔 handleWithSign 文檔
1)參數表
| 序號 | 中文 | 參數名 | 類型 | 最大長度 | 必填 | 說明 |
|---|---|---|---|---|---|---|
| 1 | 用戶地址 | user | String | 是 | 用戶地址,可通過/privateKey接口創建 |
|
| 2 | 合約名稱 | contractName | String | 是 | ||
| 3 | 合約地址 | contractAddress | String | 是 | ||
| 4 | 方法名 | funcName | String | 是 | ||
| 5 | 合約編譯后生成的abi文件內容 | contractAbi | List | 是 | 合約中單個函數的ABI,若不存在同名函數可以傳入整個合約ABI,格式:JSONArray | |
| 6 | 方法參數 | funcParam | List | 否 | JSON數組,多個參數以逗號分隔(參數為數組時同理),如:["str1",["arr1","arr2"]],根據所調用的合約方法判斷是否必填 | |
| 7 | 群組ID | groupId | int | 是 | 默認為1 | |
| 8 | 合約路徑 | contractPath | int | 否 | ||
| 9 | 是否使用cns調用 | useCns | bool | 是 | ||
| 10 | cns名稱 | cnsName | String | 否 | CNS名稱,useCns為true時不能為空 | |
| 11 | cns版本 | version | String | 否 | CNS版本,useCns為true時不能為空 |
然后去構建參數列表,格式為:
{
"groupId" :1,
"signUserId": "458ecc77a08c486087a3dcbc7ab5a9c3",
"contractAbi":[{"constant":true,"inputs":[],"name":"getVersion","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getStorageCell","outputs":[{"name":"","type":"string"},{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"n","type":"string"}],"name":"setVersion","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"storageHash","type":"string"},{"name":"storageInfo","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}],
"contractAddress":"0x14d5af9419bb5f89496678e3e74ce47583f8c166",
"funcName":"set",
"funcParam":["test"],
"useCns":false
}
其中的 signUserId 在私鑰管理中可以查詢到,如果沒有私鑰的話新建一個

contractAbi 和 contractAddress(合約地址) 可以在 合約管理 -> 合約列表 中查詢到。
將模板中的 funcParam 更換為自己編寫的智能合約中的參數列表對應的數據。使用 postman 進行測試

如圖,測試成功。
3、在應用中使用智能合約
編寫工具類 BlockChainUtils
package utils;
import cn.hutool.http.HttpUtil;
public class BlockChainUtils {
// public static long id = 1;
public static String set(String id ,String sid,String base,String newBase){
String body="{\n" +
" \n" +
" \"groupId\" :1,\n" +
" \"signUserId\": \"bbc341c4e982xxxxxxxxxxx76167f00ab4842e\",\n" +
" \"contractAbi\":[{\"constant\":true,\"inputs\":[{\"name\":\"id\",\"type\":\"string\"}],\"name\":\"get\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"},{\"name\":\"\",\"type\":\"int256\"},{\"name\":\"\",\"type\":\"string\"},{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"id\",\"type\":\"string\"},{\"name\":\"sid\",\"type\":\"int256\"},{\"name\":\"nickname\",\"type\":\"string\"},{\"name\":\"content\",\"type\":\"string\"}],\"name\":\"set\",\"outputs\":[{\"name\":\"\",\"type\":\"int256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"count\",\"type\":\"int256\"}],\"name\":\"SetResult\",\"type\":\"event\"}],\n" +
" \"contractAddress\":\"0xef860c2xxxx24b38bxxe1b317ad2\",\n" +
" \"funcName\":\"set\",\n" +
" \"funcParam\":[\""+comment.getId()+"\","+comment.getSid()+",\""+comment.getNickname()+"\",\""+comment.getContent()+"\"]\n" +
"\n" +
"}";
System.out.println(body);
String url = "http://xxxxxxxxx:5002/WeBASE-Front/trans/handleWithSign";
String res = HttpUtil.post(url,body);
return res;
// id ++;
}
public static void get(){}
}
然后在其他地方調用 set 方法就行了。
