业务系统根据具体场景使用不同的交易类型写数据上链,具体参考存证交易模型。
数据格式规范
为维护数据的可读性,区块链预先配置了不同业务场景下对应的数据格式规范,以约定统一的存证数据格式。上链数据必须满足规范要求,您可以在 client-sdk.zip 包内的schema.txt 中查看区块链的数据格式规范。数据格式规范由多个业务数据分类(Category)组成。您可以通过识别数据的 Category 在链上检索和过滤存证数据。
这里通过一个简单的 schema.txt 文件示例来演示如何构造、读取存证数据。具体操作如下:
在
schema.txt文件中声明一个名为 GuestInfo 的 Category,包含三个成员,分别是姓名、生日和邮件地址,均为字符串类型,并在注释中说明成员数据须满足的约束条件。注意:在实际开发过程中,请将 GuestInfo 替换为您本地
schema.txt中实际定义的 Category。// schema.txt定义:商家顾客信息GuestInfo {String userName; // 姓名,要求非空,长度在20以内String birthday; // 生日,要求非空String email; // 邮件地址,要求非空,符合电子邮件格式}
- 对照规范要求,在client代码中调用
GuestInfoBuilder构造一条存证数据。// client代码// 构造存证数据GuestInfoBuilder builder = new GuestInfoBuilder();byte[] bizData = builder.buildUserName("Bob").buildBirthday("2000-01-01").buildEmail("bob@inc.com").build();
- 存证数据构造完成后,构造上链的 payload。这里需要了解一个枚举类
BizCategory,SDK 根据schema.txt的约定,预先生成了这一枚举类,以标识不同的 Category。本例中的 BizCategory 如下所示:// SDK的BizCategory声明public class BizCategory {public static final int GuestInfo = 0x00010001;...}
- 把存证数据传入 payload,指定 Category 为
BizCategory.GuestInfo,构造一个数据存证交易,并通过 client 发送出去。至此,存证上链的过程已完成。// client代码// 创建ContentOnlyNotary TransactionTransactionDO tx = TransactionBuilder.getContentOnlyNotaryPayloadBuilder().setContent(bizData)//设置需要存证的数据.setTimestamp(System.currentTimeMillis())//设置业务时间.setCategory(BizCategory.GuestInfo)//通过BizCategory设置业务分类.build();//build Transaction// 发送TransactionResponse<TransactionDO> response = client.sendTransaction(tx);
- 读取数据。同样通过 BizCategory.GuestInfo 来识别数据对象类型,将存证数据转换回对象。
存证数据上链后,需要等待一段时间才能成块,出块后就可以查询数据了。获取块数据有两种模式:
- 模式一:监听成块消息,处理新数据块,参见下文的示例 1:存证数据写入。
- 模式二:通过定时任务,每隔一段时间拉取新的数据块,参见下文的示例 2:存证数据加密上链。
下面展示几个完整的示例。
代码示例
存证数据写入
// 加载client配置文件Properties p = new Properties();p.load(new FileInputStream("sdk.properties"));ClientConfig config = new ClientPropertyConfig(p);// 使用指定client配置初始化clientClient client = new Client(config);// 构造存证数据GuestInfoBuilder builder = new GuestInfoBuilder();byte[] bizData = builder.buildUserName("Bob").buildBirthday("2000-01-01").buildEmail("bob@inc.com").build();// 创建ContentOnlyNotary TransactionTransactionDO tx = TransactionBuilder.getContentOnlyNotaryPayloadBuilder().setContent(bizData)//设置需要存证的数据.setTimestamp(System.currentTimeMillis())//设置业务时间.setCategory(BizCategory.GuestInfo)//设置业务分类.build();//build Transaction// 发送TransactionResponse<TransactionDO> response = client.sendTransaction(tx);if(response.isSuccess()){// 发送成功,业务系统保留Transaction Hash与业务数据关联return tx.getTxHashValue();}else{// 发送失败,抛异常。业务系统应该重试,rpc调用失败时区块链的状态是未知的。throw new RuntimeException("写入区块链失败");}
存证数据加密上链
// 加载 client 配置文件Properties p = new Properties();p.load(new FileInputStream("client.properties"));ClientConfig config = new ClientPropertyConfig(p);// 使用指定 client 配置初始化clientClient client = new Client(config);// 初始化链上加密 key repositryKeyRepositry keyRepositry = new KeyRepositry();// 设置虚拟 root keyKeyPath key1 = new KeyPath("/ABS_ROOT");// 为BOC Bank类业务生成root加密keykeyRepositry.setKey(key1.forName("ALIPAY").forName("BOC"), genKey());// 存证数据加密 key pathString keyPathName = "/ABS_ROOT/ALIPAY/BOC/BILL";// 根据 key path 生成加密 keybyte[] key = keyRepositry.getKeyFor(keyPathName);// 构造存证数据GuestInfoBuilder builder = new GuestInfoBuilder();byte[] bizData = builder.buildUserName("Bob").buildBirthday("2000-01-01").buildEmail("bob@inc.com").build();// build transactionTransactionDO tx = TransactionBuilder.getEncryptShareNotaryPayloadBuilder().encryptContent(bizData, key, keyPathName.getBytes())//设置存证内容,加密key,key path.setTimestamp(System.currentTimeMillis())//设置业务时间.setCategory(BizCategory.GuestInfo)//设置业务分类.build();// 发送TransactionResponse<TransactionDO> response = client.sendTransaction(tx);if(response.isSuccess()){// 发送成功,业务系统保留 Transaction Hash 与业务数据关联return tx.getTxHashValue();}else{// 发送失败,抛异常。业务系统应该重试,RPC 调用失败时区块链的状态是未知的。throw new RuntimeException("写入区块链失败");}
存证数据读取
// 获取当前最新区块headerfinal Response<BlockHeader> blockHeader = client.getLatestBlockHeader();if(!blockHeader.isSuccess()) {LOGGER.error("Get block header fail: ", blockHeader.getErrorMsg());return;}// oldHeight = 当前本地区块高度;long beginHeight = oldHeight + 1;long finalHeight = blockHeader.getData().getHeight();// 发现新块则开始拉取while(beginHeight < finalHeight) {long endHeight = Math.min(beginHeight + FETCH_LIMIT - 1, finalHeight);Response<List<Block>> response = client.getBlocks((int)beginHeight, (int)endHeight);if (!response.isSuccess()) {LOGGER.error("Get response fail: ", response.getErrorMsg());return;}ConstructUtil constructUtil = new ConstructUtil();List<Block> blocks = response.getData();for (Block block : blocks) {// 获取区块的存证交易List<TransactionDO> transactions = block.getTransactions();for (TransactionDO transaction : transactions) {// 获取payloadif (ContentOnlyNotaryPayloadDO.class.isInstance(transaction.getPayload())) {ContentOnlyNotaryPayloadDO contentOnlyNotaryPayloadDO =ContentOnlyNotaryPayloadDO.class.cast(transaction.getPayload());// 判断payload的业务数据分类if (contentOnlyNotaryPayloadDO.getCategory() == BizCategory.GuestInfo) {// 构造数据对象GuestInfo renter = GuestInfo.class.cast(constructUtil.construct(contentOnlyNotaryPayloadDO.getContent(), GuestInfo.class));// 读取renter数据System.out.println(renter.getUserName());}}}beginHeight = block.getHeader().getHeight() + 1;}}
最后如果用户的系统要退出,建议在退出前调用shutdown,client会在shutdown中优雅的关闭线程池。
client.shutdown();
该文章对您有帮助吗?