今天演示一下Gin+Gateway+Fabric2.4.4结合,从区块账本抓取数据显示到前端。
前提环境:Gin,Gateway,Fabric2.4.4环境。
如何部署Fabric2.4.4环境看我这篇博客:指路:fabric2.4.4版本搭建过程(完整过程)_keep_top的博客-CSDN博客_fabric 搭建
首先,我摸看一下gin的模板:
- package main
-
- import (
- "fmt"
- "github.com/gin-gonic/gin"
- )
-
- type Stu struct {
- Name string `form:"name"`
- Id string `form:"id"`
- Age string `form:"age"`
- }
-
- func main() {
- r := gin.Default()
- var stu Stu
- r1 := r.Group("/fabric2.4")
- r1.POST("/setstu", func(c *gin.Context) {
- //var stu Stu
- c.ShouldBind(&stu)
- c.JSON(200, stu)
- fmt.Println("stu:", stu)
- })
- r1.POST("/ok1", func(c *gin.Context) {
- c.JSON(200, "ok1")
- })
- r.Run(":8080") // 监听并在 0.0.0.0:8080 上启动服务
-
- }
解释一下:
这里定义的结构体Stu,可以表示在前端输入结构体的信息,在后端拿到这个结构体的值,后续我们会定义链码中的结构体,用来获取账本中的数据和前端输入数据存入账本,进行演示。
以下是链码的只要功能部分:
- package chaincode
-
- import (
- "encoding/json"
- "fmt"
-
- "github.com/hyperledger/fabric-contract-api-go/contractapi"
- )
-
- // SmartContract provides functions for managing an Asset
- type SmartContract struct {
- contractapi.Contract
- }
-
- // Asset describes basic details of what makes up a simple asset
- //Insert struct field in alphabetic order => to achieve determinism across languages
- // golang keeps the order when marshal to json but doesn't order automatically
- type Asset struct {
- AppraisedValue int `json:"AppraisedValue"`
- Color string `json:"Color"`
- ID string `json:"ID"`
- Owner string `json:"Owner"`
- Size int `json:"Size"`
- }
-
- // InitLedger adds a base set of assets to the ledger
- func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
- assets := []Asset{
- {ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
- {ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
- {ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
- {ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
- {ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
- {ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
- }
-
- for _, asset := range assets {
- assetJSON, err := json.Marshal(asset)
- if err != nil {
- return err
- }
-
- err = ctx.GetStub().PutState(asset.ID, assetJSON)
- if err != nil {
- return fmt.Errorf("failed to put to world state. %v", err)
- }
- }
-
- return nil
- }
-
- // CreateAsset issues a new asset to the world state with given details.
- func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
- exists, err := s.AssetExists(ctx, id)
- if err != nil {
- return err
- }
- if exists {
- return fmt.Errorf("the asset %s already exists", id)
- }
-
- asset := Asset{
- ID: id,
- Color: color,
- Size: size,
- Owner: owner,
- AppraisedValue: appraisedValue,
- }
- assetJSON, err := json.Marshal(asset)
- if err != nil {
- return err
- }
-
- return ctx.GetStub().PutState(id, assetJSON)
- }
-
- // ReadAsset returns the asset stored in the world state with given id.
- func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
- assetJSON, err := ctx.GetStub().GetState(id)
- if err != nil {
- return nil, fmt.Errorf("failed to read from world state: %v", err)
- }
- if assetJSON == nil {
- return nil, fmt.Errorf("the asset %s does not exist", id)
- }
-
- var asset Asset
- err = json.Unmarshal(assetJSON, &asset)
- if err != nil {
- return nil, err
- }
-
- return &asset, nil
- }
-
- // UpdateAsset updates an existing asset in the world state with provided parameters.
- func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
- exists, err := s.AssetExists(ctx, id)
- if err != nil {
- return err
- }
- if !exists {
- return fmt.Errorf("the asset %s does not exist", id)
- }
-
- // overwriting original asset with new asset
- asset := Asset{
- ID: id,
- Color: color,
- Size: size,
- Owner: owner,
- AppraisedValue: appraisedValue,
- }
- assetJSON, err := json.Marshal(asset)
- if err != nil {
- return err
- }
-
- return ctx.GetStub().PutState(id, assetJSON)
- }
-
- // DeleteAsset deletes an given asset from the world state.
- func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
- exists, err := s.AssetExists(ctx, id)
- if err != nil {
- return err
- }
- if !exists {
- return fmt.Errorf("the asset %s does not exist", id)
- }
-
- return ctx.GetStub().DelState(id)
- }
-
- // AssetExists returns true when asset with given ID exists in world state
- func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
- assetJSON, err := ctx.GetStub().GetState(id)
- if err != nil {
- return false, fmt.Errorf("failed to read from world state: %v", err)
- }
-
- return assetJSON != nil, nil
- }
-
- // TransferAsset updates the owner field of asset with given id in world state, and returns the old owner.
- func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) (string, error) {
- asset, err := s.ReadAsset(ctx, id)
- if err != nil {
- return "", err
- }
-
- oldOwner := asset.Owner
- asset.Owner = newOwner
-
- assetJSON, err := json.Marshal(asset)
- if err != nil {
- return "", err
- }
-
- err = ctx.GetStub().PutState(id, assetJSON)
- if err != nil {
- return "", err
- }
-
- return oldOwner, nil
- }
-
- // GetAllAssets returns all assets found in world state
- func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
- // range query with empty string for startKey and endKey does an
- // open-ended query of all assets in the chaincode namespace.
- resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
- if err != nil {
- return nil, err
- }
- defer resultsIterator.Close()
-
- var assets []*Asset
- for resultsIterator.HasNext() {
- queryResponse, err := resultsIterator.Next()
- if err != nil {
- return nil, err
- }
-
- var asset Asset
- err = json.Unmarshal(queryResponse.Value, &asset)
- if err != nil {
- return nil, err
- }
- assets = append(assets, &asset)
- }
-
- return assets, nil
- }
这是官方的链码,大家可以在fabric中自己找到。
这里需要把证书文件拉到goland中,注意,因为goland是在宿主机(windows)中的,所以需要在虚拟机中把证书文件拉到window上面:


我们更改路径:
查看虚拟机的ip地址:

这是需要更改的路径:
运行这个go文件:
显示:

连接虚拟机成功了,也就是连接fabric底层成功了!
接下来我们结合一下gin的部分:
首先我们需要导入证书文件:

贴代码:
- package main
-
- import (
- "bytes"
- "crypto/x509"
- "encoding/json"
- "fmt"
- "github.com/gin-gonic/gin"
- "github.com/hyperledger/fabric-gateway/pkg/client"
- "github.com/hyperledger/fabric-gateway/pkg/identity"
- "google.golang.org/grpc"
- "google.golang.org/grpc/credentials"
- "io/ioutil"
- "path"
- "time"
- )
-
- const (
- mspID = "Org1MSP"
- cryptoPath = "./peerOrganizations/org1.example.com"
- certPath = cryptoPath + "/users/User1@org1.example.com/msp/signcerts/cert.pem"
- keyPath = cryptoPath + "/users/User1@org1.example.com/msp/keystore/"
- tlsCertPath = cryptoPath + "/peers/peer0.org1.example.com/tls/ca.crt"
- peerEndpoint = "192.168.136.130:7051"
- gatewayPeer = "peer0.org1.example.com"
- channelName = "mychannel"
- chaincodeName = "basic"
- )
-
- type Asset struct {
- AppraisedValue int `form:"appraisedValue" json:"appraisedValue" `
- Color string `form:"color" json:"color"`
- ID string `form:"id" json:"id"`
- Owner string `form:"owner" json:"owner"`
- Size int `form:"size" json:"size"`
- }
-
- func main() {
- // The gRPC client connection should be shared by all Gateway connections to this endpoint
- clientConnection := newGrpcConnection()
- defer clientConnection.Close()
-
- id := newIdentity()
- sign := newSign()
-
- // Create a Gateway connection for a specific client identity
- gateway, err := client.Connect(
- id,
- client.WithSign(sign),
- client.WithClientConnection(clientConnection),
- // Default timeouts for different gRPC calls
- client.WithEvaluateTimeout(5*time.Second),
- client.WithEndorseTimeout(15*time.Second),
- client.WithSubmitTimeout(5*time.Second),
- client.WithCommitStatusTimeout(1*time.Minute),
- )
- if err != nil {
- panic(err)
- }
- defer gateway.Close()
- network := gateway.GetNetwork(channelName)
- contract := network.GetContract(chaincodeName)
-
- r := gin.Default()
- r1 := r.Group("/fabric2.4")
- r1.POST("/CreateAsset", func(c *gin.Context) {
- var asset Asset
- c.ShouldBind(&asset)
- c.JSON(200, asset)
- marshal, _ := json.Marshal(asset)
- fmt.Println(string(marshal))
- fmt.Println("asset:", asset)
- })
- r1.POST("/GetAllAssets", func(c *gin.Context) {
- result := getAllAssets(contract)
- c.JSON(200, result)
- })
- r.Run(":8080") // 监听并在 0.0.0.0:8080 上启动服务
-
- }
-
- // Evaluate a transaction to query ledger state.
- func getAllAssets(contract *client.Contract) string {
- fmt.Println("Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger")
-
- evaluateResult, err := contract.EvaluateTransaction("GetAllAssets")
- if err != nil {
- panic(fmt.Errorf("failed to evaluate transaction: %w", err))
- }
- result := formatJSON(evaluateResult)
-
- fmt.Printf("*** Result:%s\n", result)
-
- return string(evaluateResult)
- }
-
- // newGrpcConnection creates a gRPC connection to the Gateway server.
- func newGrpcConnection() *grpc.ClientConn {
- certificate, err := loadCertificate(tlsCertPath)
- if err != nil {
- panic(err)
- }
-
- certPool := x509.NewCertPool()
- certPool.AddCert(certificate)
- transportCredentials := credentials.NewClientTLSFromCert(certPool, gatewayPeer)
-
- connection, err := grpc.Dial(peerEndpoint, grpc.WithTransportCredentials(transportCredentials))
- if err != nil {
- panic(fmt.Errorf("failed to create gRPC connection: %w", err))
- }
-
- return connection
- }
-
- // newIdentity creates a client identity for this Gateway connection using an X.509 certificate.
- func newIdentity() *identity.X509Identity {
- certificate, err := loadCertificate(certPath)
- if err != nil {
- panic(err)
- }
-
- id, err := identity.NewX509Identity(mspID, certificate)
- if err != nil {
- panic(err)
- }
-
- return id
- }
-
- func loadCertificate(filename string) (*x509.Certificate, error) {
- certificatePEM, err := ioutil.ReadFile(filename)
- if err != nil {
- return nil, fmt.Errorf("failed to read certificate file: %w", err)
- }
- return identity.CertificateFromPEM(certificatePEM)
- }
-
- // newSign creates a function that generates a digital signature from a message digest using a private key.
- func newSign() identity.Sign {
- files, err := ioutil.ReadDir(keyPath)
- if err != nil {
- panic(fmt.Errorf("failed to read private key directory: %w", err))
- }
- privateKeyPEM, err := ioutil.ReadFile(path.Join(keyPath, files[0].Name()))
-
- if err != nil {
- panic(fmt.Errorf("failed to read private key file: %w", err))
- }
-
- privateKey, err := identity.PrivateKeyFromPEM(privateKeyPEM)
- if err != nil {
- panic(err)
- }
-
- sign, err := identity.NewPrivateKeySign(privateKey)
- if err != nil {
- panic(err)
- }
-
- return sign
- }
-
- // Format JSON data
- func formatJSON(data []byte) string {
- var prettyJSON bytes.Buffer
- if err := json.Indent(&prettyJSON, data, " ", ""); err != nil {
- panic(fmt.Errorf("failed to parse JSON: %w", err))
- }
- return prettyJSON.String()
- }
为了方便演示。这里仅仅结合了查询所有信息的功能,如需要结合其他功能可以自己结合:
我们来分析结构:
首先添加Gateway的功能函数:
- // newGrpcConnection creates a gRPC connection to the Gateway server.
- func newGrpcConnection() *grpc.ClientConn {
- certificate, err := loadCertificate(tlsCertPath)
- if err != nil {
- panic(err)
- }
-
- certPool := x509.NewCertPool()
- certPool.AddCert(certificate)
- transportCredentials := credentials.NewClientTLSFromCert(certPool, gatewayPeer)
-
- connection, err := grpc.Dial(peerEndpoint, grpc.WithTransportCredentials(transportCredentials))
- if err != nil {
- panic(fmt.Errorf("failed to create gRPC connection: %w", err))
- }
-
- return connection
- }
-
- // newIdentity creates a client identity for this Gateway connection using an X.509 certificate.
- func newIdentity() *identity.X509Identity {
- certificate, err := loadCertificate(certPath)
- if err != nil {
- panic(err)
- }
-
- id, err := identity.NewX509Identity(mspID, certificate)
- if err != nil {
- panic(err)
- }
-
- return id
- }
-
- func loadCertificate(filename string) (*x509.Certificate, error) {
- certificatePEM, err := ioutil.ReadFile(filename)
- if err != nil {
- return nil, fmt.Errorf("failed to read certificate file: %w", err)
- }
- return identity.CertificateFromPEM(certificatePEM)
- }
-
- // newSign creates a function that generates a digital signature from a message digest using a private key.
- func newSign() identity.Sign {
- files, err := ioutil.ReadDir(keyPath)
- if err != nil {
- panic(fmt.Errorf("failed to read private key directory: %w", err))
- }
- privateKeyPEM, err := ioutil.ReadFile(path.Join(keyPath, files[0].Name()))
-
- if err != nil {
- panic(fmt.Errorf("failed to read private key file: %w", err))
- }
-
- privateKey, err := identity.PrivateKeyFromPEM(privateKeyPEM)
- if err != nil {
- panic(err)
- }
-
- sign, err := identity.NewPrivateKeySign(privateKey)
- if err != nil {
- panic(err)
- }
-
- return sign
- }
-
- // Format JSON data
- func formatJSON(data []byte) string {
- var prettyJSON bytes.Buffer
- if err := json.Indent(&prettyJSON, data, " ", ""); err != nil {
- panic(fmt.Errorf("failed to parse JSON: %w", err))
- }
- return prettyJSON.String()
- }
然后在最开始添加常量信息,里面含有fabric的一些基本信息,还有定义asset结构体:
- const (
- mspID = "Org1MSP"
- cryptoPath = "./peerOrganizations/org1.example.com"
- certPath = cryptoPath + "/users/User1@org1.example.com/msp/signcerts/cert.pem"
- keyPath = cryptoPath + "/users/User1@org1.example.com/msp/keystore/"
- tlsCertPath = cryptoPath + "/peers/peer0.org1.example.com/tls/ca.crt"
- peerEndpoint = "192.168.136.130:7051"
- gatewayPeer = "peer0.org1.example.com"
- channelName = "mychannel"
- chaincodeName = "basic"
- )
-
- type Asset struct {
- AppraisedValue int `form:"appraisedValue" json:"appraisedValue" `
- Color string `form:"color" json:"color"`
- ID string `form:"id" json:"id"`
- Owner string `form:"owner" json:"owner"`
- Size int `form:"size" json:"size"`
- }
在mian函数里首先添加连接Gateway的代码:
(这里注意,如果我们先写的是连接Gateway的代码,表示我们后续都会使用这个用户的身份去连接固定的fabric peer节点进行操作!如果我们想要每次连接使用不同用户身份连接不用的peer节点那么只要按照逻辑改变代码即可)
- // The gRPC client connection should be shared by all Gateway connections to this endpoint
- clientConnection := newGrpcConnection()
- defer clientConnection.Close()
-
- id := newIdentity()
- sign := newSign()
-
- // Create a Gateway connection for a specific client identity
- gateway, err := client.Connect(
- id,
- client.WithSign(sign),
- client.WithClientConnection(clientConnection),
- // Default timeouts for different gRPC calls
- client.WithEvaluateTimeout(5*time.Second),
- client.WithEndorseTimeout(15*time.Second),
- client.WithSubmitTimeout(5*time.Second),
- client.WithCommitStatusTimeout(1*time.Minute),
- )
- if err != nil {
- panic(err)
- }
- defer gateway.Close()
- network := gateway.GetNetwork(channelName)
- contract := network.GetContract(chaincodeName)
我们再写gin的代码:
- r := gin.Default()
- r1 := r.Group("/fabric2.4")
- r1.POST("/CreateAsset", func(c *gin.Context) {
- var asset Asset
- c.ShouldBind(&asset)
- c.JSON(200, asset)
- marshal, _ := json.Marshal(asset)
- fmt.Println(string(marshal))
- fmt.Println("asset:", asset)
- })
- r1.POST("/GetAllAssets", func(c *gin.Context) {
- result := getAllAssets(contract)
- c.JSON(200, result)
- })
- r.Run(":8080") // 监听并在 0.0.0.0:8080 上启动服务
这里在GetAllAssets中,我们执行result := getAllAssets(contract)函数:
如下:
- // Evaluate a transaction to query ledger state.
- func getAllAssets(contract *client.Contract) string {
- fmt.Println("Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger")
-
- evaluateResult, err := contract.EvaluateTransaction("GetAllAssets")
- if err != nil {
- panic(fmt.Errorf("failed to evaluate transaction: %w", err))
- }
- result := formatJSON(evaluateResult)
-
- fmt.Printf("*** Result:%s\n", result)
-
- return string(evaluateResult)
- }
这个方法是官方的demo,我进行了小改动,返回一个string是为了方便大家在前端看到拿到的数据,原本的方法是没有返回值的,这里注意。
最后,我们执行main函数:

打开postman进行测试:

我们看到账本的数据已经查出来到前端了!