全部產品
Search
文件中心

:資料存證寫入

更新時間:Jul 06, 2024

資料分類

為維護資料的可讀性,區塊鏈預先配置了不同業務情境下對應的資料分類,作為所有使用者的存證資料格式約定。上鏈資料必須滿足規範要求。

前置條件

在閱讀此 開發樣本 前,請與您所在的區塊鏈的 管理員 溝通確定已配置的資料分類,如果您是管理員,請參考 配置資料分類 一節完成資料分類的配置;管理員完成資料分類配置後,就可以下載並解壓SDK包,完成本文各樣本講解的功能開發。

SDK包下載方式為,登入BaaS平台,在左側導覽列選擇 區塊鏈列表,在列表中找到您希望訪問的區塊鏈,滑鼠選中右側 更多,並點擊 下載簽署憑證下載SDK

您可以在client-sdk.zip包內的 schema.txt 檔案中查看區塊鏈的資料分類。一條區塊鏈可以包含多個業務資料分類(Category),鏈上會為每個分類分配一個Category ID,分類配置完成後,使用者可以通過識別資料的Category ID在鏈上檢索和過濾存證資料。

開發樣本

這裡通過一個簡單的schema.txt檔案樣本來示範如何構造、讀取存證資料,請對照您自己的檔案內容,將GuestInfo類型替換為您實際的資料分類。檔案聲明了一個名為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. 簡單的存證資料寫入

// 載入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("寫入區塊鏈失敗");
}

例2. 存證資料讀取

// 擷取當前最新區塊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();

更多

螞蟻區塊鏈使用Transaction(存證交易模型)進行資料存證,以上樣本使用了ContentOnlyNotaryPayload和EncryptShareNotaryPayload兩種存證模型,業務系統還可以根據具體情境,使用其它類型的Transaction寫資料上鏈,詳情請參考 存證交易模型