存证溯源案例2.0概览
本文分享如何使用 Go
语言和Fabric 2.0链码
开发fabric智能合约实战案例-存证溯源案例,本篇主要介绍K-V数据库的ID一致性、范围查询的使用方法、K-V数据库范围查询的弊端以及解决方案。如下图所示:
1、K-V数据库的ID一致性
Hyperledger fabric默认采用LevelDB 作为peer 节点的状态数据库。因此,我们在链码中进行数据存储时,基于K-V数据库来进行设计。
在本存证溯源案例中,我们采用LevelDB数据库来作为存储溯源记录的组件,但作为键值数据库,存储的Key多为字符串,因此就产生了如何实现ID主键自增的问题。
我们设计了一个全局自增变量IdNum
来作为signal,标记全局ID进行存储。
type AssetGlobal struct {
IdNum int `json:"idNum"`
}
对初始化方法进行相应调整
func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
key := "assetGlobal"
exist, err := ctx.GetStub().GetState(key)
if err != nil {
return fmt.Errorf("can not find assetGlobal record, error :%v", err)
}
if exist != nil {
return fmt.Errorf("assetGlobal exist, can not initLedger again.")
}
assetGlobalInstance := &AssetGlobal{
IdNum: 0,
}
assetGlobalInstanceBytes, err := json.Marshal(assetGlobalInstance)
if err != nil {
return fmt.Errorf("json assetGlobalInstance error :%v", err)
}
err = ctx.GetStub().PutState(assetGlobalName, assetGlobalInstanceBytes)
if err != nil {
return fmt.Errorf("PutState assetGlobalInstance error: %v", err)
}
return nil
}
相应的增删改方法也做出调整
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, color string, size int, owner string, appraisedValue int) error {
assetGlobalInstance, err := s.ReadAssetGlobal(ctx)
if err != nil {
return fmt.Errorf("get assetGlobalInstance error: %s", err)
}
var nextAssetID = assetGlobalInstance.IdNum + 1
assetGlobalNewInstance := &AssetGlobal{
IdNum: nextAssetID,
}
// id补零
nextAssetIDStr := transIDToStr(nextAssetID)
exists, err := s.AssetExists(ctx, nextAssetIDStr)
if err != nil {
return fmt.Errorf("failed to get asset: %v", err)
}
if exists {
return fmt.Errorf("asset already exists: %s", nextAssetIDStr)
}
asset := &Asset{
DocType: "asset",
ID: nextAssetIDStr,
Color: color,
Size: size,
Owner: owner,
AppraisedValue: appraisedValue,
}
assetBytes, err := json.Marshal(asset)
if err != nil {
return err
}
err = ctx.GetStub().PutState(nextAssetIDStr, assetBytes)
if err != nil {
return err
}
return nil
}
func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, assetID int, color string, size int, owner string, appraisedValue int) error {
exists, err := s.AssetExists(ctx, transIDToStr(assetID))
if err != nil {
return fmt.Errorf("failed to get asset: %v", err)
}
if !exists {
return fmt.Errorf("asset not exists: %d", assetID)
}
asset, err := s.ReadAsset(ctx, assetID)
if err != nil {
return err
asset.Color = color
asset.Size = size
asset.Owner = owner
asset.AppraisedValue = appraisedValue
assetJSON, err := json.Marshal(asset)
if err != nil {
return fmt.Errorf("asset json marshal error:%s", err)
}
return ctx.GetStub().PutState(transIDToStr(assetID), assetJSON)
}
func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, assetID int) error {
asset, err := s.ReadAsset(ctx, assetID)
if err != nil {
return fmt.Errorf("failed to get asset: %v", err)
}
if asset != nil {
return fmt.Errorf("asset not exists: %d", assetID)
}
err = ctx.GetStub().DelState(transIDToStr(assetID))
if err != nil {
return fmt.Errorf("failed to delete asset %d: %v", assetID, err)
}
}
2、范围查询的使用方法
增加了范围查询
func (s *SmartContract) GetAssetsByRangeLatest(ctx contractapi.TransactionContextInterface, pageSize, pageIndex int) ([]*Asset, error) {
assetGlobalInstance, err := s.ReadAssetGlobal(ctx)
if err != nil {
return nil, fmt.Errorf("get assetGlobalInstance error: %s", err)
}
latestKey := assetGlobalInstance.IdNum
var endKey int = latestKey - (pageIndex-1)*pageSize + 1
var startKey int = endKey - pageSize
startKeyStr := transIDToStr(startKey)
endKeyStr := transIDToStr(endKey)
resultsIterator, _, err := ctx.GetStub().GetStateByRangeWithPagination(startKeyStr, endKeyStr, int32(pageSize), "")
if err != nil {
return nil, err
}
defer resultsIterator.Close()
return constructQueryResponseFromIterator(resultsIterator)
}
3、K-V数据库范围查询的弊端与解决方案
在实际调用主键ID作为Key来范围查询时,我们发现当,id作为自增的key
逐渐来存入数据库时,范围查询排序以字符串的排序逻辑进行排序,即:1,10,11,2等。
这与我们设想的ID自增排序不同,因此我们需对原有ID进行补零操作
func transIDToStr(assetID int) string {
assetIDStr := strconv.Itoa(assetID)
if len(assetIDStr) < idStringLong {
size := idStringLong - len(assetIDStr)
for i := 0; i < size; i++ {
assetIDStr = "0" + assetIDStr
}
}
return assetIDStr
}