Hyperledger Fabric v2.5动态供应链多阶段数据格式管理咨询
解决方案:基于分层状态键+阶段索引的无冗余全链路查询方案
针对你在Hyperledger Fabric v2.5农产品供应链链码开发中遇到的「多阶段数据区分难、冗余膨胀」问题,推荐采用分层状态键设计+批次阶段索引的方案,既保证各阶段数据独立存储无冗余,又能通过batchId快速查询全链路流程数据。
核心思路
- 给每个阶段的数据设计独立的状态键,通过键的命名规则关联同一个
batchId,避免数据复制冗余 - 维护一个批次专属的「阶段索引」,记录该批次经历的所有阶段及对应数据键的后缀,保证查询时的流程顺序
1. 状态键命名规则
按阶段类型+batchId+阶段标识设计键,示例如下:
- 农场阶段:
batch:<batchId>:farm - 物流阶段(区分多次物流,用序号标记):
batch:<batchId>:logistics:<sequence>(sequence为1/2/3...对应不同物流节点) - 集散中心:
batch:<batchId>:distribution - 冷库:
batch:<batchId>:coldstorage - 分销商:
batch:<batchId>:distributor - 零售商:
batch:<batchId>:retailer - 阶段索引:
batch:<batchId>:stage-index(存储该批次的阶段顺序列表)
2. Golang链码实现示例
定义阶段数据结构体
package main import ( "encoding/json" "fmt" "strings" "github.com/hyperledger/fabric-contract-api-go/contractapi" ) // SmartContract 主链码结构体 type SmartContract struct { contractapi.Contract } // FarmData 农场阶段数据结构 type FarmData struct { HarvestDate string `json:"harvestDate"` FarmerName string `json:"farmerName"` BatchId string `json:"batchId"` } // LogisticsData 物流阶段数据结构 type LogisticsData struct { VehicleNumber string `json:"vehicleNumber"` DriverName string `json:"driverName"` BatchId string `json:"batchId"` Sequence int `json:"sequence"` } // DistributionData 集散中心阶段数据结构 type DistributionData struct { CenterName string `json:"centerName"` ArrivalDt string `json:"arrivalDt"` BatchId string `json:"batchId"` } // StageIndex 批次阶段索引,记录流程顺序 type StageIndex struct { BatchId string `json:"batchId"` Stages []string `json:"stages"` // 存储阶段键后缀,如"farm", "logistics:1" }
数据写入与索引更新
// CreateFarmData 写入农场阶段数据并更新索引 func (s *SmartContract) CreateFarmData(ctx contractapi.TransactionContextInterface, batchId, harvestDate, farmerName string) error { farmData := FarmData{ HarvestDate: harvestDate, FarmerName: farmerName, BatchId: batchId, } dataBytes, err := json.Marshal(farmData) if err != nil { return err } // 写入农场数据 err = ctx.GetStub().PutState(fmt.Sprintf("batch:%s:farm", batchId), dataBytes) if err != nil { return err } // 更新阶段索引 return updateStageIndex(ctx, batchId, "farm") } // CreateLogisticsData 写入物流阶段数据并更新索引 func (s *SmartContract) CreateLogisticsData(ctx contractapi.TransactionContextInterface, batchId, vehicleNumber, driverName string, sequence int) error { logisticsData := LogisticsData{ VehicleNumber: vehicleNumber, DriverName: driverName, BatchId: batchId, Sequence: sequence, } dataBytes, err := json.Marshal(logisticsData) if err != nil { return err } stageSuffix := fmt.Sprintf("logistics:%d", sequence) // 写入物流数据 err = ctx.GetStub().PutState(fmt.Sprintf("batch:%s:%s", batchId, stageSuffix), dataBytes) if err != nil { return err } // 更新阶段索引 return updateStageIndex(ctx, batchId, stageSuffix) } // updateStageIndex 通用的阶段索引更新函数 func updateStageIndex(ctx contractapi.TransactionContextInterface, batchId, stageSuffix string) error { indexKey := fmt.Sprintf("batch:%s:stage-index", batchId) indexBytes, err := ctx.GetStub().GetState(indexKey) if err != nil { return err } var stageIndex StageIndex if indexBytes == nil { // 首次创建索引 stageIndex = StageIndex{ BatchId: batchId, Stages: []string{stageSuffix}, } } else { err = json.Unmarshal(indexBytes, &stageIndex) if err != nil { return err } // 追加新阶段 stageIndex.Stages = append(stageIndex.Stages, stageSuffix) } indexBytes, err = json.Marshal(stageIndex) if err != nil { return err } return ctx.GetStub().PutState(indexKey, indexBytes) }
全链路查询实现
// GetFullSupplyChain 通过batchId查询全链路数据 func (s *SmartContract) GetFullSupplyChain(ctx contractapi.TransactionContextInterface, batchId string) (map[string]interface{}, error) { // 先获取阶段索引 indexKey := fmt.Sprintf("batch:%s:stage-index", batchId) indexBytes, err := ctx.GetStub().GetState(indexKey) if err != nil { return nil, err } if indexBytes == nil { return nil, fmt.Errorf("batch %s does not exist", batchId) } var stageIndex StageIndex err = json.Unmarshal(indexBytes, &stageIndex) if err != nil { return nil, err } // 遍历索引获取各阶段数据 fullChain := make(map[string]interface{}) for _, stageSuffix := range stageIndex.Stages { dataKey := fmt.Sprintf("batch:%s:%s", batchId, stageSuffix) dataBytes, err := ctx.GetStub().GetState(dataKey) if err != nil { return nil, err } if dataBytes == nil { continue } // 根据阶段类型解析对应结构体 var data interface{} switch stageSuffix { case "farm": var farm FarmData json.Unmarshal(dataBytes, &farm) data = farm case "distribution": var dist DistributionData json.Unmarshal(dataBytes, &dist) data = dist case "coldstorage": // 自行实现ColdStorageData结构体解析 var cold interface{} json.Unmarshal(dataBytes, &cold) data = cold case "distributor": var distr interface{} json.Unmarshal(dataBytes, &distr) data = distr case "retailer": var retail interface{} json.Unmarshal(dataBytes, &retail) data = retail default: if strings.HasPrefix(stageSuffix, "logistics:") { var logistics LogisticsData json.Unmarshal(dataBytes, &logistics) data = logistics } } fullChain[stageSuffix] = data } return fullChain, nil }
3. 前端展示逻辑
调用GetFullSupplyChain接口后,按stageIndex.Stages的顺序(该顺序严格匹配供应链流程)依次渲染各阶段数据即可,比如:
- 先渲染
farm阶段数据 - 再渲染
logistics:1、distribution、logistics:2等后续阶段
完全符合「农场→物流→集散中心→...→消费者」的流程顺序。
4. 额外优化建议
- 给每个阶段数据添加
timestamp字段,在索引中同步记录,可用于前端校验流程时序 - 若需批量查询多个批次的阶段数据,可使用Fabric的范围查询
GetStateByRange("batch:", "batch:~"),结合键前缀快速筛选目标批次
内容的提问来源于stack exchange,提问作者Rishabh Raghwendra
相关产品推荐
相关产品推荐

