业务系统根据具体场景使用不同的交易类型写数据上链,具体参考存证交易模型。
数据格式规范
为维护数据的可读性,区块链预先配置了不同业务场景下对应的数据格式规范,以约定统一的存证数据格式。上链数据必须满足规范要求,您可以在 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 Transaction
TransactionDO tx = TransactionBuilder.getContentOnlyNotaryPayloadBuilder()
.setContent(bizData)//设置需要存证的数据
.setTimestamp(System.currentTimeMillis())//设置业务时间
.setCategory(BizCategory.GuestInfo)//通过BizCategory设置业务分类
.build();//build Transaction
// 发送Transaction
Response<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配置初始化client
Client client = new Client(config);
// 构造存证数据
GuestInfoBuilder builder = new GuestInfoBuilder();
byte[] bizData = builder.buildUserName("Bob")
.buildBirthday("2000-01-01")
.buildEmail("bob@inc.com")
.build();
// 创建ContentOnlyNotary Transaction
TransactionDO tx = TransactionBuilder.getContentOnlyNotaryPayloadBuilder()
.setContent(bizData)//设置需要存证的数据
.setTimestamp(System.currentTimeMillis())//设置业务时间
.setCategory(BizCategory.GuestInfo)//设置业务分类
.build();//build Transaction
// 发送Transaction
Response<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 配置初始化client
Client client = new Client(config);
// 初始化链上加密 key repositry
KeyRepositry keyRepositry = new KeyRepositry();
// 设置虚拟 root key
KeyPath key1 = new KeyPath("/ABS_ROOT");
// 为BOC Bank类业务生成root加密key
keyRepositry.setKey(key1.forName("ALIPAY").forName("BOC"), genKey());
// 存证数据加密 key path
String keyPathName = "/ABS_ROOT/ALIPAY/BOC/BILL";
// 根据 key path 生成加密 key
byte[] key = keyRepositry.getKeyFor(keyPathName);
// 构造存证数据
GuestInfoBuilder builder = new GuestInfoBuilder();
byte[] bizData = builder.buildUserName("Bob")
.buildBirthday("2000-01-01")
.buildEmail("bob@inc.com")
.build();
// build transaction
TransactionDO tx = TransactionBuilder.getEncryptShareNotaryPayloadBuilder()
.encryptContent(bizData, key, keyPathName.getBytes())//设置存证内容,加密key,key path
.setTimestamp(System.currentTimeMillis())//设置业务时间
.setCategory(BizCategory.GuestInfo)//设置业务分类
.build();
// 发送Transaction
Response<TransactionDO> response = client.sendTransaction(tx);
if(response.isSuccess()){
// 发送成功,业务系统保留 Transaction Hash 与业务数据关联
return tx.getTxHashValue();
}else{
// 发送失败,抛异常。业务系统应该重试,RPC 调用失败时区块链的状态是未知的。
throw new RuntimeException("写入区块链失败");
}
存证数据读取
// 获取当前最新区块header
final 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) {
// 获取payload
if (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();
文档内容是否对您有帮助?