开发第一个区块链应用

avatar
作者
筋斗云
阅读量:0

FISCO BCOS开发第一个区块链应用

在这里插入图片描述

1.应用的需求

区块链天然具有防篡改,可追溯等特性,这些特性决定其更容易受金融领域的青睐。本示例中,将会提供一个简易的资产管理的开发示例,并最终实现以下功能:

  • 能够在区块链上进行资产注册
  • 能够实现不同账户的转账
  • 可以查询账户的资产金额

2.设计与开发智能合约

在区块链上进行应用开发时,结合业务需求,首先需要设计对应的智能合约,确定合约需要储存的数据,在此基础上确定智能合约对外提供的接口,最后给出各个接口的具体实现。

第一步. 设计智能合约

存储设计

FISCO BCOS提供合约CRUD接口开发模式,可以通过合约创建表,并对创建的表进行增删改查操作。针对本应用需要设计一个存储资产管理的表t_asset,该表字段如下:

  • account: 主键,资产账户(string类型)
  • asset_value: 资产金额(uint256类型)

其中account是主键,即操作t_asset表时需要传入的字段,区块链根据该主键字段查询表中匹配的记录。t_asset表示例如下:

accountasset_value
Alice10000
Bob20000

接口设计

按照业务的设计目标,需要实现资产注册,转账,查询功能,对应功能的接口如下:

// 查询资产金额 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.solTable.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.javaTable.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.xmlnetwork属性下的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区块链随手记录笔记,转载请标明!有问题可以留言一起探讨~

    广告一刻

    为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!