• Gin+getway+Fabric2.4.4演示


    今天演示一下Gin+Gateway+Fabric2.4.4结合,从区块账本抓取数据显示到前端。

    前提环境:Gin,Gateway,Fabric2.4.4环境。

    如何部署Fabric2.4.4环境看我这篇博客:指路:fabric2.4.4版本搭建过程(完整过程)_keep_top的博客-CSDN博客_fabric 搭建

    首先,我摸看一下gin的模板:

    1. package main
    2. import (
    3. "fmt"
    4. "github.com/gin-gonic/gin"
    5. )
    6. type Stu struct {
    7. Name string `form:"name"`
    8. Id string `form:"id"`
    9. Age string `form:"age"`
    10. }
    11. func main() {
    12. r := gin.Default()
    13. var stu Stu
    14. r1 := r.Group("/fabric2.4")
    15. r1.POST("/setstu", func(c *gin.Context) {
    16. //var stu Stu
    17. c.ShouldBind(&stu)
    18. c.JSON(200, stu)
    19. fmt.Println("stu:", stu)
    20. })
    21. r1.POST("/ok1", func(c *gin.Context) {
    22. c.JSON(200, "ok1")
    23. })
    24. r.Run(":8080") // 监听并在 0.0.0.0:8080 上启动服务
    25. }

    解释一下:

    这里定义的结构体Stu,可以表示在前端输入结构体的信息,在后端拿到这个结构体的值,后续我们会定义链码中的结构体,用来获取账本中的数据和前端输入数据存入账本,进行演示。

    以下是链码的只要功能部分:

    1. package chaincode
    2. import (
    3. "encoding/json"
    4. "fmt"
    5. "github.com/hyperledger/fabric-contract-api-go/contractapi"
    6. )
    7. // SmartContract provides functions for managing an Asset
    8. type SmartContract struct {
    9. contractapi.Contract
    10. }
    11. // Asset describes basic details of what makes up a simple asset
    12. //Insert struct field in alphabetic order => to achieve determinism across languages
    13. // golang keeps the order when marshal to json but doesn't order automatically
    14. type Asset struct {
    15. AppraisedValue int `json:"AppraisedValue"`
    16. Color string `json:"Color"`
    17. ID string `json:"ID"`
    18. Owner string `json:"Owner"`
    19. Size int `json:"Size"`
    20. }
    21. // InitLedger adds a base set of assets to the ledger
    22. func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
    23. assets := []Asset{
    24. {ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
    25. {ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
    26. {ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
    27. {ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
    28. {ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
    29. {ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
    30. }
    31. for _, asset := range assets {
    32. assetJSON, err := json.Marshal(asset)
    33. if err != nil {
    34. return err
    35. }
    36. err = ctx.GetStub().PutState(asset.ID, assetJSON)
    37. if err != nil {
    38. return fmt.Errorf("failed to put to world state. %v", err)
    39. }
    40. }
    41. return nil
    42. }
    43. // CreateAsset issues a new asset to the world state with given details.
    44. func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
    45. exists, err := s.AssetExists(ctx, id)
    46. if err != nil {
    47. return err
    48. }
    49. if exists {
    50. return fmt.Errorf("the asset %s already exists", id)
    51. }
    52. asset := Asset{
    53. ID: id,
    54. Color: color,
    55. Size: size,
    56. Owner: owner,
    57. AppraisedValue: appraisedValue,
    58. }
    59. assetJSON, err := json.Marshal(asset)
    60. if err != nil {
    61. return err
    62. }
    63. return ctx.GetStub().PutState(id, assetJSON)
    64. }
    65. // ReadAsset returns the asset stored in the world state with given id.
    66. func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
    67. assetJSON, err := ctx.GetStub().GetState(id)
    68. if err != nil {
    69. return nil, fmt.Errorf("failed to read from world state: %v", err)
    70. }
    71. if assetJSON == nil {
    72. return nil, fmt.Errorf("the asset %s does not exist", id)
    73. }
    74. var asset Asset
    75. err = json.Unmarshal(assetJSON, &asset)
    76. if err != nil {
    77. return nil, err
    78. }
    79. return &asset, nil
    80. }
    81. // UpdateAsset updates an existing asset in the world state with provided parameters.
    82. func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
    83. exists, err := s.AssetExists(ctx, id)
    84. if err != nil {
    85. return err
    86. }
    87. if !exists {
    88. return fmt.Errorf("the asset %s does not exist", id)
    89. }
    90. // overwriting original asset with new asset
    91. asset := Asset{
    92. ID: id,
    93. Color: color,
    94. Size: size,
    95. Owner: owner,
    96. AppraisedValue: appraisedValue,
    97. }
    98. assetJSON, err := json.Marshal(asset)
    99. if err != nil {
    100. return err
    101. }
    102. return ctx.GetStub().PutState(id, assetJSON)
    103. }
    104. // DeleteAsset deletes an given asset from the world state.
    105. func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
    106. exists, err := s.AssetExists(ctx, id)
    107. if err != nil {
    108. return err
    109. }
    110. if !exists {
    111. return fmt.Errorf("the asset %s does not exist", id)
    112. }
    113. return ctx.GetStub().DelState(id)
    114. }
    115. // AssetExists returns true when asset with given ID exists in world state
    116. func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
    117. assetJSON, err := ctx.GetStub().GetState(id)
    118. if err != nil {
    119. return false, fmt.Errorf("failed to read from world state: %v", err)
    120. }
    121. return assetJSON != nil, nil
    122. }
    123. // TransferAsset updates the owner field of asset with given id in world state, and returns the old owner.
    124. func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) (string, error) {
    125. asset, err := s.ReadAsset(ctx, id)
    126. if err != nil {
    127. return "", err
    128. }
    129. oldOwner := asset.Owner
    130. asset.Owner = newOwner
    131. assetJSON, err := json.Marshal(asset)
    132. if err != nil {
    133. return "", err
    134. }
    135. err = ctx.GetStub().PutState(id, assetJSON)
    136. if err != nil {
    137. return "", err
    138. }
    139. return oldOwner, nil
    140. }
    141. // GetAllAssets returns all assets found in world state
    142. func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
    143. // range query with empty string for startKey and endKey does an
    144. // open-ended query of all assets in the chaincode namespace.
    145. resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
    146. if err != nil {
    147. return nil, err
    148. }
    149. defer resultsIterator.Close()
    150. var assets []*Asset
    151. for resultsIterator.HasNext() {
    152. queryResponse, err := resultsIterator.Next()
    153. if err != nil {
    154. return nil, err
    155. }
    156. var asset Asset
    157. err = json.Unmarshal(queryResponse.Value, &asset)
    158. if err != nil {
    159. return nil, err
    160. }
    161. assets = append(assets, &asset)
    162. }
    163. return assets, nil
    164. }

    这是官方的链码,大家可以在fabric中自己找到。

    这里需要把证书文件拉到goland中,注意,因为goland是在宿主机(windows)中的,所以需要在虚拟机中把证书文件拉到window上面:

     

     我们更改路径:

    查看虚拟机的ip地址:

     这是需要更改的路径:

     运行这个go文件:

    显示:

     连接虚拟机成功了,也就是连接fabric底层成功了!

    接下来我们结合一下gin的部分:

    首先我们需要导入证书文件:

    如果报错了,基本上就是证书文件对不上的原因!注意,fabric网络每次重启后证书文件都会重新生成,所以每次重启了fabric网络,证书文件都需要更换! 

    贴代码:

    1. package main
    2. import (
    3. "bytes"
    4. "crypto/x509"
    5. "encoding/json"
    6. "fmt"
    7. "github.com/gin-gonic/gin"
    8. "github.com/hyperledger/fabric-gateway/pkg/client"
    9. "github.com/hyperledger/fabric-gateway/pkg/identity"
    10. "google.golang.org/grpc"
    11. "google.golang.org/grpc/credentials"
    12. "io/ioutil"
    13. "path"
    14. "time"
    15. )
    16. const (
    17. mspID = "Org1MSP"
    18. cryptoPath = "./peerOrganizations/org1.example.com"
    19. certPath = cryptoPath + "/users/User1@org1.example.com/msp/signcerts/cert.pem"
    20. keyPath = cryptoPath + "/users/User1@org1.example.com/msp/keystore/"
    21. tlsCertPath = cryptoPath + "/peers/peer0.org1.example.com/tls/ca.crt"
    22. peerEndpoint = "192.168.136.130:7051"
    23. gatewayPeer = "peer0.org1.example.com"
    24. channelName = "mychannel"
    25. chaincodeName = "basic"
    26. )
    27. type Asset struct {
    28. AppraisedValue int `form:"appraisedValue" json:"appraisedValue" `
    29. Color string `form:"color" json:"color"`
    30. ID string `form:"id" json:"id"`
    31. Owner string `form:"owner" json:"owner"`
    32. Size int `form:"size" json:"size"`
    33. }
    34. func main() {
    35. // The gRPC client connection should be shared by all Gateway connections to this endpoint
    36. clientConnection := newGrpcConnection()
    37. defer clientConnection.Close()
    38. id := newIdentity()
    39. sign := newSign()
    40. // Create a Gateway connection for a specific client identity
    41. gateway, err := client.Connect(
    42. id,
    43. client.WithSign(sign),
    44. client.WithClientConnection(clientConnection),
    45. // Default timeouts for different gRPC calls
    46. client.WithEvaluateTimeout(5*time.Second),
    47. client.WithEndorseTimeout(15*time.Second),
    48. client.WithSubmitTimeout(5*time.Second),
    49. client.WithCommitStatusTimeout(1*time.Minute),
    50. )
    51. if err != nil {
    52. panic(err)
    53. }
    54. defer gateway.Close()
    55. network := gateway.GetNetwork(channelName)
    56. contract := network.GetContract(chaincodeName)
    57. r := gin.Default()
    58. r1 := r.Group("/fabric2.4")
    59. r1.POST("/CreateAsset", func(c *gin.Context) {
    60. var asset Asset
    61. c.ShouldBind(&asset)
    62. c.JSON(200, asset)
    63. marshal, _ := json.Marshal(asset)
    64. fmt.Println(string(marshal))
    65. fmt.Println("asset:", asset)
    66. })
    67. r1.POST("/GetAllAssets", func(c *gin.Context) {
    68. result := getAllAssets(contract)
    69. c.JSON(200, result)
    70. })
    71. r.Run(":8080") // 监听并在 0.0.0.0:8080 上启动服务
    72. }
    73. // Evaluate a transaction to query ledger state.
    74. func getAllAssets(contract *client.Contract) string {
    75. fmt.Println("Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger")
    76. evaluateResult, err := contract.EvaluateTransaction("GetAllAssets")
    77. if err != nil {
    78. panic(fmt.Errorf("failed to evaluate transaction: %w", err))
    79. }
    80. result := formatJSON(evaluateResult)
    81. fmt.Printf("*** Result:%s\n", result)
    82. return string(evaluateResult)
    83. }
    84. // newGrpcConnection creates a gRPC connection to the Gateway server.
    85. func newGrpcConnection() *grpc.ClientConn {
    86. certificate, err := loadCertificate(tlsCertPath)
    87. if err != nil {
    88. panic(err)
    89. }
    90. certPool := x509.NewCertPool()
    91. certPool.AddCert(certificate)
    92. transportCredentials := credentials.NewClientTLSFromCert(certPool, gatewayPeer)
    93. connection, err := grpc.Dial(peerEndpoint, grpc.WithTransportCredentials(transportCredentials))
    94. if err != nil {
    95. panic(fmt.Errorf("failed to create gRPC connection: %w", err))
    96. }
    97. return connection
    98. }
    99. // newIdentity creates a client identity for this Gateway connection using an X.509 certificate.
    100. func newIdentity() *identity.X509Identity {
    101. certificate, err := loadCertificate(certPath)
    102. if err != nil {
    103. panic(err)
    104. }
    105. id, err := identity.NewX509Identity(mspID, certificate)
    106. if err != nil {
    107. panic(err)
    108. }
    109. return id
    110. }
    111. func loadCertificate(filename string) (*x509.Certificate, error) {
    112. certificatePEM, err := ioutil.ReadFile(filename)
    113. if err != nil {
    114. return nil, fmt.Errorf("failed to read certificate file: %w", err)
    115. }
    116. return identity.CertificateFromPEM(certificatePEM)
    117. }
    118. // newSign creates a function that generates a digital signature from a message digest using a private key.
    119. func newSign() identity.Sign {
    120. files, err := ioutil.ReadDir(keyPath)
    121. if err != nil {
    122. panic(fmt.Errorf("failed to read private key directory: %w", err))
    123. }
    124. privateKeyPEM, err := ioutil.ReadFile(path.Join(keyPath, files[0].Name()))
    125. if err != nil {
    126. panic(fmt.Errorf("failed to read private key file: %w", err))
    127. }
    128. privateKey, err := identity.PrivateKeyFromPEM(privateKeyPEM)
    129. if err != nil {
    130. panic(err)
    131. }
    132. sign, err := identity.NewPrivateKeySign(privateKey)
    133. if err != nil {
    134. panic(err)
    135. }
    136. return sign
    137. }
    138. // Format JSON data
    139. func formatJSON(data []byte) string {
    140. var prettyJSON bytes.Buffer
    141. if err := json.Indent(&prettyJSON, data, " ", ""); err != nil {
    142. panic(fmt.Errorf("failed to parse JSON: %w", err))
    143. }
    144. return prettyJSON.String()
    145. }

    为了方便演示。这里仅仅结合了查询所有信息的功能,如需要结合其他功能可以自己结合:

    我们来分析结构:

    首先添加Gateway的功能函数:

    1. // newGrpcConnection creates a gRPC connection to the Gateway server.
    2. func newGrpcConnection() *grpc.ClientConn {
    3. certificate, err := loadCertificate(tlsCertPath)
    4. if err != nil {
    5. panic(err)
    6. }
    7. certPool := x509.NewCertPool()
    8. certPool.AddCert(certificate)
    9. transportCredentials := credentials.NewClientTLSFromCert(certPool, gatewayPeer)
    10. connection, err := grpc.Dial(peerEndpoint, grpc.WithTransportCredentials(transportCredentials))
    11. if err != nil {
    12. panic(fmt.Errorf("failed to create gRPC connection: %w", err))
    13. }
    14. return connection
    15. }
    16. // newIdentity creates a client identity for this Gateway connection using an X.509 certificate.
    17. func newIdentity() *identity.X509Identity {
    18. certificate, err := loadCertificate(certPath)
    19. if err != nil {
    20. panic(err)
    21. }
    22. id, err := identity.NewX509Identity(mspID, certificate)
    23. if err != nil {
    24. panic(err)
    25. }
    26. return id
    27. }
    28. func loadCertificate(filename string) (*x509.Certificate, error) {
    29. certificatePEM, err := ioutil.ReadFile(filename)
    30. if err != nil {
    31. return nil, fmt.Errorf("failed to read certificate file: %w", err)
    32. }
    33. return identity.CertificateFromPEM(certificatePEM)
    34. }
    35. // newSign creates a function that generates a digital signature from a message digest using a private key.
    36. func newSign() identity.Sign {
    37. files, err := ioutil.ReadDir(keyPath)
    38. if err != nil {
    39. panic(fmt.Errorf("failed to read private key directory: %w", err))
    40. }
    41. privateKeyPEM, err := ioutil.ReadFile(path.Join(keyPath, files[0].Name()))
    42. if err != nil {
    43. panic(fmt.Errorf("failed to read private key file: %w", err))
    44. }
    45. privateKey, err := identity.PrivateKeyFromPEM(privateKeyPEM)
    46. if err != nil {
    47. panic(err)
    48. }
    49. sign, err := identity.NewPrivateKeySign(privateKey)
    50. if err != nil {
    51. panic(err)
    52. }
    53. return sign
    54. }
    55. // Format JSON data
    56. func formatJSON(data []byte) string {
    57. var prettyJSON bytes.Buffer
    58. if err := json.Indent(&prettyJSON, data, " ", ""); err != nil {
    59. panic(fmt.Errorf("failed to parse JSON: %w", err))
    60. }
    61. return prettyJSON.String()
    62. }

    然后在最开始添加常量信息,里面含有fabric的一些基本信息,还有定义asset结构体:

    1. const (
    2. mspID = "Org1MSP"
    3. cryptoPath = "./peerOrganizations/org1.example.com"
    4. certPath = cryptoPath + "/users/User1@org1.example.com/msp/signcerts/cert.pem"
    5. keyPath = cryptoPath + "/users/User1@org1.example.com/msp/keystore/"
    6. tlsCertPath = cryptoPath + "/peers/peer0.org1.example.com/tls/ca.crt"
    7. peerEndpoint = "192.168.136.130:7051"
    8. gatewayPeer = "peer0.org1.example.com"
    9. channelName = "mychannel"
    10. chaincodeName = "basic"
    11. )
    12. type Asset struct {
    13. AppraisedValue int `form:"appraisedValue" json:"appraisedValue" `
    14. Color string `form:"color" json:"color"`
    15. ID string `form:"id" json:"id"`
    16. Owner string `form:"owner" json:"owner"`
    17. Size int `form:"size" json:"size"`
    18. }

    在mian函数里首先添加连接Gateway的代码:

    (这里注意,如果我们先写的是连接Gateway的代码,表示我们后续都会使用这个用户的身份去连接固定的fabric peer节点进行操作!如果我们想要每次连接使用不同用户身份连接不用的peer节点那么只要按照逻辑改变代码即可)

    1. // The gRPC client connection should be shared by all Gateway connections to this endpoint
    2. clientConnection := newGrpcConnection()
    3. defer clientConnection.Close()
    4. id := newIdentity()
    5. sign := newSign()
    6. // Create a Gateway connection for a specific client identity
    7. gateway, err := client.Connect(
    8. id,
    9. client.WithSign(sign),
    10. client.WithClientConnection(clientConnection),
    11. // Default timeouts for different gRPC calls
    12. client.WithEvaluateTimeout(5*time.Second),
    13. client.WithEndorseTimeout(15*time.Second),
    14. client.WithSubmitTimeout(5*time.Second),
    15. client.WithCommitStatusTimeout(1*time.Minute),
    16. )
    17. if err != nil {
    18. panic(err)
    19. }
    20. defer gateway.Close()
    21. network := gateway.GetNetwork(channelName)
    22. contract := network.GetContract(chaincodeName)

    我们再写gin的代码:

    1. r := gin.Default()
    2. r1 := r.Group("/fabric2.4")
    3. r1.POST("/CreateAsset", func(c *gin.Context) {
    4. var asset Asset
    5. c.ShouldBind(&asset)
    6. c.JSON(200, asset)
    7. marshal, _ := json.Marshal(asset)
    8. fmt.Println(string(marshal))
    9. fmt.Println("asset:", asset)
    10. })
    11. r1.POST("/GetAllAssets", func(c *gin.Context) {
    12. result := getAllAssets(contract)
    13. c.JSON(200, result)
    14. })
    15. r.Run(":8080") // 监听并在 0.0.0.0:8080 上启动服务

    这里在GetAllAssets中,我们执行result := getAllAssets(contract)函数:

    如下:

    1. // Evaluate a transaction to query ledger state.
    2. func getAllAssets(contract *client.Contract) string {
    3. fmt.Println("Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger")
    4. evaluateResult, err := contract.EvaluateTransaction("GetAllAssets")
    5. if err != nil {
    6. panic(fmt.Errorf("failed to evaluate transaction: %w", err))
    7. }
    8. result := formatJSON(evaluateResult)
    9. fmt.Printf("*** Result:%s\n", result)
    10. return string(evaluateResult)
    11. }

    这个方法是官方的demo,我进行了小改动,返回一个string是为了方便大家在前端看到拿到的数据,原本的方法是没有返回值的,这里注意。

    最后,我们执行main函数:

    打开postman进行测试:

     

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

  • 相关阅读:
    RMAN-06217: not connected to auxiliary database with a net service name
    房屋差价能否作为非违约方的损失
    如何建立用户关注与青睐的产品设计?
    Micro-OLED(硅基OLED)的工艺简介
    过五关,斩六将!「网易/美团/菜鸟」已拿offer【Java岗】
    基础复习——数据库SQLite——SQL的基本语法——数据库管理器SQLiteDatabase——数据库帮助器SQLiteOpenHelper...
    vue打包优化
    近视是怎样形成的?
    Selenium实现自动登录163邮箱和Locating Elements介绍
    中国财政科学研究院党委书记、院长刘尚希一行莅临麒麟信安调研
  • 原文地址:https://blog.csdn.net/lakersssss24/article/details/126434147