文章目录
- 项目框架:
- 开发流程:
- 1、创建一个Java工程
- 2、引入FISCO BCOS Java SDK
- 3、配置SDK证书
- 4、业务逻辑开发
- 第一步.将3编译好的Java合约引入项目中
- 第二步.开发业务逻辑
- 5. 运行应用
项目框架:
|-- 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 // 项目运行脚本
开发流程:
1、创建一个Java工程
在IntelliJ IDE中创建一个gradle项目,勾选Gradle和Java,并输入工程名asset-app
。
注意:该项目的源码可以用以下方法获得并参考。(此步骤为非必须步骤)
$ 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
2、引入FISCO BCOS Java 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"
}
}
testImplementation group: 'junit', name: 'junit', version: '4.12'
implementation ('org.fisco-bcos.java-sdk:fisco-bcos-java-sdk:2.9.1')
3、配置SDK证书
修改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.9.1")
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>
注意: 如果搭链时设置的jsonrpc_listen_ip为127.0.0.1或者0.0.0.0,channel_port为20200, 则applicationContext.xml
配置不用修改。若区块链节点配置有改动,需要同样修改配置applicationContext.xml
的network
属性下的peers
配置选项,配置所连接节点的IP:channel_listen_port
。
在以上配置文件中,我们指定了证书存放的位certPath
的值为conf
。接下来我们需要把SDK用于连接节点的证书放到指定的conf
目录下。
# 假设我们将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
4、业务逻辑开发
我们已经介绍了如何在自己的项目中引入以及配置Java SDK,本节介绍如何通过Java程序调用合约,同样以示例的资产管理说明。
第一步.将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
实现对合约的部署与调用
AssetClient.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);
}
}
让我们通过AssetClient这个例子,来了解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
。
#!/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 $@
接着,配置好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.9.1")
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 // 项目运行脚本
5. 运行应用
至此我们已经介绍使用区块链开发资产管理应用的所有流程并实现了功能,接下来可以运行项目,测试功能是否正常。
- 编译
# 切换到项目目录
$ 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
总结: 至此,我们通过合约开发,合约编译,SDK配置与业务开发构建了一个基于FISCO BCOS联盟区块链的应用。