开发第一个区块链应用
本文介绍合约编译以及如何部署到区块链,通过Java SDK实现对区块链上合约的调用访问。
本教程要求用户熟悉Linux操作环境,具备Java开发的基本技能,能够使用Gradle工具,熟悉Solidity语法。
如果您还未搭建区块链网络,或未下载控制台,请先走完教程搭建第一个区块链网络。
本文记录自己的应用过程,具体参照开发第一个区块链应用
1. 合约开发
预期实现以下功能:
- 能够在区块链上进行资产注册
- 能够实现不同账户的转账
- 可以查询账户的资产金额
首先需要设计对应的智能合约,确定合约需要储存的数据,在此基础上确定智能合约对外提供的接口,最后给出各个接口的具体实现。
第一步:设计智能合约
存储设计
FISCO BCOS提供合约CRUD接口开发模式,可以通过合约创建表,并对创建的表进行增删改查操作。针对本应用需要设计一个存储资产管理的表t_asset ,该表字段如下:
- account: 主键,资产账户(string类型)
- asset_value: 资产金额(uint256类型)
其中account是主键,即操作t_asset 表时需要传入的字段,区块链根据该主键字段查询表中匹配的记录。t_asset 表示例如下:
接口设计
按照业务的设计目标,需要实现资产注册,转账,查询功能,对应功能的接口如下:
// 查询资产金额
function select(string account) public constant returns(int256, uint256)
// 资产注册
function register(string account, uint256 amount) public returns(int256)
// 资产转移
function transfer(string from_asset_account, string to_asset_account, uint256 amount) public returns(int256)
第二步:开发源码
根据我们第一步的存储和接口设计,创建一个Asset的智能合约,实现注册、转账、查询功能,并引入一个叫Table的系统合约,这个合约提供了CRUD接口。
# 进入console/contracts目录
cd ~/fisco/console/contracts/solidity
# 创建Asset.sol合约文件
vi Asset.sol
# 将Assert.sol合约内容写入。
# 并键入wq保存退出。
Asset.sol的内容如下:
pragma solidity ^0.4.24;
import "./Table.sol";
contract Asset {
// event
event RegisterEvent(int256 ret, string account, uint256 asset_value);
event TransferEvent(int256 ret, string from_account, string to_account, uint256 amount);
constructor() public {
// 构造函数中创建t_asset表
createTable();
}
function createTable() private {
TableFactory tf = TableFactory(0x1001);
// 资产管理表, key : account, field : asset_value
// | 资产账户(主键) | 资产金额 |
// |-------------------- |-------------------|
// | account | asset_value |
// |---------------------|-------------------|
//
// 创建表
tf.createTable("t_asset", "account", "asset_value");
}
function openTable() private returns(Table) {
TableFactory tf = TableFactory(0x1001);
Table table = tf.openTable("t_asset");
return table;
}
/*
描述 : 根据资产账户查询资产金额
参数 :
account : 资产账户
返回值:
参数一: 成功返回0, 账户不存在返回-1
参数二: 第一个参数为0时有效,资产金额
*/
function select(string account) public constant returns(int256, uint256) {
// 打开表
Table table = openTable();
// 查询
Entries entries = table.select(account, table.newCondition());
uint256 asset_value = 0;
if (0 == uint256(entries.size())) {
return (-1, asset_value);
} else {
Entry entry = entries.get(0);
return (0, uint256(entry.getInt("asset_value")));
}
}
/*
描述 : 资产注册
参数 :
account : 资产账户
amount : 资产金额
返回值:
0 资产注册成功
-1 资产账户已存在
-2 其他错误
*/
function register(string account, uint256 asset_value) public returns(int256){
int256 ret_code = 0;
int256 ret= 0;
uint256 temp_asset_value = 0;
// 查询账户是否存在
(ret, temp_asset_value) = select(account);
if(ret != 0) {
Table table = openTable();
Entry entry = table.newEntry();
entry.set("account", account);
entry.set("asset_value", int256(asset_value));
// 插入
int count = table.insert(account, entry);
if (count == 1) {
// 成功
ret_code = 0;
} else {
// 失败? 无权限或者其他错误
ret_code = -2;
}
} else {
// 账户已存在
ret_code = -1;
}
emit RegisterEvent(ret_code, account, asset_value);
return ret_code;
}
/*
描述 : 资产转移
参数 :
from_account : 转移资产账户
to_account : 接收资产账户
amount : 转移金额
返回值:
0 资产转移成功
-1 转移资产账户不存在
-2 接收资产账户不存在
-3 金额不足
-4 金额溢出
-5 其他错误
*/
function transfer(string from_account, string to_account, uint256 amount) public returns(int256) {
// 查询转移资产账户信息
int ret_code = 0;
int256 ret = 0;
uint256 from_asset_value = 0;
uint256 to_asset_value = 0;
// 转移账户是否存在?
(ret, from_asset_value) = select(from_account);
if(ret != 0) {
ret_code = -1;
// 转移账户不存在
emit TransferEvent(ret_code, from_account, to_account, amount);
return ret_code;
}
// 接受账户是否存在?
(ret, to_asset_value) = select(to_account);
if(ret != 0) {
ret_code = -2;
// 接收资产的账户不存在
emit TransferEvent(ret_code, from_account, to_account, amount);
return ret_code;
}
if(from_asset_value < amount) {
ret_code = -3;
// 转移资产的账户金额不足
emit TransferEvent(ret_code, from_account, to_account, amount);
return ret_code;
}
if (to_asset_value + amount < to_asset_value) {
ret_code = -4;
// 接收账户金额溢出
emit TransferEvent(ret_code, from_account, to_account, amount);
return ret_code;
}
Table table = openTable();
Entry entry0 = table.newEntry();
entry0.set("account", from_account);
entry0.set("asset_value", int256(from_asset_value - amount));
// 更新转账账户
int count = table.update(from_account, entry0, table.newCondition());
if(count != 1) {
ret_code = -5;
// 失败? 无权限或者其他错误?
emit TransferEvent(ret_code, from_account, to_account, amount);
return ret_code;
}
Entry entry1 = table.newEntry();
entry1.set("account", to_account);
entry1.set("asset_value", int256(to_asset_value + amount));
// 更新接收账户
table.update(to_account, entry1, table.newCondition());
emit TransferEvent(ret_code, from_account, to_account, amount);
return ret_code;
}
}
Asset.sol所引用的Table.sol已在~/fisco/console/contracts/solidity 目录下。该系统合约文件中的接口由FISCO BCOS底层实现。当业务合约需要操作CRUD接口时,均需要引入该接口合约文件。Table.sol 合约详细接口参考这里。
运行ls 命令,确保Asset.sol 和Table.sol 在目录~/fisco/console/contracts/solidity 下。
第三步:编译智能合约
.sol 的智能合约需要编译成ABI和BIN文件才能部署至区块链网络上。有了这两个文件即可凭借Java SDK进行合约部署和调用。但这种调用方式相对繁琐,需要用户根据合约ABI来传参和解析结果。为此,控制台提供的编译工具不仅可以编译出ABI和BIN文件,还可以自动生成一个与编译的智能合约同名的合约Java类。这个Java类是根据ABI生成的,帮助用户解析好了参数,提供同名的方法。当应用需要部署和调用合约时,可以调用该合约类的对应方法,传入指定参数即可。使用这个合约Java类来开发应用,可以极大简化用户的代码。
# 创建工作目录~/fisco
mkdir -p ~/fisco
# 下载控制台
cd ~/fisco && curl -#LO https://github.com/FISCO-BCOS/console/releases/download/v2.7.2/download_console.sh && bash download_console.sh
# 切换到fisco/console/目录
cd ~/fisco/console/
# 若控制台版本大于等于2.8.0,编译合约方法如下:(可通过bash sol2java.sh -h命令查看该脚本使用方法)
bash sol2java.sh -p org.fisco.bcos.asset.contract
# 若控制台版本小于2.8.0,编译合约(后面指定一个Java的包名参数,可以根据实际项目路径指定包名)如下:
./sol2java.sh org.fisco.bcos.asset.contract
运行成功之后,将会在console/contracts/sdk 目录生成java、abi和bin目录,如下所示。
# 其它无关文件省略
|-- abi # 生成的abi目录,存放solidity合约编译生成的abi文件
| |-- Asset.abi
| |-- Table.abi
|-- bin # 生成的bin目录,存放solidity合约编译生成的bin文件
| |-- Asset.bin
| |-- Table.bin
|-- contracts # 存放solidity合约源码文件,将需要编译的合约拷贝到该目录下
| |-- Asset.sol # 拷贝进来的Asset.sol合约,依赖Table.sol
| |-- Table.sol # 实现系统CRUD操作的合约接口文件
|-- java # 存放编译的包路径及Java合约文件
| |-- org
| |--fisco
| |--bcos
| |--asset
| |--contract
| |--Asset.java # Asset.sol合约生成的Java文件
| |--Table.java # Table.sol合约生成的Java文件
|-- sol2java.sh
java目录下生成了org/fisco/bcos/asset/contract/ 包路径目录,该目录下包含Asset.java 和Table.java 两个文件,其中Asset.java 是Java应用调用Asset.sol 合约需要的文件。
Asset.java 的主要接口:
package org.fisco.bcos.asset.contract;
public class Asset extends Contract {
// Asset.sol合约 transfer接口生成
public TransactionReceipt transfer(String from_account, String to_account, BigInteger amount);
// Asset.sol合约 register接口生成
public TransactionReceipt register(String account, BigInteger asset_value);
// Asset.sol合约 select接口生成
public Tuple2<BigInteger, BigInteger> select(String account) throws ContractException;
// 加载Asset合约地址,生成Asset对象
public static Asset load(String contractAddress, Client client, CryptoKeyPair credential);
// 部署Assert.sol合约,生成Asset对象
public static Asset deploy(Client client, CryptoKeyPair credential) throws ContractException;
}
其中load与deploy函数用于构造Asset对象,其他接口分别用来调用对应的solidity合约的接口。
在通过控制台提供的编译工具编译后得到的Java文件就可以在后面的程序编写中直接带入文件中使用
2.运行一个区块链项目
所需依赖
获取源码
$ cd ~/fisco
$ curl -#LO https://github.com/FISCO-BCOS/LargeFiles/raw/master/tools/asset-app.tar.gz
# 解压得到Java工程项目asset-app
$ tar -zxf asset-app.tar.gz
如果因为网络问题导致长时间无法下载,请尝试将199.232.28.133 raw.githubusercontent.com 追加到/etc/hosts 中,或者请尝试 curl -#LO https://osp-1257653870.cos.ap-guangzhou.myqcloud.com/FISCO-BCOS/FISCO-BCOS/tools/asset-app.tar.gz
编译
# 切换到项目目录
$ cd ~/fisco/asset-app
# 编译项目
$ ./gradlew build
编译成功之后,将在项目根目录下生成dist 目录。dist目录下有一个asset_run.sh 脚本,简化项目运行。现在开始一一验证本文开始定下的需求。
部署Asset.sol 合约
注意:在部署的过程中参照上篇文章先启动节点
# 进入dist目录
$ cd dist
$ bash asset_run.sh deploy
Deploy Asset successfully, contract address is 0xd09ad04220e40bb8666e885730c8c460091a4775
合约调用
#注册资产
$ bash asset_run.sh register Alice 100000
Register account successfully => account: Alice, value: 100000
$ bash asset_run.sh register Bob 100000
Register account successfully => account: Bob, value: 100000
#查询资产
$ bash asset_run.sh query Alice
account Alice, value 100000
$ bash asset_run.sh query Bob
account Bob, value 100000
#资产转移
$ bash asset_run.sh transfer Alice Bob 50000
Transfer successfully => from_account: Alice, to_account: Bob, amount: 50000
$ bash asset_run.sh query Alice
account Alice, value 50000
$ bash asset_run.sh query Bob
account Bob, value 150000
分析
在使用./gradlew build 编译后会,在项目根目录下生成dist 目录。dist目录下有一个asset_run.sh 脚本,简化项目运行。bash asset_run.sh deploy 就可以直接部署合约,后面的各种合约操作也都包含在该脚本里如:bash asset_run.sh register Alice 100000
java源码分析
注意:该部分其实就是在构建上述下载的java文件,这里主要是为了了解其工作原理
1.创建gradle项目
2.SDK相关设置
在build.gradle文件中的dependencies 下加入对FISCO BCOS Java SDK的引用。
repositories {
mavenCentral()
maven {
allowInsecureProtocol = true
url "http://maven.aliyun.com/nexus/content/groups/public/"
}
maven {
allowInsecureProtocol = true
url "https://oss.sonatype.org/content/repositories/snapshots"
}
}
引入Java SDK jar包
testImplementation group: 'junit', name: 'junit', version: '4.12'
implementation ('org.fisco-bcos.java-sdk:fisco-bcos-java-sdk:2.8.0')
修改build.gradle 文件,引入Spring框架
def spring_version = "4.3.27.RELEASE"
List spring = [
"org.springframework:spring-core:$spring_version",
"org.springframework:spring-beans:$spring_version",
"org.springframework:spring-context:$spring_version",
"org.springframework:spring-tx:$spring_version",
]
dependencies {
testImplementation group: 'junit', name: 'junit', version: '4.12'
implementation ("org.fisco-bcos.java-sdk:fisco-bcos-java-sdk:2.8.0")
implementation spring
}
在asset-app/test/resources 目录下创建配置文件applicationContext.xml ,写入配置内容。各配置项的内容可参考Java SDK 配置说明,该配置说明以toml配置文件为例,本例中的配置项与该配置项相对应。
applicationContext.xml的内容如下:
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<bean id="defaultConfigProperty" class="org.fisco.bcos.sdk.config.model.ConfigProperty">
<property name="cryptoMaterial">
<map>
<entry key="certPath" value="conf" />
</map>
</property>
<property name="network">
<map>
<entry key="peers">
<list>
<value>127.0.0.1:20200</value>
<value>127.0.0.1:20201</value>
</list>
</entry>
</map>
</property>
<property name="account">
<map>
<entry key="keyStoreDir" value="account" />
<entry key="accountAddress" value="" />
<entry key="accountFileFormat" value="pem" />
<entry key="password" value="" />
<entry key="accountFilePath" value="" />
</map>
</property>
<property name="threadPool">
<map>
<entry key="channelProcessorThreadSize" value="16" />
<entry key="receiptProcessorThreadSize" value="16" />
<entry key="maxBlockingQueueSize" value="102400" />
</map>
</property>
</bean>
<bean id="defaultConfigOption" class="org.fisco.bcos.sdk.config.ConfigOption">
<constructor-arg name="configProperty">
<ref bean="defaultConfigProperty"/>
</constructor-arg>
</bean>
<bean id="bcosSDK" class="org.fisco.bcos.sdk.BcosSDK">
<constructor-arg name="configOption">
<ref bean="defaultConfigOption"/>
</constructor-arg>
</bean>
</beans>
在以上配置文件中,我们指定了证书存放的位certPath 的值为conf 。接下来我们需要把SDK用于连接节点的证书放到指定的conf 目录下。
需要从nodes/127.0.0.1/sdk中获取证书,复制即可
# 假设我们将asset-app放在~/fisco目录下 进入~/fisco目录
$ cd ~/fisco
# 创建放置证书的文件夹
$ mkdir -p asset-app/src/test/resources/conf
# 拷贝节点证书到项目的资源目录
$ cp -r nodes/127.0.0.1/sdk/* asset-app/src/test/resources/conf
# 若在IDE直接运行,拷贝证书到resources路径
$ mkdir -p asset-app/src/main/resources/conf
$ cp -r nodes/127.0.0.1/sdk/* asset-app/src/main/resources/conf
3.引入Java合约
cd ~/fisco
# 将编译好的合约Java类引入项目中。
cp console/contracts/sdk/java/org/fisco/bcos/asset/contract/Asset.java asset-app/src/main/java/org/fisco/bcos/asset/contract/Asset.java
在路径/src/main/java/org/fisco/bcos/asset/client 目录下,创建AssetClient.java 类,通过调用Asset.java 实现对合约的部署与调用
package org.fisco.bcos.asset.client;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.List;
import java.util.Properties;
import org.fisco.bcos.asset.contract.Asset;
import org.fisco.bcos.sdk.BcosSDK;
import org.fisco.bcos.sdk.abi.datatypes.generated.tuples.generated.Tuple2;
import org.fisco.bcos.sdk.client.Client;
import org.fisco.bcos.sdk.crypto.keypair.CryptoKeyPair;
import org.fisco.bcos.sdk.model.TransactionReceipt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
public class AssetClient {
static Logger logger = LoggerFactory.getLogger(AssetClient.class);
private BcosSDK bcosSDK;
private Client client;
private CryptoKeyPair cryptoKeyPair;
public void initialize() throws Exception {
@SuppressWarnings("resource")
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
bcosSDK = context.getBean(BcosSDK.class);
client = bcosSDK.getClient(1);
cryptoKeyPair = client.getCryptoSuite().createKeyPair();
client.getCryptoSuite().setCryptoKeyPair(cryptoKeyPair);
logger.debug("create client for group1, account address is " + cryptoKeyPair.getAddress());
}
public void deployAssetAndRecordAddr() {
try {
Asset asset = Asset.deploy(client, cryptoKeyPair);
System.out.println(
" deploy Asset success, contract address is " + asset.getContractAddress());
recordAssetAddr(asset.getContractAddress());
} catch (Exception e) {
// TODO Auto-generated catch block
// e.printStackTrace();
System.out.println(" deploy Asset contract failed, error message is " + e.getMessage());
}
}
public void recordAssetAddr(String address) throws FileNotFoundException, IOException {
Properties prop = new Properties();
prop.setProperty("address", address);
final Resource contractResource = new ClassPathResource("contract.properties");
FileOutputStream fileOutputStream = new FileOutputStream(contractResource.getFile());
prop.store(fileOutputStream, "contract address");
}
public String loadAssetAddr() throws Exception {
// load Asset contact address from contract.properties
Properties prop = new Properties();
final Resource contractResource = new ClassPathResource("contract.properties");
prop.load(contractResource.getInputStream());
String contractAddress = prop.getProperty("address");
if (contractAddress == null || contractAddress.trim().equals("")) {
throw new Exception(" load Asset contract address failed, please deploy it first. ");
}
logger.info(" load Asset address from contract.properties, address is {}", contractAddress);
return contractAddress;
}
public void queryAssetAmount(String assetAccount) {
try {
String contractAddress = loadAssetAddr();
Asset asset = Asset.load(contractAddress, client, cryptoKeyPair);
Tuple2<BigInteger, BigInteger> result = asset.select(assetAccount);
if (result.getValue1().compareTo(new BigInteger("0")) == 0) {
System.out.printf(" asset account %s, value %s \n", assetAccount, result.getValue2());
} else {
System.out.printf(" %s asset account is not exist \n", assetAccount);
}
} catch (Exception e) {
// TODO Auto-generated catch block
// e.printStackTrace();
logger.error(" queryAssetAmount exception, error message is {}", e.getMessage());
System.out.printf(" query asset account failed, error message is %s\n", e.getMessage());
}
}
public void registerAssetAccount(String assetAccount, BigInteger amount) {
try {
String contractAddress = loadAssetAddr();
Asset asset = Asset.load(contractAddress, client, cryptoKeyPair);
TransactionReceipt receipt = asset.register(assetAccount, amount);
List<Asset.RegisterEventEventResponse> response = asset.getRegisterEventEvents(receipt);
if (!response.isEmpty()) {
if (response.get(0).ret.compareTo(new BigInteger("0")) == 0) {
System.out.printf(
" register asset account success => asset: %s, value: %s \n", assetAccount, amount);
} else {
System.out.printf(
" register asset account failed, ret code is %s \n", response.get(0).ret.toString());
}
} else {
System.out.println(" event log not found, maybe transaction not exec. ");
}
} catch (Exception e) {
// TODO Auto-generated catch block
// e.printStackTrace();
logger.error(" registerAssetAccount exception, error message is {}", e.getMessage());
System.out.printf(" register asset account failed, error message is %s\n", e.getMessage());
}
}
public void transferAsset(String fromAssetAccount, String toAssetAccount, BigInteger amount) {
try {
String contractAddress = loadAssetAddr();
Asset asset = Asset.load(contractAddress, client, cryptoKeyPair);
TransactionReceipt receipt = asset.transfer(fromAssetAccount, toAssetAccount, amount);
List<Asset.TransferEventEventResponse> response = asset.getTransferEventEvents(receipt);
if (!response.isEmpty()) {
if (response.get(0).ret.compareTo(new BigInteger("0")) == 0) {
System.out.printf(
" transfer success => from_asset: %s, to_asset: %s, amount: %s \n",
fromAssetAccount, toAssetAccount, amount);
} else {
System.out.printf(
" transfer asset account failed, ret code is %s \n", response.get(0).ret.toString());
}
} else {
System.out.println(" event log not found, maybe transaction not exec. ");
}
} catch (Exception e) {
// TODO Auto-generated catch block
// e.printStackTrace();
logger.error(" registerAssetAccount exception, error message is {}", e.getMessage());
System.out.printf(" register asset account failed, error message is %s\n", e.getMessage());
}
}
public static void Usage() {
System.out.println(" Usage:");
System.out.println(
"\t java -cp conf/:lib/*:apps/* org.fisco.bcos.asset.client.AssetClient deploy");
System.out.println(
"\t java -cp conf/:lib/*:apps/* org.fisco.bcos.asset.client.AssetClient query account");
System.out.println(
"\t java -cp conf/:lib/*:apps/* org.fisco.bcos.asset.client.AssetClient register account value");
System.out.println(
"\t java -cp conf/:lib/*:apps/* org.fisco.bcos.asset.client.AssetClient transfer from_account to_account amount");
System.exit(0);
}
public static void main(String[] args) throws Exception {
if (args.length < 1) {
Usage();
}
AssetClient client = new AssetClient();
client.initialize();
switch (args[0]) {
case "deploy":
client.deployAssetAndRecordAddr();
break;
case "query":
if (args.length < 2) {
Usage();
}
client.queryAssetAmount(args[1]);
break;
case "register":
if (args.length < 3) {
Usage();
}
client.registerAssetAccount(args[1], new BigInteger(args[2]));
break;
case "transfer":
if (args.length < 4) {
Usage();
}
client.transferAsset(args[1], args[2], new BigInteger(args[3]));
break;
default:
{
Usage();
}
}
System.exit(0);
}
}
分析FISCO BCOS Java SDK的调用
初始化代码的主要功能为构造Client与CryptoKeyPair对象,这两个对象在创建对应的合约类对象(调用合约类的deploy或者load函数)时需要使用。
// 函数initialize中进行初始化
// 初始化BcosSDK
@SuppressWarnings("resource")
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
bcosSDK = context.getBean(BcosSDK.class);
// 初始化可向群组1发交易的Client
client = bcosSDK.getClient(1);
// 随机生成发送交易的公私钥对
cryptoKeyPair = client.getCryptoSuite().createKeyPair();
client.getCryptoSuite().setCryptoKeyPair(cryptoKeyPair);
logger.debug("create client for group1, account address is " + cryptoKeyPair.getAddress());
可以使用deploy或者load函数初始化合约对象,两者使用场景不同,前者适用于初次部署合约,后者在合约已经部署并且已知合约地址时使用。
// 部署合约
Asset asset = Asset.deploy(client, cryptoKeyPair);
// 加载合约地址
Asset asset = Asset.load(contractAddress, client, cryptoKeyPair);
使用合约对象调用对应的接口,处理返回结果。
// select接口调用
Tuple2<BigInteger, BigInteger> result = asset.select(assetAccount);
// register接口调用
TransactionReceipt receipt = asset.register(assetAccount, amount);
// transfer接口
TransactionReceipt receipt = asset.transfer(fromAssetAccount, toAssetAccount, amount);
在asset-app/tool 目录下添加一个调用AssetClient的脚本asset_run.sh 。
4.创立脚本
在asset-app/tool 目录下添加一个调用AssetClient的脚本asset_run.sh 。
#!/bin/bash
function usage()
{
echo " Usage : "
echo " bash asset_run.sh deploy"
echo " bash asset_run.sh query asset_account "
echo " bash asset_run.sh register asset_account asset_amount "
echo " bash asset_run.sh transfer from_asset_account to_asset_account amount "
echo " "
echo " "
echo "examples : "
echo " bash asset_run.sh deploy "
echo " bash asset_run.sh register Asset0 10000000 "
echo " bash asset_run.sh register Asset1 10000000 "
echo " bash asset_run.sh transfer Asset0 Asset1 11111 "
echo " bash asset_run.sh query Asset0"
echo " bash asset_run.sh query Asset1"
exit 0
}
case $1 in
deploy)
[ $# -lt 1 ] && { usage; }
;;
register)
[ $# -lt 3 ] && { usage; }
;;
transfer)
[ $# -lt 4 ] && { usage; }
;;
query)
[ $# -lt 2 ] && { usage; }
;;
*)
usage
;;
esac
java -Djdk.tls.namedGroups="secp256k1" -cp 'apps/*:conf/:lib/*' org.fisco.bcos.asset.client.AssetClient $@
5.日志记录
接着,配置好log。在asset-app/src/test/resources目录下创建log4j.properties
### set log levels ###
log4j.rootLogger=DEBUG, file
### output the log information to the file ###
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.DatePattern='_'yyyyMMddHH'.log'
log4j.appender.file.File=./log/sdk.log
log4j.appender.file.Append=true
log4j.appender.file.filter.traceFilter=org.apache.log4j.varia.LevelRangeFilter
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p] [%-d{yyyy-MM-dd HH:mm:ss}] %C{1}.%M(%L) | %m%n
###output the log information to the console ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%p] [%-d{yyyy-MM-dd HH:mm:ss}] %C{1}.%M(%L) | %m%n
接着,通过配置gradle中的Jar命令,指定复制和编译任务。并引入日志库,在asset-app/src/test/resources 目录下,创建一个空的contract.properties 文件,用于应用在运行时存放合约地址。
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
compile ("org.fisco-bcos.java-sdk:fisco-bcos-java-sdk:2.7.2")
compile spring
compile ('org.slf4j:slf4j-log4j12:1.7.25')
runtime ('org.slf4j:slf4j-log4j12:1.7.25')
}
jar {
destinationDir file('dist/apps')
archiveName project.name + '.jar'
exclude '**/*.xml'
exclude '**/*.properties'
exclude '**/*.crt'
exclude '**/*.key'
doLast {
copy {
from configurations.runtime
into 'dist/lib'
}
copy {
from file('src/test/resources/')
into 'dist/conf'
}
copy {
from file('tool/')
into 'dist/'
}
copy {
from file('src/test/resources/contract')
into 'dist/contract'
}
}
}
至此,我们已经完成了这个应用的开发。最后,我们得到的assert-app的目录结构如下:
|-- build.gradle // gradle配置文件
|-- gradle
| |-- wrapper
| |-- gradle-wrapper.jar // 用于下载Gradle的相关代码实现
| |-- gradle-wrapper.properties // wrapper所使用的配置信息,比如gradle的版本等信息
|-- gradlew // Linux或者Unix下用于执行wrapper命令的Shell脚本
|-- gradlew.bat // Windows下用于执行wrapper命令的批处理脚本
|-- src
| |-- main
| | |-- java
| | | |-- org
| | | |-- fisco
| | | |-- bcos
| | | |-- asset
| | | |-- client // 放置客户端调用类
| | | |-- AssetClient.java
| | | |-- contract // 放置Java合约类
| | | |-- Asset.java
| | |-- resources
| | |-- conf
| | |-- ca.crt
| | |-- node.crt
| | |-- node.key
| | |-- sdk.crt
| | |-- sdk.key
| | |-- sdk.publickey
| | |-- applicationContext.xml // 项目配置文件
| | |-- contract.properties // 存储部署合约地址的文件
| | |-- log4j.properties // 日志配置文件
| | |-- contract //存放solidity约文件
| | |-- Asset.sol
| | |-- Table.sol
| |-- test
| |-- resources // 存放代码资源文件
| |-- conf
| |-- ca.crt
| |-- node.crt
| |-- node.key
| |-- sdk.crt
| |-- sdk.key
| |-- sdk.publickey
| |-- applicationContext.xml // 项目配置文件
| |-- contract.properties // 存储部署合约地址的文件
| |-- log4j.properties // 日志配置文件
| |-- contract //存放solidity约文件
| |-- Asset.sol
| |-- Table.sol
|
|-- tool
|-- asset_run.sh // 项目运行脚本
6.总结
这里的Java项目中并不是主动构建的而是通过脚本编译启动,注意其中的几个地方:skd的配置在applicationContext.xml 中同时该配置把证书指向conf文件夹。在引入合约时是之前编译产生的Java文件,在AssetClient.java 中调用实现相关功能。
|