FISCO BCOS开发第一个区块链应用
1.应用的需求
区块链天然具有防篡改,可追溯等特性,这些特性决定其更容易受金融领域的青睐。本示例中,将会提供一个简易的资产管理的开发示例,并最终实现以下功能:
- 能够在区块链上进行资产注册
- 能够实现不同账户的转账
- 可以查询账户的资产金额
2.设计与开发智能合约
在区块链上进行应用开发时,结合业务需求,首先需要设计对应的智能合约,确定合约需要储存的数据,在此基础上确定智能合约对外提供的接口,最后给出各个接口的具体实现。
第一步. 设计智能合约
存储设计
FISCO BCOS提供合约CRUD接口开发模式,可以通过合约创建表,并对创建的表进行增删改查操作。针对本应用需要设计一个存储资产管理的表t_asset
,该表字段如下:
- account: 主键,资产账户(string类型)
- asset_value: 资产金额(uint256类型)
其中account是主键,即操作t_asset
表时需要传入的字段,区块链根据该主键字段查询表中匹配的记录。t_asset
表示例如下:
account | asset_value |
---|---|
Alice | 10000 |
Bob | 20000 |
接口设计
按照业务的设计目标,需要实现资产注册,转账,查询功能,对应功能的接口如下:
// 查询资产金额 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; } }
运行ls
命令,确保Asset.sol
和Table.sol
在目录~/fisco/console/contracts/solidity
下。
3. 编译智能合约
.sol
的智能合约需要编译成ABI和BIN文件才能部署至区块链网络上。有了这两个文件即可凭借Java SDK进行合约部署和调用。但这种调用方式相对繁琐,需要用户根据合约ABI来传参和解析结果。为此,控制台提供的编译工具不仅可以编译出ABI和BIN文件,还可以自动生成一个与编译的智能合约同名的合约Java类。这个Java类是根据ABI生成的,帮助用户解析好了参数,提供同名的方法。当应用需要部署和调用合约时,可以调用该合约类的对应方法,传入指定参数即可。使用这个合约Java类来开发应用,可以极大简化用户的代码。
mkdir -p ~/fisco# 创建工作目录~/fisco
cd ~/fisco && curl -#LO https://github.com/FISCO-BCOS/console/releases/download/v2.9.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合约的接口。
4.创建区块链应用项目
第一步. 安装环境
首先,我们需要安装JDK以及集成开发环境
Java:JDK 14
首先,在官网上下载JDK14并安装
然后,修改环境变量 在/etc/profile中
#JAVA environment export JAVA_HOME=/usr/lib/jdk-14.0.2 export JRE_HOME=$JAVA_HOME/jre export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$JRE_HOME/lib export PATH=$PATH:$JAVA_HOME/bin
- IDE:IntelliJ IDE 下载社区版即可
第二步. 创建一个Java工程
在IntelliJ IDE中创建一个gradle项目,勾选Gradle和Java,并输入工程名asset-app
如果创建的项目没有src目录,则创建task任务,这个时候点击右侧Gradle 打开other 就会发现刚才我们写的任务名会在这里,点击执行任务
task 'create-dirs' { doLast { sourceSets*.java.srcDirs*.each { it.mkdirs() } sourceSets*.resources.srcDirs*.each { it.mkdirs() } } }
第三步. 引入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" } }
引入Java SDK jar包
testImplementation group: 'junit', name: 'junit', version: '4.12' implementation ('org.fisco-bcos.java-sdk:fisco-bcos-java-sdk:2.9.1')
第四步. 配置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配置文件为例,本例中的配置项与该配置项相对应。
<?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
目录下。
5. 业务逻辑开发
第一步.将3编译好的Java合约引入项目中
cp /root/fisco/console/contracts/sdk/java/org/fisco/bcos/asset/contract/Asset.java /home/djh/workSpace/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); } }
- 初始化
初始化代码的主要功能为构造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 // 项目运行脚本
6. 运行应用
- 编译
# 切换到项目目录 $ cd ~/home/djh/workSpace/Java/asset-app # 编译项目 $ ./gradlew build
编译成功之后,将在项目根目录下生成dist
目录。dist目录下有一个asset_run.sh
脚本,简化项目运行。现在开始一一验证本文开始定下的需求。
- 部署
Asset.sol
合约
# 进入dist目录,运行前需要启动四个节点。 $ cd dist $ bash asset_run.sh deploy
- 注册资产
- 查询资产
- 资产转移
综上,通过合约开发,合约编译,SDK配置与业务开发构建了一个基于FISCO BCOS联盟区块链的应用。
项目目录
本文是作者学习FISCO BCOS区块链随手记录笔记,转载请标明!有问题可以留言一起探讨~