You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Hyperledger Fabric v2.5动态供应链多阶段数据格式管理咨询

解决方案:基于分层状态键+阶段索引的无冗余全链路查询方案

针对你在Hyperledger Fabric v2.5农产品供应链链码开发中遇到的「多阶段数据区分难、冗余膨胀」问题,推荐采用分层状态键设计+批次阶段索引的方案,既保证各阶段数据独立存储无冗余,又能通过batchId快速查询全链路流程数据。

核心思路

  1. 给每个阶段的数据设计独立的状态键,通过键的命名规则关联同一个batchId,避免数据复制冗余
  2. 维护一个批次专属的「阶段索引」,记录该批次经历的所有阶段及对应数据键的后缀,保证查询时的流程顺序

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的顺序(该顺序严格匹配供应链流程)依次渲染各阶段数据即可,比如:

  1. 先渲染farm阶段数据
  2. 再渲染logistics:1、distribution、logistics:2等后续阶段
    完全符合「农场→物流→集散中心→...→消费者」的流程顺序。

4. 额外优化建议

  • 给每个阶段数据添加timestamp字段,在索引中同步记录,可用于前端校验流程时序
  • 若需批量查询多个批次的阶段数据,可使用Fabric的范围查询GetStateByRange("batch:", "batch:~"),结合键前缀快速筛选目标批次

内容的提问来源于stack exchange,提问作者Rishabh Raghwendra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 07:36:05