Fabric链码
链码(即智能合约)是一段对账本进行操作的程序。⼀般是指由开发⼈员使⽤Go语⾔(也⽀持Java等语⾔)编写的应⽤程序代码,提供分布式账本的状态处理逻辑。链码被部署在Fabric的⽹络节点中,能够独⽴运⾏在具有安全特性的受保护的Docker 容器中,以 gRPC 协议与相应的 peer 节点进⾏通信,以操作(初始化 或管理)分布式账本中的数据。可以根据不同的需求开发出不同的复杂的应⽤。
在 Hyperledger Fabric 中,链码⼀般分为:
-
系统链码(略)
-
⽤户链码: 指由开发⼈员编写的应⽤代码(即接下来我们需要掌握的部分)
链码的生命周期
链码开发编写完成后,并不能⽴刻使⽤,⽽是必须经过⼀系列的操作之后才能应⽤在 Hyperledger Fabric ⽹络中进⽽处理客户端提交的交易。这⼀系列的操作是由链码的⽣命周期来负责管理。主要需要 熟悉的三个操作如下:
-
install:将已编写完成的链码安装在⽹络节点中;
-
instantiate:对已安装的链码进⾏实例化;
-
upgrade:对已有链码进⾏升级。链代码可以在安装后根据具体需求的变化进⾏升级
链码的使用
工作目录
先创建一个文件夹 chaincode
作为工作目录 ,在该文件夹下执行命令:
go mod init chaincode
链码文件
创建第一个链码 main.go
package main
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos/peer"
)
//创建自定义链码
type MyChaincode struct {}
//实现Chaincode接口
func (MyChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
return shim.Success(nil)
}
func (MyChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
return shim.Success(nil)
}
//程序入口
func main() {
err := shim.Start(new(MyChaincode))
if err != nil {
fmt.Println("链码启动失败",err)
}
}
添加依赖
若要使用fabric,需要在 go.mod
中添加依赖。
module chaincode
require (
github.com/Knetic/govaluate v3.0.0+incompatible // indirect
github.com/Shopify/sarama v1.27.0 // indirect
github.com/fsouza/go-dockerclient v1.6.5 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.2.1 // indirect
github.com/hashicorp/go-version v1.2.1 // indirect
github.com/hyperledger/fabric v1.4.2
github.com/hyperledger/fabric-amcl v0.0.0-20200424173818-327c9e2cf77a // indirect
github.com/miekg/pkcs11 v1.0.3 // indirect
github.com/mitchellh/mapstructure v1.3.3 // indirect
github.com/onsi/ginkgo v1.14.0 // indirect
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/spf13/viper v1.7.1 // indirect
github.com/stretchr/testify v1.6.1
github.com/sykesm/zap-logfmt v0.0.3 // indirect
go.uber.org/zap v1.15.0 // indirect
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de // indirect
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc // indirect
google.golang.org/grpc v1.31.0 // indirect
)
go 1.14
测试文件
创建一个测试文件用于测试链码是否正常运行 main_test.go
package main
import (
"encoding/json"
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/stretchr/testify/assert"
"testing"
)
func checkInit(t *testing.T, stub *shim.MockStub, args [][]byte) {
res := stub.MockInit("1", args)
assert.Equal(t, int32(shim.OK), res.Status, "Init failed:%v", string(res.Message))
}
func checkInvoke(t *testing.T, stub *shim.MockStub, args [][]byte) (data []byte) {
res := stub.MockInvoke("1", args)
assert.Equal(t, int32(shim.OK), res.Status, "Invoke failed:%v", string(res.Message))
return res.Payload
}
func Test(t *testing.T){
//需传入一个Chaincode
chaincode := new(MyChaincode)
stub := shim.NewMockStub("MyChaincode",chaincode)
checkInit(t,stub,[][]byte{[]byte("init")})
checkInvoke(t,stub,[][]byte{})
}
常用API
GetState
从账本中获取数据
func (ChaincodeStubInterface) GetState(key string) ([]byte, error)
key 账本中的Key;返回该Key对应的Value以及一个错误
若该Key对应的数据不存在,返回(nil, nil)
GetState returns the value of the specified
key
from the ledger. Note that GetState doesn’t read data from the writeset, which has not been committed to the ledger. In other words, GetState doesn’t consider data modified by PutState that has not been committed.If the key does not exist in the state database, (nil, nil) is returned.
PutState
向账本设置数据
func (ChaincodeStubInterface) PutState(key string, value []byte) error
key 该条数据的Key;value 数据值 (一般使用json序列化后存入)
PutState puts the specified
key
andvalue
into the transaction’s writeset as a data-write proposal. PutState doesn’t effect the ledger until the transaction is validated and successfully committed. Simple keys must not be an empty string and must not start with a null character (0x00) in order to avoid range query collisions with composite keys, which internally get prefixed with 0x00 as composite key namespace. In addition, if using CouchDB, keys can only contain valid UTF-8 strings and cannot begin with an underscore (“_”).
DelState
删除账本中的数据
func (ChaincodeStubInterface) DelState(key string) error
key 待删除数据的Key
DelState records the specified
key
to be deleted in the writeset of the transaction proposal. Thekey
and its value will be deleted from the ledger when the transaction is validated and successfully committed.
GetFunctionAndParameters
获取Invoke调用的方法及其参数。
func (ChaincodeStubInterface) GetFunctionAndParameters() (string, []string)
string 方法名 []string 参数数组
在获取到方法名后,就可以通过switch case 来判断调用的方法,并传入参数。
GetFunctionAndParameters returns the first argument as the function name and the rest of the arguments as parameters in a string array. Only use GetFunctionAndParameters if the client passes arguments intended to be used as strings.
CreateCompositeKey:
账本数据是以Key-Value形式进行存储的。一般使用主键ID(唯一)为Key,数据为Value进行存储。如用户数据,Key为ID,User为Value。这时若有需求需要使用姓名来查询用户,由于姓名可能会重复,账本数据就会被覆盖。因为一个Key只能存储条数据,相同的Key只会将之前的那条数据覆盖掉。复合键在存入账本后会使用字典序来排序。
func (ChaincodeStubInterface) CreateCompositeKey(objectType string, attributes []string){}
根据给定的 objectType 和 attributes 参数组合成⼀个组合键,⽣成的组合键可以⽤ 于PutState()作为key值写⼊账本。
CreateCompositeKey combines the given
attributes
to form a composite key. The objectType and attributes are expected to have only valid utf8 strings and should not contain U+0000 (nil byte) and U+10FFFF (biggest and unallocated code point). The resulting composite key can be used as the key in PutState().
使用复合键来存储账本数据时,有一个类似于MySQL组合索引最左匹配原则的机制。
stub.CreateCompositeKey(“user”,[]string{“name”,user.Name,strconv.Itoa(user.ID)})
第一个参数 user 类似于表名,后面的string数组则类似于索引。在使用组合键查询数据时,会在该表中使用string数组给出的索引顺序进行筛选查找。首先查找表名为user的数据,再查找前缀是 name 的数据,再查找与 user.Name 匹配的数据(可能有多条),最后再查找与 user.ID 匹配的数据(这时可能就只有一条了)
GetStateByPartialCompositeKey
使用复合键来存储数据后,就可以通过复合键来获取数据了。
func (ChaincodeStubInterface) GetStateByPartialCompositeKey(objectType string, keys []string) (StateQueryIteratorInterface, error)
objectType
创建复合键时所提到的“表名”,keys
也就是所谓的索引。 返回一个迭代器和一个异常,通过该迭代器获取
*queryresult.KV
,其每一项的就是通过给定objectType及keys查询到的数据项。 GetStateByPartialCompositeKey queries the state in the ledger based on a given partial composite key. This function returns an iterator which can be used to iterate over all composite keys whose prefix matches the given partial composite key. However, if the number of matching composite keys is greater than the totalQueryLimit (defined in core.yaml), this iterator cannot be used to fetch all matching keys (results will be limited by the totalQueryLimit). The
objectType
and attributes are expected to have only valid utf8 strings and should not contain U+0000 (nil byte) and U+10FFFF (biggest and unallocated code point). See related functions SplitCompositeKey and CreateCompositeKey. Call Close() on the returned StateQueryIteratorInterface object when done. The query is re-executed during validation phase to ensure result set has not changed since transaction endorsement (phantom reads detected).GetStateByPartialCompositeKey queries the state in the ledger based on a given partial composite key. This function returns an iterator which can be used to iterate over all composite keys whose prefix matches the given partial composite key. However, if the number of matching composite keys is greater than the totalQueryLimit (defined in core.yaml), this iterator cannot be used to fetch all matching keys (results will be limited by the totalQueryLimit). TheobjectType
and attributes are expected to have only valid utf8 strings and should not contain U+0000 (nil byte) and U+10FFFF (biggest and unallocated code point). See related functions SplitCompositeKey and CreateCompositeKey. Call Close() on the returned StateQueryIteratorInterface object when done. The query is re-executed during validation phase to ensure result set has not changed since transaction endorsement (phantom reads detected).
GetStateByPartialCompositeKeyWithPagination
使用复合键分页查询数据。好像有问题,并不能正常使用。
GetStateByPartialCompositeKeyWithPagination(objectType string, keys []string, pageSize int32, bookmark string) (StateQueryIteratorInterface, *pb.QueryResponseMetadata, error)
pageSize:每页条数。即本次查询返回的条数上限。 bookmark:书签,定位需查询的下一条数据,用于查询下一页
这个接口会返回一个包含了前pagesize条数据的迭代器,以及一个bookmark。这个bookmark并不像分页富查询返回的bookmark一样。这个接口返回的bookmark是查询结果返回的下一个复合键key,如:
通过复合键查询到的数据有20条,key为数字:1 ~ 20。传入pagesize为10,该接口会返回一个包含了前pagesize(即10)条数据的迭代器,以及一个值为下一条数据的复合键(即11)。
这时,如果不把上次查询到的bookmark(即11)传入这个函数,它会啥也查不到。富查询的bookmark传入后会返回下一页的数据,而这个函数的bookmark只是下一条数据的key,并不能查到下一页的数据。
需要将这个bookmark根据复合键的创建方式string(0) 拆分出来然后在重新创建新复合键,把该复合键作为bookmark传入该函数,才能获取到下一页的数据。
GetStateByRange
范围查找。可使用简单键或者复合键来查找。因为账本中的Key通过字典序来排列,所以可使用给定的范围进行多条数据匹配。(如查询年龄在18-23之间的用户数据)。左闭右开 (startKey <= data < endKey)。
func (ChaincodeStubInterface) GetStateByRange(startKey string, endKey string) (StateQueryIteratorInterface, error)
startKey:起始字符;endKey:结束字符。
若startKey为空字符串,则表示从账本数据起始位置一直匹配到endKey位置(类似于小于)。若endKey为空字符串,则表示从起始位置匹配到账本数据结尾(类似于大于)。注意:由于源码本身的原因,会导致endKey设为空字符串时查询不到数据。
GetStateByRange returns a range iterator over a set of keys in the ledger. The iterator can be used to iterate over all keys between the startKey (inclusive) and endKey (exclusive). However, if the number of keys between startKey and endKey is greater than the totalQueryLimit (defined in core.yaml), this iterator cannot be used to fetch all keys (results will be capped by the totalQueryLimit). The keys are returned by the iterator in lexical order. Note that startKey and endKey can be empty string, which implies unbounded range query on start or end. Call Close() on the returned StateQueryIteratorInterface object when done. The query is re-executed during validation phase to ensure result set has not changed since transaction endorsement (phantom reads detected).
注意:https://jira.hyperledger.org/browse/FAB-2183 fabric这次讨论中为了防止简单键和复合键的冲突,限制了 GetStateByRange 函数只能使用简单键。但是当我们只使用复合键而不使用简单键的时候这个限制是多余的。如果在使用该方法查询出现错误时,可以暂时将那一部分代码注释掉。
//目标文件:\GO\pkg\mod\github.com\hyperledger\fabric@v1.4.2\core\chaincode\shim\mockstub.go 275 - 277 行
func (stub *MockStub) GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) {
// if err := validateSimpleKeys(startKey, endKey); err != nil {
// return nil, err
// }
return NewMockStateRangeQueryIterator(stub, startKey, endKey), nil
}
peer.Response
Init和Invoke方法的返回值类型。常用shim.Error()和shim.Success()作为返回值。
type Response struct { Status int32
protobuf:"varint,1,opt,name=status,proto3" json:"status,omitempty"
Message stringprotobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"
Payload []byteprotobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"
XXX_NoUnkeyedLiteral struct{}json:"-"
XXX_unrecognized []bytejson:"-"
XXX_sizecache int32json:"-"
}A response with a representation similar to an HTTP response that can be used within another message.
shim.Success
函数正确执行后返回。与shim.Error对应
func Success(payload []byte) peer.Response
paload 返回的数据
可通过 peer.Response.Payload []byte获取到传入的payload数据
因为是[]byte数组类型,所以一般在回传数据时,会先使用json.Marshal()进行数据序列化。
在使用Payload获取数据时再使用json.Unmarshal()反序列化出真实数据
shim.Error
函数执行错误时返回。与shim.Success对应
func Error(msg string) peer.Response
msg 错误信息
可通过 peer.Response.Payload []byte获取到传入的payload数据
案例
main.go
package main
import (
"encoding/json"
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos/peer"
"sort"
"strconv"
)
type MyChaincode struct {
}
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
Sex string `json:"sex"`
Province string `json:"province"`
City string `json:"city"`
}
func (MyChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
return shim.Success(nil)
}
func (MyChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
//获取调用的方法及其参数
functionName,args := stub.GetFunctionAndParameters()
switch functionName {
case "AddUser":
return AddUser(stub,args)
case "AddUserByArray":
return AddUserByArray(stub,args)
case "SelectAllUser":
return SelectAllUser(stub)
case "SelectByID":
return SelectByID(stub,args)
case "SelectByName":
return SelectByName(stub,args)
case "SelectBySex":
return SelectBySex(stub,args)
case "SelectByProvinceAndCity":
return SelectByProvinceAndCity(stub,args)
case "SelectByProvinceOrCity":
return SelectByProvinceOrCity(stub,args)
case "SelectByRange":
return SelectByRange(stub,args)
case "UpdateNameByID":
return UpdateNameByID(stub,args)
}
return shim.Success(nil)
}
//UpdateNameByID 通过ID修改用户名
func UpdateNameByID(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args) != 2 {
return shim.Error("参数非法")
}
//先查
keyOfID,errOfCreComp := stub.CreateCompositeKey("user",[]string{"id",args[0]})
if errOfCreComp != nil {
fmt.Println(errOfCreComp)
return shim.Error("创建组合键失败")
}
result,errOfGetState := stub.GetState(keyOfID)
if errOfGetState != nil {
fmt.Println(errOfGetState)
return shim.Error("获取数据失败")
}
var user User
errOfUnmarshal := json.Unmarshal(result,&user)
if errOfUnmarshal != nil{
fmt.Println(errOfUnmarshal)
return shim.Error("数据反序列化失败")
}
//修改
user.Name = args[1]
//再存进去
data,errOfMarshal := json.Marshal(user)
if errOfMarshal != nil {
fmt.Println(errOfMarshal)
return shim.Error("数据序列化失败")
}
errOfPutState := stub.PutState(keyOfID,data)
if errOfPutState != nil {
fmt.Println(errOfPutState)
return shim.Error("存入数据失败")
}
//最后修改索引
//先删除原有索引
keyOfName,errOfCreName := stub.CreateCompositeKey("user",[]string{"name",user.Name})
if errOfCreName != nil {
fmt.Println(errOfCreName)
return shim.Error("创建组合键失败")
}
errOfDelState := stub.DelState(keyOfName)
if errOfDelState != nil {
fmt.Println(errOfDelState)
return shim.Error("删除组合键失败")
}
//添加新索引
keyOfName2,errOfCreName2 := stub.CreateCompositeKey("user",[]string{"name",args[1]})
if errOfCreName2 != nil {
fmt.Println(errOfCreName2)
return shim.Error("创建组合键失败")
}
errOfPutState2 := stub.PutState(keyOfName2,[]byte(keyOfID))
if errOfPutState2 != nil {
fmt.Println(errOfPutState2)
return shim.Error("存入数据失败")
}
return shim.Success([]byte("数据修改成功"))
}
//SelectByRange 通过范围查找
func SelectByRange(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args) != 2 {
return shim.Error("参数非法")
}
startAge := args[0]
startAge = formatAge(startAge)
endAge := args[1]
endAge = formatAge(endAge)
var users []User
//因为使用的是复合键存储数据 查的时候 也需要先构建复合键
start,errOfCreStartKey := stub.CreateCompositeKey("user",[]string{"age",startAge})
end,errOfCreEndKey := stub.CreateCompositeKey("user",[]string{"age",endAge})
if checkErrors(errOfCreStartKey,errOfCreEndKey) {
return shim.Error("创建组合键失败")
}
iterator,errOfGetByRange := stub.GetStateByRange(start,end)
defer iterator.Close()
if errOfGetByRange != nil {
fmt.Println(errOfGetByRange)
return shim.Error("获取主键数据失败")
}
for iterator.HasNext() {
kv,errOfIter := iterator.Next()
if errOfIter != nil {
fmt.Println(errOfIter)
return shim.Error("迭代异常")
}
result,errOfGetState := stub.GetState(string(kv.Value))
if errOfGetState != nil {
fmt.Println(errOfGetState)
}
var user User
errOfUnmarshal := json.Unmarshal(result,&user)
if errOfUnmarshal != nil {
fmt.Println(errOfUnmarshal)
return shim.Error("反序列失败")
}
users = append(users,user)
}
data,errOfMarshal := json.Marshal(users)
if errOfMarshal != nil {
fmt.Println(errOfMarshal)
return shim.Error("数据序列化失败")
}
return shim.Success(data)
}
//SelectByProvinceOrCity 通过省或市查找
func SelectByProvinceOrCity(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args) != 2 {
return shim.Error("参数非法")
}
var users []User
// 分别通过市或省查找时 若市在省内 则会造成重复
// 直接找出全部 再进行判断是否添加 在数据量小的时候使用若数据量太大则需根据具体情况而定
iterator, _ := stub.GetStateByPartialCompositeKey("user", []string{"id"})
defer iterator.Close()
for iterator.HasNext() {
kv, errOfIter := iterator.Next()
if errOfIter != nil {
fmt.Println(errOfIter)
return shim.Error(errOfIter.Error())
}
var user User
errOfUnmarshal := json.Unmarshal(kv.Value, &user)
if errOfUnmarshal != nil {
fmt.Println(errOfUnmarshal)
return shim.Error(errOfUnmarshal.Error())
}
if user.Province == province || user.City == city {
users = append(users, user)
}
}
data,errOfMarshal := json.Marshal(users)
if errOfMarshal != nil {
fmt.Println(errOfMarshal)
return shim.Error("数据序列化失败")
}
return shim.Success(data)
}
//SelectByProvinceAndCity 通过省市查找
func SelectByProvinceAndCity(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args) != 2 {
return shim.Error("参数非法")
}
var users []User
iterator,errOfGetCompKey := stub.GetStateByPartialCompositeKey("user",[]string{"province",args[0],args[1]})
defer iterator.Close()
if errOfGetCompKey != nil {
fmt.Println(errOfGetCompKey)
return shim.Error("获取主键数据失败")
}
for iterator.HasNext() {
kv,errOfIter := iterator.Next()
if errOfIter != nil {
fmt.Println(errOfIter)
return shim.Error("迭代异常")
}
result,errOfGetState := stub.GetState(string(kv.Value))
if errOfGetState != nil {
fmt.Println(errOfGetState)
}
var user User
errOfUnmarshal := json.Unmarshal(result,&user)
if errOfUnmarshal != nil {
fmt.Println(errOfUnmarshal)
return shim.Error("反序列失败")
}
users = append(users,user)
}
data,errOfMarshal := json.Marshal(users)
if errOfMarshal != nil {
fmt.Println(errOfMarshal)
return shim.Error("数据序列化失败")
}
return shim.Success(data)
}
//SelectBySex 通过性别查询并以年龄倒序排列
func SelectBySex(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args) != 1 {
return shim.Error("参数非法")
}
iterator,errOfGetByComp := stub.GetStateByPartialCompositeKey("user",[]string{"sex",args[0]})
defer iterator.Close()
if errOfGetByComp != nil {
fmt.Println(errOfGetByComp)
return shim.Error("获取主键失败")
}
var users []User
for iterator.HasNext() {
kv,errOfIter := iterator.Next()
if errOfIter != nil {
fmt.Println(errOfIter)
return shim.Error("迭代失败")
}
result,errOfGetState := stub.GetState(string(kv.Value))
if errOfGetState != nil {
fmt.Println(errOfGetState)
return shim.Error("获取数据失败")
}
var user User
errOfUnMarshal := json.Unmarshal(result,&user)
if errOfUnMarshal != nil {
fmt.Println(errOfUnMarshal)
return shim.Error("数据反序列化失败")
}
users = append(users,user)
}
//使用sort.Slice进行数组排序
sort.Slice(users, func(i, j int) bool {
if users[i].Age < users[j].Age {
return true
}
return false
})
data,errOfMarshal := json.Marshal(users)
if errOfMarshal != nil {
fmt.Println(errOfMarshal)
return shim.Error("数据序列化失败")
}
return shim.Success(data)
}
//SelectByName 通过单个名称查询
func SelectByName(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args) < 1 {
return shim.Error("参数异常")
}
var users []User
iterator,errOfGetNameKey := stub.GetStateByPartialCompositeKey("user",[]string{"name",args[0]})
defer iterator.Close()
if errOfGetNameKey != nil {
fmt.Println(errOfGetNameKey)
return shim.Error("通过组合键获取主键失败")
}
for iterator.HasNext() {
kv,errOfIterNext := iterator.Next()
if errOfIterNext != nil {
fmt.Println(errOfIterNext)
return shim.Error("迭代异常")
}
var user User
result,errOfGetState := stub.GetState(string(kv.Value))
if errOfGetState != nil {
fmt.Println(errOfGetState)
return shim.Error("查找数据失败")
}
errOfUnmarshal := json.Unmarshal(result,&user)
if errOfUnmarshal != nil {
fmt.Println(errOfUnmarshal)
return shim.Error("数据反序列化失败")
}
users = append(users,user)
}
data,errOfMarshal := json.Marshal(users)
if errOfMarshal != nil {
fmt.Println(errOfMarshal)
return shim.Error("数据序列化失败")
}
return shim.Success(data)
}
//SelectByID 通过ID查询
func SelectByID(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args) != 1 {
return shim.Error("非法参数")
}
keyOfID,errOfCreIDKey := stub.CreateCompositeKey("user",[]string{"id",args[0]})
if errOfCreIDKey != nil {
fmt.Println(errOfCreIDKey)
return shim.Error("创建组合键失败")
}
result,errOfGetState := stub.GetState(keyOfID)
if errOfGetState != nil {
fmt.Println(errOfGetState)
return shim.Error("获取数据失败")
}
return shim.Success(result)
}
//SelectAllUser 查找所有用户
func SelectAllUser(stub shim.ChaincodeStubInterface) pb.Response {
//所有用户的数据的主键都是以 user 和 id 开头的复合键 获取该前缀即可获取所有数据
iterator,errOfGetState := stub.GetStateByPartialCompositeKey("user",[]string{"id"})
defer iterator.Close()
if errOfGetState != nil {
fmt.Println(errOfGetState)
return shim.Error("获取数据失败!")
}
var users []User
for iterator.HasNext() {
kv,errOfIterNext := iterator.Next()
if errOfIterNext != nil {
fmt.Println(errOfIterNext)
return shim.Error("迭代失败!")
}
var user User
errOfUnmarshalUser := json.Unmarshal(kv.Value,&user)
if errOfUnmarshalUser != nil {
fmt.Println(errOfUnmarshalUser)
return shim.Error("反序列化User失败!")
}
users = append(users,user)
}
data,errOfMarshalUsers := json.Marshal(users)
if errOfMarshalUsers != nil {
fmt.Println(errOfMarshalUsers)
return shim.Error("数组序列化失败!")
}
return shim.Success(data)
}
//AddUserByArray 一次添加多个用户
func AddUserByArray(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args) != 1 {
return shim.Success([]byte("参数不合法"))
}
var users []User
errOfUnmarshalUser := json.Unmarshal([]byte(args[0]),&users)
if errOfUnmarshalUser != nil {
fmt.Println(errOfUnmarshalUser)
return shim.Error("参数转化数组失败!")
}
for i := 0; i < len(users); i ++ {
user := users[i]
//修改年龄显示格式 个位数前加0
age := strconv.Itoa(user.Age)
age = formatAge(age)
//创建主键
keyOfID,errOfCreateIDKey := stub.CreateCompositeKey("user",[]string{"id",strconv.Itoa(user.ID)})
//创建二级索引
keyOfName,errOfCreateNameKey := stub.CreateCompositeKey("user",[]string{"name",user.Name,strconv.Itoa(user.ID)})
keyOfAge,errOfCreateAgeKey := stub.CreateCompositeKey("user",[]string{"age",age,strconv.Itoa(user.ID)})
keyOfSex,errOfCreateSexKey := stub.CreateCompositeKey("user",[]string{"sex",user.Sex,strconv.Itoa(user.ID)})
keyOfPro,errOfCreateProKey := stub.CreateCompositeKey("user",[]string{"province",user.Province,user.City,strconv.Itoa(user.ID)})
keyOfCity,errOfCreateCityKey := stub.CreateCompositeKey("user",[]string{"city",user.City,strconv.Itoa(user.ID)})
if checkErrors(errOfCreateIDKey,errOfCreateNameKey,errOfCreateAgeKey,errOfCreateSexKey,errOfCreateProKey,errOfCreateCityKey) {
return shim.Error("创建复合键失败!")
}
//使用主键存真实数据
data,errOfMarshalUser := json.Marshal(user)
if errOfMarshalUser != nil {
fmt.Println(errOfMarshalUser)
return shim.Error("序列化User失败!")
}
errOfPutID := stub.PutState(keyOfID,data)
//使用二级索引存储主索引
realKey := []byte(keyOfID)
errOfPutName := stub.PutState(keyOfName,realKey)
errOfPutAge := stub.PutState(keyOfAge,realKey)
errOfPutSex := stub.PutState(keyOfSex,realKey)
errOfPutPro := stub.PutState(keyOfPro,realKey)
errOfPutCity := stub.PutState(keyOfCity,realKey)
if checkErrors(errOfPutID,errOfPutName,errOfPutAge,errOfPutSex,errOfPutPro,errOfPutCity) {
return shim.Error("数据存储失败!")
}
}
return shim.Success(nil)
}
//AddUser 添加单个用户
func AddUser(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args) != 1 {
return shim.Success([]byte("参数不合法"))
}
var user User
errOfUnmarshalUser := json.Unmarshal([]byte(args[0]),&user)
if errOfUnmarshalUser != nil {
fmt.Println(errOfUnmarshalUser)
return shim.Error("参数转化User失败!")
}
//修改年龄显示格式 个位数前加0
age := strconv.Itoa(user.Age)
age = formatAge(age)
//创建主键
keyOfID,errOfCreateIDKey := stub.CreateCompositeKey("user",[]string{"id",strconv.Itoa(user.ID)})
//创建二级索引
keyOfName,errOfCreateNameKey := stub.CreateCompositeKey("user",[]string{"name",user.Name,strconv.Itoa(user.ID)})
keyOfAge,errOfCreateAgeKey := stub.CreateCompositeKey("user",[]string{"age",age,strconv.Itoa(user.ID)})
keyOfSex,errOfCreateSexKey := stub.CreateCompositeKey("user",[]string{"sex",user.Sex,strconv.Itoa(user.ID)})
keyOfPro,errOfCreateProKey := stub.CreateCompositeKey("user",[]string{"province",user.Province,strconv.Itoa(user.ID)})
keyOfCity,errOfCreateCityKey := stub.CreateCompositeKey("user",[]string{"city",user.City,strconv.Itoa(user.ID)})
if checkErrors(errOfCreateIDKey,errOfCreateNameKey,errOfCreateAgeKey,errOfCreateSexKey,errOfCreateProKey,errOfCreateCityKey) {
return shim.Error("创建复合键失败!")
}
//使用主键存真实数据
data,errOfMarshalUser := json.Marshal(user)
if errOfMarshalUser != nil {
fmt.Println(errOfMarshalUser)
return shim.Error("序列化User失败!")
}
errOfPutID := stub.PutState(keyOfID,data)
//使用二级索引存储主索引
realKey := []byte(keyOfID)
errOfPutName := stub.PutState(keyOfName,realKey)
errOfPutAge := stub.PutState(keyOfAge,realKey)
errOfPutSex := stub.PutState(keyOfSex,realKey)
errOfPutPro := stub.PutState(keyOfPro,realKey)
errOfPutCity := stub.PutState(keyOfCity,realKey)
if checkErrors(errOfPutID,errOfPutName,errOfPutAge,errOfPutSex,errOfPutPro,errOfPutCity) {
return shim.Error("数据存储失败!")
}
return shim.Success([]byte("数据存储完成"))
}
//checkErrors 用于判断传入的error中是否有错误
func checkErrors(errors ...error) bool {
//用于记录该数组是否有错误
for i := 0; i < len(errors); i ++ {
if errors[i] != nil {
return true
}
}
return false
}
//用于格式化年龄 使年龄变为三位数
func formatAge(age string) string {
if len(age) == 1 {
age = "00" + age
} else if len(age) == 2 {
age = "0" + age
}
return age
}
func main() {
err := shim.Start(new(MyChaincode))
if err != nil {
fmt.Println("链码启动失败",err)
}
}
main_test.go
package main
import (
"encoding/json"
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/stretchr/testify/assert"
"testing"
)
func checkInit(t *testing.T, stub *shim.MockStub, args [][]byte) {
res := stub.MockInit("1", args)
assert.Equal(t, int32(shim.OK), res.Status, "Init failed:%v", string(res.Message))
}
func checkInvoke(t *testing.T, stub *shim.MockStub, args [][]byte) (data []byte) {
res := stub.MockInvoke("1", args)
assert.Equal(t, int32(shim.OK), res.Status, "Invoke failed:%v", string(res.Message))
return res.Payload
}
//执行Test方法,即可进行调试验证
func Test(t *testing.T){
chaincode := new(MyChaincode)
stub := shim.NewMockStub("MyChaincode",chaincode)
checkInit(t,stub,[][]byte{[]byte("init")})
var user = User{ID: 1,Name: "胡广",Age: 18,Sex: "男",Province: "四川",City: "宜宾"}
var user2 = User{ID: 2,Name: "老王",Age: 23,Sex: "男",Province: "四川",City: "成都"}
var user3 = User{ID: 3,Name: "张青青",Age: 22,Sex: "女",Province: "浙江",City: "台州"}
var user4 = User{ID: 4,Name: "小静静",Age: 30,Sex: "男",Province: "四川",City: "达州"}
var user5 = User{ID: 5,Name: "胡广",Age: 22,Sex: "男",Province: "浙江",City: "杭州"}
var user6 = User{ID: 6,Name: "老王",Age: 60,Sex: "男",Province: "四川",City: "宜宾"}
var user7 = User{ID: 7,Name: "唐军",Age: 23,Sex: "男",Province: "四川",City: "宜宾"}
var user8 = User{ID: 8,Name: "唐军",Age: 23,Sex: "男",Province: "浙江",City: "杭州"}
var users = []User{user,user2,user3,user4,user5,user6,user7,user8}
data,errOfMarshalUser := json.Marshal(users)
if errOfMarshalUser != nil {
fmt.Println("数据序列化失败")
}
//data,errOfMarshalUser := json.Marshal(user)
//if errOfMarshalUser != nil {
// fmt.Println("数据序列化失败")
//}
//resultOfAdd := checkInvoke(t,stub,[][]byte{[]byte("AddUser"),data})
//fmt.Println(string(resultOfAdd))
resultOfAddArray := checkInvoke(t,stub,[][]byte{[]byte("AddUserByArray"),data})
fmt.Println(string(resultOfAddArray))
resultOfSelectAll := checkInvoke(t,stub,[][]byte{[]byte("SelectAllUser")})
fmt.Println(string(resultOfSelectAll))
resultOfByID := checkInvoke(t,stub,[][]byte{[]byte("SelectByID"),[]byte("1")})
var userOfByID User
json.Unmarshal(resultOfByID,&userOfByID)
fmt.Println(userOfByID)
resultOfByName := checkInvoke(t,stub,[][]byte{[]byte("SelectByName"),[]byte("胡广")})
fmt.Println(string(resultOfByName))
resultOfBySex := checkInvoke(t,stub,[][]byte{[]byte("SelectBySex"),[]byte("男")})
fmt.Println(string(resultOfBySex))
resultOfByPro := checkInvoke(t,stub,[][]byte{[]byte("SelectByProvinceAndCity"),[]byte("浙江"),[]byte("杭州")})
fmt.Println(string(resultOfByPro))
resultOfByCity := checkInvoke(t,stub,[][]byte{[]byte("SelectByProvinceOrCity"),[]byte("浙江"),[]byte("宜宾")})
fmt.Println(string(resultOfByCity))
resultOfByRange := checkInvoke(t,stub,[][]byte{[]byte("SelectByRange"),[]byte("23"),[]byte("60")})
fmt.Println(string(resultOfByRange))
resultOfUpdate := checkInvoke(t,stub,[][]byte{[]byte("UpdateNameByID"),[]byte("1"),[]byte("帅哥")})
fmt.Println(string(resultOfUpdate))
resultOfByID2 := checkInvoke(t,stub,[][]byte{[]byte("SelectByID"),[]byte("1")})
var userOfByID2 User
json.Unmarshal(resultOfByID2,&userOfByID2)
fmt.Println(userOfByID2)
resultOfByName2 := checkInvoke(t,stub,[][]byte{[]byte("SelectByName"),[]byte("帅哥")})
fmt.Println(string(resultOfByName2))
}