Hyperledger Fabric链码修改与测试(二)

上一篇对链码修改之后还存在数据结构设计不够合理的问题,在设想的情景下,一个树莓派通过RS485与一组仪表通讯,包括一个水表、一个电表和一个气表,但是实际情景下可能有的树莓派只连接了一个仪表,比如在只与一个水表通讯的情况下,树莓派只能获得水表中的瞬时流量与累计流量两个数据,那么原数据结构Data中的其余数据则全都为空,查询出来的结果也会显示出这些空数据,而实际上这是一件没有意义的事情。原Data结构如下:

type Data struct {
	Flow_now	string `json:"flow_now(L/H)"`
	Flow_total   	string `json:"flow_total(L)"`
	Voltage	string `json:"voltage(V)"`
	Current	string `json:"current(A)"`
	Power		string `json:"power(W)"`
	Energy		string `json:"energy(kWh)"`
	Factor		string `json:"factor"`
	Emissions	string `json:"emissions(Kg)"`
	Time		string `json:"time"`
}

于是我对链码进行修改,将原本的Data分开,分为水表数据的结构体与电表数据的结构体,然后再添加一个气表数据的结构体方便下次实验使用,修改之后三个结构体如下:

type Water struct {
	Waterflow_now	string `json:"waterflow_now(L/H)"`
	Waterflow_total string `json:"waterflow_total(L)"`
	Time		string `json:"time"`
}
type Electricity struct {
	Voltage	string `json:"voltage(V)"`
	Current	string `json:"current(A)"`
	Power		string `json:"power(W)"`
	Energy		string `json:"energy(kWh)"`
	Factor		string `json:"factor"`
	Emissions	string `json:"emissions(Kg)"`
	Time		string `json:"time"`
}
type Gas struct {
	Gasflow_now	string `json:"gasflow_now(LSPM)"`
	Gasflow_total	string `json:"gasflow_total(NCM)"`
	Time		string `json:"time"`
}

每个结构体只包含相对应的仪表能查询到的数据,Time是每个结构体都包含的成员,因为我是将日期作为每条数据的Key值以便于查询,结合Key的日期和这一条数据的Time值就可以知道获取这一条数据具体是在什么时候。但是后续调试过程中发现这样写还是有问题的,后面会讲到。

QueryResult结构:

type WaterQueryResult struct {
	Key    string `json:"Key"`
	Record *Water
}
type ElectricityQueryResult struct {
	Key	string `json:"Key"`
	Record *Electricity
}
type GasQueryResult struct {
	Key    string `json:"Key"`
	Record *Gas
}

QueryResult储存链码从World State查询到的结果。

初始化:

func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
	waters := []Water{
		Water{Waterflow_now: "0", Waterflow_total: "0", Time: "00:00"},
	}
	electricitys := []Electricity{
		Electricity{Voltage: "0", Current: "0", Power: "0", Energy: "0", Factor: "0", Emissions: "0", Time: "00:00"},
	}
	gass := []Gas{
		Gas{Gasflow_now: "0", Gasflow_total: "0", Time: "00:00"},
	}

	for _,water := range waters {
		dataAsBytes, _ := json.Marshal(water)
		err := ctx.GetStub().PutState("2022-07-20 001 000", dataAsBytes)

		if err != nil {
			return fmt.Errorf("Failed to put to world state. %s", err.Error())
		}
	}
	for _,electricity := range electricitys {
		dataAsBytes, _ := json.Marshal(electricity)
		err := ctx.GetStub().PutState("2022-07-20 002 000", dataAsBytes)

		if err != nil {
			return fmt.Errorf("Failed to put to world state. %s", err.Error())
		}
	}
	for _,gas := range gass {
		dataAsBytes, _ := json.Marshal(gas)
		err := ctx.GetStub().PutState("2022-07-20 003 000", dataAsBytes)

		if err != nil {
			return fmt.Errorf("Failed to put to world state. %s", err.Error())
		}
	}

	return nil
}

链码初始化是安装链码后必须进行的步骤,初始化时我向State中添加了三条ID为“XXX 000”的数据,且值全都为0,其实这个值没有实际意义就只为了初始化而已。ID中前三位的“XXX”是仪表的编号,比如这个树莓派上水表编号是“001”,电表“002”,气表“003”。实际情景下每个仪表的编号是在与它通讯的树莓派中完成的且不同的水表编号也不同,可能树莓派1中的水表编号是“001”,树莓派2的水表编号是“004”,所以初始化添加的这几个仪表ID并不是代表所有该类型的表都是这个ID,我只是完成一下这个初始化的过程而已~

添加数据(AddData):

func (s *SmartContract) AddData_water(ctx contractapi.TransactionContextInterface, dataNumber string, waterflow_now string, waterflow_total string, time string) error {
	water := Water{
		Waterflow_now:		waterflow_now,
		Waterflow_total:   	waterflow_total,
		Time:			time,
	}

	dataAsBytes, _ := json.Marshal(water)

	return ctx.GetStub().PutState(dataNumber, dataAsBytes)
}
func (s *SmartContract) AddData_electricity(ctx contractapi.TransactionContextInterface, dataNumber string, voltage string, current string, power string, energy string, factor string, emissions string, time string) error {
	electricity := Electricity{
		Voltage:	voltage,
		Current:	current,
		Power:		power,
		Energy:	energy,
		Factor:	factor,
		Emissions:	emissions,
		Time:		time,
	}

	dataAsBytes, _ := json.Marshal(electricity)

	return ctx.GetStub().PutState(dataNumber, dataAsBytes)
}
func (s *SmartContract) AddData_gas(ctx contractapi.TransactionContextInterface, dataNumber string, gasflow_now string, gasflow_total string, time string) error {
	gas := Gas{
		Gasflow_now:		gasflow_now,
		Gasflow_total:   	gasflow_total,
		Time:			time,
	}

	dataAsBytes, _ := json.Marshal(gas)

	return ctx.GetStub().PutState(dataNumber, dataAsBytes)
}

结构体分开了那么添加数据的方法肯定也要分开,在添加数据时我需要弄清楚数据在World State中存储是以什么形式,以便于查询时提取出数据,在学习完go语言后知道了json.Marshal这个函数将结构体数据转化成json形式,也就是World State储存的都是json格式的数据,这个json格式在这里相当于一种格式化的字符串,可以与结构体互相传值。所以不管是我之前定义的Water类型还是Gas类型等等在State存储时全都是json类型。那就会有个问题,在查询数据时我怎么知道这个json类型转换之前是Water类型还是Gas类型呢?当然查询单个数据时不需要考虑这个问题:

func (s *SmartContract) QueryData_water(ctx contractapi.TransactionContextInterface, dataNumber string) (*Water, error) {
	dataAsBytes, err := ctx.GetStub().GetState(dataNumber)

	if err != nil {
		return nil, fmt.Errorf("Failed to read from world state. %s", err.Error())
	}

	if dataAsBytes == nil {
		return nil, fmt.Errorf("%s does not exist", dataNumber)
	}

	water := new(Water)
	_ = json.Unmarshal(dataAsBytes, water)

	return water, nil
}
func (s *SmartContract) QueryData_electricity(ctx contractapi.TransactionContextInterface, dataNumber string) (*Electricity, error) {
	dataAsBytes, err := ctx.GetStub().GetState(dataNumber)

	if err != nil {
		return nil, fmt.Errorf("Failed to read from world state. %s", err.Error())
	}

	if dataAsBytes == nil {
		return nil, fmt.Errorf("%s does not exist", dataNumber)
	}

	electricity := new(Electricity)
	_ = json.Unmarshal(dataAsBytes, electricity)

	return electricity, nil
}
func (s *SmartContract) QueryData_gas(ctx contractapi.TransactionContextInterface, dataNumber string) (*Gas, error) {
	dataAsBytes, err := ctx.GetStub().GetState(dataNumber)

	if err != nil {
		return nil, fmt.Errorf("Failed to read from world state. %s", err.Error())
	}

	if dataAsBytes == nil {
		return nil, fmt.Errorf("%s does not exist", dataNumber)
	}

	gas := new(Gas)
	_ = json.Unmarshal(dataAsBytes, gas)

	return gas, nil
}

因为查询单个数据依据的是这个数据的Key值,Key值在设计时确保了每条数据都带有唯一的Key,而Key本身就包含了标识仪表类型的ID,所以只要查询时输入的Key值无误,那查询到的结果也不会出错。

查询所有数据时出现了问题,由于不同表数据的结构不同,所以原本的QueryAllDatas要分成三个方法:

func (s *SmartContract) QueryAllDatas_water(ctx contractapi.TransactionContextInterface) ([]WaterQueryResult, error) {
	startKey := ""
	endKey := ""

	resultsIterator, err := ctx.GetStub().GetStateByRange(startKey, endKey)

	if err != nil {
		return nil, err
	}
	defer resultsIterator.Close()

	results := []WaterQueryResult{}

	for resultsIterator.HasNext() {	
		queryResponse, err := resultsIterator.Next()

		if err != nil {
			return nil, err
		}

		water := new(Water)
		_ = json.Unmarshal(queryResponse.Value, water)
		if *water != (Water{}){
		queryResult := WaterQueryResult{Key: queryResponse.Key, Record: water}
		results = append(results, queryResult)
		}
	}

	return results, nil
}
func (s *SmartContract) QueryAllDatas_electricity(ctx contractapi.TransactionContextInterface) ([]ElectricityQueryResult, error) {
	startKey := ""
	endKey := ""

	resultsIterator, err := ctx.GetStub().GetStateByRange(startKey, endKey)

	if err != nil {
		return nil, err
	}
	defer resultsIterator.Close()

	results := []ElectricityQueryResult{}

	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()

		if err != nil {
			return nil, err
		}

		electricity := new(Electricity)
		_ = json.Unmarshal(queryResponse.Value, electricity)
		if *electricity != (Electricity{}){
		queryResult := ElectricityQueryResult{Key: queryResponse.Key, Record: electricity}
		results = append(results, queryResult)
		}
	}

	return results, nil
}
func (s *SmartContract) QueryAllDatas_gas(ctx contractapi.TransactionContextInterface) ([]GasQueryResult, error) {
	startKey := ""
	endKey := ""

	resultsIterator, err := ctx.GetStub().GetStateByRange(startKey, endKey)

	if err != nil {
		return nil, err
	}
	defer resultsIterator.Close()

	results := []GasQueryResult{}

	for resultsIterator.HasNext() {	
		queryResponse, err := resultsIterator.Next()

		if err != nil {
			return nil, err
		}

		gas := new(Gas)
		_ = json.Unmarshal(queryResponse.Value, gas)
		if *gas != (Gas{}){
		queryResult := GasQueryResult{Key: queryResponse.Key, Record: gas}
		results = append(results, queryResult)
		}
	}

	return results, nil
}

初始化时我添加了三个不同类型的数据,在调用这三个查询时发现返回的都是三条数据,并且有两条废数据,比如我调用QueryAllDatas_water,返回的三条数据有一条正确的水表数据,但另外两条数据除了Time是初始化时的值“00:00”,其余数据都为空,ID分别为“002 000”和“003 000”,也就是说电表和气表的数据在这时也被错误地输出了,这显然是不合理的,一开始没注意Time是有值的,所以简单地加了一个结构体判空的条件,如果不为空就进行后面的append操作,结果运行一遍结果还是这样,后来才看到每条数据Time值都不为空的,所以得换个方法。

从以上这个错误我可以判断出json.Marshal这个函数在json形式的数据与结构体各个成员变量不对应时不会报错且仍然会执行,比如ID为“002 000”的数据的voltage和current等等都为空,而Time是初始化好的“00:00”,所以json.Marshal在两个形参的成员不完全对应时会把对应的部分分别传值,不对应的部分不作操作,因此为空。解决这个问题有两个办法,一个是判断一下json.Marshal传值之后这个新的结构体变量是不是除了Time之外全都为空值,如果是则不进行之后的append操作;这里我用另一个办法,修改了每个数据结构中Time的json解析值:

type Water struct {
	Waterflow_now	string `json:"waterflow_now(L/H)"`
	Waterflow_total string `json:"waterflow_total(L)"`
	Time		string `json:"water_time"`
}
type Electricity struct {
	Voltage	string `json:"voltage(V)"`
	Current	string `json:"current(A)"`
	Power		string `json:"power(W)"`
	Energy		string `json:"energy(kWh)"`
	Factor		string `json:"factor"`
	Emissions	string `json:"emissions(Kg)"`
	Time		string `json:"electricity_time"`
}
type Gas struct {
	Gasflow_now	string `json:"gasflow_now(LSPM)"`
	Gasflow_total	string `json:"gasflow_total(NCM)"`
	Time		string `json:"gas_time"`
}

因为这时我想了想json.Marshal传值应该是依靠json与结构体成员变量的映射来完成的,原本三个结构体中成员变量Time到json的time映射是相同的,所以只有Time被改了值,所以我把每个json的time改成了不同的写法,解决了这个问题。

还要注意一点,虽然没做实验但我觉得只有修改json的time才是有效的,因为如果只修改结构体中的Time,即使改成三个不同的写法,最终在State里储存的属性都叫time,所以json看来每个数据都含有time这个共同属性,最后json.Marshal传值时还是会分别给每个结构体的Time赋值,结果还是原地tp~

添加及查询操作结果:

总结下,改这个链码过程还是比较麻烦的,倒不是代码难改,主要是运行调试太麻烦,每次出了错都要重新开channel、打包链码、安装链码、节点认证一堆操作,输个命令又那么长,还好改的地方不多。改完之后对代码合理性更加注重了,对Go语言也更加熟悉了一些。

猜你喜欢

转载自blog.csdn.net/qq_43824745/article/details/126383642