基於Fabric的簡單Web應用

基於Fabric的簡單Web應用

作爲初學者,本文主要以復現課程爲主,相關內容在文末的參考鏈接。

一、基本環境

  • Ubuntu 18.04
  • vim、git
  • docker 19.03.5
  • docker-compose 1.25.0
  • Golang1.13.5
  • Fabric-go-sdk f7ae259

前邊的過程中上邊的環境都安裝好了,就差Fabric-go-sdk,下邊就簡單說一下與sdk相關的安裝:

cd $GOPATH/src/github.com/hyperledger
git clone https://github.com/hyperledger/fabric-sdk-go.git
git reset --hard f7ae259 #切換到同一版本的commit下
git log

截圖:
在這裏插入圖片描述

二、網絡環境

2.1、網絡環境準備

Hyperledger Fabric 處理交易時需要大量的證書來確保在整個端到端流程(TSL,身份驗證,簽名塊…)期間進行加密。我們的核心問題是構建chaincode從而搭建基於Fabric的應用,因此對這些我們採取網上開源的或者直接應用示例文件的,有時間的話再研究這個。 主要內容就是在前期cryptogenconfigtxhen工具的輔助和配置文件的內容下生成genesis.block channel.tx 以及各種信息如下圖,這裏不進行重點講解。

2.2、配置docker-compose.yml文件

fixtures 目錄下創建一個 docker-compose.yml 文件並編輯

$ vim docker-compose.yml
  1. network下的basic 修改爲 default

    version: '2'
    
    networks:
      default:
    
    services:
    
  2. 編輯 orderer 部分

      orderer.kevin.kongyixueyuan.com:
        image: hyperledger/fabric-orderer
        container_name: orderer.kevin.kongyixueyuan.com
        environment:
          - ORDERER_GENERAL_LOGLEVEL=debug
          - ORDERER_GENERAL_LISTENADDRESS=0.0.0.0
          - ORDERER_GENERAL_LISTENPORT=7050
          - ORDERER_GENERAL_GENESISPROFILE=kongyixueyuan
          - ORDERER_GENERAL_GENESISMETHOD=file
          - ORDERER_GENERAL_GENESISFILE=/var/hyperledger/orderer/genesis.block
          - ORDERER_GENERAL_LOCALMSPID=kevin.kongyixueyuan.com
          - ORDERER_GENERAL_LOCALMSPDIR=/var/hyperledger/orderer/msp
          - ORDERER_GENERAL_TLS_ENABLED=true
          - ORDERER_GENERAL_TLS_PRIVATEKEY=/var/hyperledger/orderer/tls/server.key
          - ORDERER_GENERAL_TLS_CERTIFICATE=/var/hyperledger/orderer/tls/server.crt
          - ORDERER_GENERAL_TLS_ROOTCAS=[/var/hyperledger/orderer/tls/ca.crt]
        working_dir: /opt/gopath/src/github.com/hyperledger/fabric
        command: orderer
        volumes:
          - ./artifacts/genesis.block:/var/hyperledger/orderer/genesis.block
          - ./crypto-config/ordererOrganizations/kevin.kongyixueyuan.com/orderers/orderer.kevin.kongyixueyuan.com/msp:/var/hyperledger/orderer/msp
          - ./crypto-config/ordererOrganizations/kevin.kongyixueyuan.com/orderers/orderer.kevin.kongyixueyuan.com/tls:/var/hyperledger/orderer/tls
        ports:
          - 7050:7050
        networks:
          default:
            aliases:
              - orderer.kevin.kongyixueyuan.com
    
  3. 編輯 ca 部分

      ca.org1.kevin.kongyixueyuan.com:
        image: hyperledger/fabric-ca
        container_name: ca.org1.kevin.kongyixueyuan.com
        environment:
          - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server
          - FABRIC_CA_SERVER_CA_NAME=ca.org1.kevin.kongyixueyuan.com
          - FABRIC_CA_SERVER_CA_CERTFILE=/etc/hyperledger/fabric-ca-server-config/ca.org1.kevin.kongyixueyuan.com-cert.pem
          - FABRIC_CA_SERVER_CA_KEYFILE=/etc/hyperledger/fabric-ca-server-config/727e69ed4a01a204cd53bf4a97c2c1cb947419504f82851f6ae563c3c96dea3a_sk
          - FABRIC_CA_SERVER_TLS_ENABLED=true
          - FABRIC_CA_SERVER_TLS_CERTFILE=/etc/hyperledger/fabric-ca-server-config/ca.org1.kevin.kongyixueyuan.com-cert.pem
          - FABRIC_CA_SERVER_TLS_KEYFILE=/etc/hyperledger/fabric-ca-server-config/727e69ed4a01a204cd53bf4a97c2c1cb947419504f82851f6ae563c3c96dea3a_sk
        ports:
          - 7054:7054
        command: sh -c 'fabric-ca-server start -b admin:adminpw -d'
        volumes:
          - ./crypto-config/peerOrganizations/org1.kevin.kongyixueyuan.com/ca/:/etc/hyperledger/fabric-ca-server-config
        networks:
          default:
            aliases:
              - ca.org1.kevin.kongyixueyuan.com
    
  4. 編輯Peer部分

    1. peer0.org1.example.com 內容如下

        peer0.org1.kevin.kongyixueyuan.com:
          image: hyperledger/fabric-peer
          container_name: peer0.org1.kevin.kongyixueyuan.com
          environment:
            - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
            - CORE_VM_DOCKER_ATTACHSTDOUT=true
            - CORE_LOGGING_LEVEL=DEBUG
            - CORE_PEER_NETWORKID=kongyixueyuan
            - CORE_PEER_PROFILE_ENABLED=true
            - CORE_PEER_TLS_ENABLED=true
            - CORE_PEER_TLS_CERT_FILE=/var/hyperledger/tls/server.crt
            - CORE_PEER_TLS_KEY_FILE=/var/hyperledger/tls/server.key
            - CORE_PEER_TLS_ROOTCERT_FILE=/var/hyperledger/tls/ca.crt
            - CORE_PEER_ID=peer0.org1.kevin.kongyixueyuan.com
            - CORE_PEER_ADDRESSAUTODETECT=true
            - CORE_PEER_ADDRESS=peer0.org1.kevin.kongyixueyuan.com:7051
            - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org1.kevin.kongyixueyuan.com:7051
            - CORE_PEER_GOSSIP_USELEADERELECTION=true
            - CORE_PEER_GOSSIP_ORGLEADER=false
            - CORE_PEER_GOSSIP_SKIPHANDSHAKE=true
            - CORE_PEER_LOCALMSPID=org1.kevin.kongyixueyuan.com
            - CORE_PEER_MSPCONFIGPATH=/var/hyperledger/msp
            - CORE_PEER_TLS_SERVERHOSTOVERRIDE=peer0.org1.kevin.kongyixueyuan.com
          working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
          command: peer node start
          volumes:
            - /var/run/:/host/var/run/
            - ./crypto-config/peerOrganizations/org1.kevin.kongyixueyuan.com/peers/peer0.org1.kevin.kongyixueyuan.com/msp:/var/hyperledger/msp
            - ./crypto-config/peerOrganizations/org1.kevin.kongyixueyuan.com/peers/peer0.org1.kevin.kongyixueyuan.com/tls:/var/hyperledger/tls
          ports:
            - 7051:7051
            - 7053:7053
          depends_on:
            - orderer.kevin.kongyixueyuan.com
          networks:
            default:
              aliases:
                - peer0.org1.kevin.kongyixueyuan.com
      
    2. peer1.org1.example.com 內容如下

        peer1.org1.kevin.kongyixueyuan.com:
          image: hyperledger/fabric-peer
          container_name: peer1.org1.kevin.kongyixueyuan.com
          environment:
            - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
            - CORE_VM_DOCKER_ATTACHSTDOUT=true
            - CORE_LOGGING_LEVEL=DEBUG
            - CORE_PEER_NETWORKID=kongyixueyuan
            - CORE_PEER_PROFILE_ENABLED=true
            - CORE_PEER_TLS_ENABLED=true
            - CORE_PEER_TLS_CERT_FILE=/var/hyperledger/tls/server.crt
            - CORE_PEER_TLS_KEY_FILE=/var/hyperledger/tls/server.key
            - CORE_PEER_TLS_ROOTCERT_FILE=/var/hyperledger/tls/ca.crt
            - CORE_PEER_ID=peer1.org1.kevin.kongyixueyuan.com
            - CORE_PEER_ADDRESSAUTODETECT=true
            - CORE_PEER_ADDRESS=peer1.org1.kevin.kongyixueyuan.com:7051
            - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer1.org1.kevin.kongyixueyuan.com:7051
            - CORE_PEER_GOSSIP_USELEADERELECTION=true
            - CORE_PEER_GOSSIP_ORGLEADER=false
            - CORE_PEER_GOSSIP_SKIPHANDSHAKE=true
            - CORE_PEER_LOCALMSPID=org1.kevin.kongyixueyuan.com
            - CORE_PEER_MSPCONFIGPATH=/var/hyperledger/msp
            - CORE_PEER_TLS_SERVERHOSTOVERRIDE=peer1.org1.kevin.kongyixueyuan.com
          working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
          command: peer node start
          volumes:
            - /var/run/:/host/var/run/
            - ./crypto-config/peerOrganizations/org1.kevin.kongyixueyuan.com/peers/peer1.org1.kevin.kongyixueyuan.com/msp:/var/hyperledger/msp
            - ./crypto-config/peerOrganizations/org1.kevin.kongyixueyuan.com/peers/peer1.org1.kevin.kongyixueyuan.com/tls:/var/hyperledger/tls
          ports:
            - 7151:7051
            - 7153:7053
          depends_on:
            - orderer.kevin.kongyixueyuan.com
          networks:
            default:
              aliases:
                - peer1.org1.kevin.kongyixueyuan.com
      
  5. 其餘可根據實際情況進行添加

2.3、測試網絡環境

爲了檢查網絡是否正常工作,使用docker-compose同時啓動或停止所有容器。 進入fixtures文件夾,運行:

 docker-compose up

如果在您的系統中沒有相關的容器,那麼會自動下載docker鏡像。下載完畢後自動啓動,控制檯會輸出很多不同顏色的日誌(紅色不等於錯誤)
在這裏插入圖片描述
打開一個新終端並運行:

 $ docker ps -a

在這裏插入圖片描述

將看到:兩個peer,一個orderer和一個CA容器。 代表已成功創建了一個新的網絡,可以隨SDK一起使用。 要停止網絡,請返回到上一個終端,按Ctrl-C並等待所有容器都停止。

最後在終端2中執行如下命令關閉網絡:

docker-compose down

在這裏插入圖片描述

三、 配置Fabric-SDK

3.1、創建config.yaml

確認 Hyperledger Fabric 基礎網絡環境運行沒有問題後,現在我們通過創建一個新的 config.yaml 配置文件給應用程序所使用的 Fabric-SDK-Go 配置相關參數及 Fabric 組件的通信地址

進入項目的根目錄中創建一個 config.yaml 文件並編輯

$ cd $GOPATH/src/github.com/kongyixueyuan.com/kongyixueyuan
$ vim config.yaml

config.yaml 文件完整內容如下:

name: "kongyixueyuan-network"
#
# Schema version of the content. Used by the SDK to apply the corresponding parsing rules.
#
version: 1.0.0

#
# The client section used by GO SDK.
#
client:

  # Which organization does this application instance belong to? The value must be the name of an org
  # defined under "organizations"
  organization: Org1

  logging:
    level: info

  # Global configuration for peer, event service and orderer timeouts
  # if this this section is omitted, then default values will be used (same values as below)
#  peer:
#    timeout:
#      connection: 10s
#      response: 180s
#      discovery:
#        # Expiry period for discovery service greylist filter
#        # The channel client will greylist peers that are found to be offline
#        # to prevent re-selecting them in subsequent retries.
#        # This interval will define how long a peer is greylisted
#        greylistExpiry: 10s
#  eventService:
#    # Event service type (optional). If not specified then the type is automatically
#    # determined from channel capabilities.
#    type: (deliver|eventhub)
    # the below timeouts are commented out to use the default values that are found in
    # "pkg/fab/endpointconfig.go"
    # the client is free to override the default values by uncommenting and resetting
    # the values as they see fit in their config file
#    timeout:
#      connection: 15s
#      registrationResponse: 15s
#  orderer:
#    timeout:
#      connection: 15s
#      response: 15s
#  global:
#    timeout:
#      query: 180s
#      execute: 180s
#      resmgmt: 180s
#    cache:
#      connectionIdle: 30s
#      eventServiceIdle: 2m
#      channelConfig: 30m
#      channelMembership: 30s
#      discovery: 10s
#      selection: 10m

  # Root of the MSP directories with keys and certs.
  cryptoconfig:
    path: ${GOPATH}/src/github.com/kongyixueyuan.com/kongyixueyuan/fixtures/crypto-config

  # Some SDKs support pluggable KV stores, the properties under "credentialStore"
  # are implementation specific
  credentialStore:
    path: /tmp/kongyixueyuan-store

    # [Optional]. Specific to the CryptoSuite implementation used by GO SDK. Software-based implementations
    # requiring a key store. PKCS#11 based implementations does not.
    cryptoStore:
      path: /tmp/kongyixueyuan-msp

   # BCCSP config for the client. Used by GO SDK.
  BCCSP:
    security:
     enabled: true
     default:
      provider: "SW"
     hashAlgorithm: "SHA2"
     softVerify: true
     level: 256

  tlsCerts:
    # [Optional]. Use system certificate pool when connecting to peers, orderers (for negotiating TLS) Default: false
    systemCertPool: false

    # [Optional]. Client key and cert for TLS handshake with peers and orderers
    client:
      key:
        path:
      cert:
        path:

#
# [Optional]. But most apps would have this section so that channel objects can be constructed
# based on the content below. If an app is creating channels, then it likely will not need this
# section.
#
channels:
  # name of the channel
  kevinkongyixueyuan:
    # Required. list of orderers designated by the application to use for transactions on this
    # channel. This list can be a result of access control ("org1" can only access "ordererA"), or
    # operational decisions to share loads from applications among the orderers.  The values must
    # be "names" of orgs defined under "organizations/peers"
    # deprecated: not recommended, to override any orderer configuration items, entity matchers should be used.
    # orderers:
    #  - orderer.kevin.kongyixueyuan.com

    # Required. list of peers from participating orgs
    peers:
      peer0.org1.kevin.kongyixueyuan.com:
        # [Optional]. will this peer be sent transaction proposals for endorsement? The peer must
        # have the chaincode installed. The app can also use this property to decide which peers
        # to send the chaincode install request. Default: true
        endorsingPeer: true

        # [Optional]. will this peer be sent query proposals? The peer must have the chaincode
        # installed. The app can also use this property to decide which peers to send the
        # chaincode install request. Default: true
        chaincodeQuery: true

        # [Optional]. will this peer be sent query proposals that do not require chaincodes, like
        # queryBlock(), queryTransaction(), etc. Default: true
        ledgerQuery: true

        # [Optional]. will this peer be the target of the SDK's listener registration? All peers can
        # produce events but the app typically only needs to connect to one to listen to events.
        # Default: true
        eventSource: true

      peer1.org1.kevin.kongyixueyuan.com:
        endorsingPeer: true
        chaincodeQuery: true
        ledgerQuery: true
        eventSource: true

    policies:
      #[Optional] options for retrieving channel configuration blocks
      queryChannelConfig:
        #[Optional] min number of success responses (from targets/peers)
        minResponses: 1
        #[Optional] channel config will be retrieved for these number of random targets
        maxTargets: 1
        #[Optional] retry options for query config block
        retryOpts:
          #[Optional] number of retry attempts
          attempts: 5
          #[Optional] the back off interval for the first retry attempt
          initialBackoff: 500ms
          #[Optional] the maximum back off interval for any retry attempt
          maxBackoff: 5s
          #[Optional] he factor by which the initial back off period is exponentially incremented
          backoffFactor: 2.0
      #[Optional] options for retrieving discovery info
      discovery:
        #[Optional] discovery info will be retrieved for these number of random targets
        maxTargets: 2
        #[Optional] retry options for retrieving discovery info
        retryOpts:
          #[Optional] number of retry attempts
          attempts: 4
          #[Optional] the back off interval for the first retry attempt
          initialBackoff: 500ms
          #[Optional] the maximum back off interval for any retry attempt
          maxBackoff: 5s
          #[Optional] he factor by which the initial back off period is exponentially incremented
          backoffFactor: 2.0
      #[Optional] options for the event service
      eventService:
        # [Optional] resolverStrategy specifies the peer resolver strategy to use when connecting to a peer
        # Possible values: [PreferOrg (default), MinBlockHeight, Balanced]
        #
        # PreferOrg:
        #   Determines which peers are suitable based on block height lag threshold, although will prefer the peers in the
        #   current org (as long as their block height is above a configured threshold). If none of the peers from the current org
        #   are suitable then a peer from another org is chosen.
        # MinBlockHeight:
        #   Chooses the best peer according to a block height lag threshold. The maximum block height of all peers is
        #   determined and the peers whose block heights are under the maximum height but above a provided "lag" threshold are load
        #   balanced. The other peers are not considered.
        # Balanced:
        #   Chooses peers using the configured balancer.
        resolverStrategy: PreferOrg
        # [Optional] balancer is the balancer to use when choosing a peer to connect to
        # Possible values: [Random (default), RoundRobin]
        balancer: Random
        # [Optional] blockHeightLagThreshold sets the block height lag threshold. This value is used for choosing a peer
        # to connect to. If a peer is lagging behind the most up-to-date peer by more than the given number of
        # blocks then it will be excluded from selection.
        # If set to 0 then only the most up-to-date peers are considered.
        # If set to -1 then all peers (regardless of block height) are considered for selection.
        # Default: 5
        blockHeightLagThreshold: 5
        # [Optional] reconnectBlockHeightLagThreshold - if >0 then the event client will disconnect from the peer if the peer's
        # block height falls behind the specified number of blocks and will reconnect to a better performing peer.
        # If set to 0 then this feature is disabled.
        # Default: 10
        # NOTES:
        #   - peerMonitorPeriod must be >0 to enable this feature
        #   - Setting this value too low may cause the event client to disconnect/reconnect too frequently, thereby
        #     affecting performance.
        reconnectBlockHeightLagThreshold: 10
        # [Optional] peerMonitorPeriod is the period in which the connected peer is monitored to see if
        # the event client should disconnect from it and reconnect to another peer.
        # Default: 0 (disabled)
        peerMonitorPeriod: 5s

#
# list of participating organizations in this network
#
organizations:
  Org1:
    mspid: org1.kevin.kongyixueyuan.com
    cryptoPath: peerOrganizations/org1.kevin.kongyixueyuan.com/users/{userName}@org1.kevin.kongyixueyuan.com/msp
    peers:
      - peer0.org1.kevin.kongyixueyuan.com
      - peer1.org1.kevin.kongyixueyuan.com

    # [Optional]. Certificate Authorities issue certificates for identification purposes in a Fabric based
    # network. Typically certificates provisioning is done in a separate process outside of the
    # runtime network. Fabric-CA is a special certificate authority that provides a REST APIs for
    # dynamic certificate management (enroll, revoke, re-enroll). The following section is only for
    # Fabric-CA servers.
    certificateAuthorities:
      - ca.org1.kevin.kongyixueyuan.com

#
# List of orderers to send transaction and channel create/update requests to. For the time
# being only one orderer is needed. If more than one is defined, which one get used by the
# SDK is implementation specific. Consult each SDK's documentation for its handling of orderers.
#
orderers:
  orderer.kevin.kongyixueyuan.com:
    url: localhost:7050

    # these are standard properties defined by the gRPC library
    # they will be passed in as-is to gRPC client constructor
    grpcOptions:
      ssl-target-name-override: orderer.kevin.kongyixueyuan.com
      # These parameters should be set in coordination with the keepalive policy on the server,
      # as incompatible settings can result in closing of connection.
      # When duration of the 'keep-alive-time' is set to 0 or less the keep alive client parameters are disabled
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
      # allow-insecure will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
      allow-insecure: false

    tlsCACerts:
      # Certificate location absolute path
      path: ${GOPATH}/src/github.com/kongyixueyuan.com/kongyixueyuan/fixtures/crypto-config/ordererOrganizations/kevin.kongyixueyuan.com/tlsca/tlsca.kevin.kongyixueyuan.com-cert.pem

#
# List of peers to send various requests to, including endorsement, query
# and event listener registration.
#
peers:
  peer0.org1.kevin.kongyixueyuan.com:
    # this URL is used to send endorsement and query requests
    url: localhost:7051
    # eventUrl is only needed when using eventhub (default is delivery service)
    eventUrl: localhost:7053

    grpcOptions:
      ssl-target-name-override: peer0.org1.kevin.kongyixueyuan.com
      # These parameters should be set in coordination with the keepalive policy on the server,
      # as incompatible settings can result in closing of connection.
      # When duration of the 'keep-alive-time' is set to 0 or less the keep alive client parameters are disabled
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
      # allow-insecure will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
      allow-insecure: false

    tlsCACerts:
      # Certificate location absolute path
      path: ${GOPATH}/src/github.com/kongyixueyuan.com/kongyixueyuan/fixtures/crypto-config/peerOrganizations/org1.kevin.kongyixueyuan.com/tlsca/tlsca.org1.kevin.kongyixueyuan.com-cert.pem

  peer1.org1.kevin.kongyixueyuan.com:
    # this URL is used to send endorsement and query requests
    url: localhost:7151
    # eventUrl is only needed when using eventhub (default is delivery service)
    eventUrl: localhost:7153

    grpcOptions:
      ssl-target-name-override: peer1.org1.kevin.kongyixueyuan.com
      # These parameters should be set in coordination with the keepalive policy on the server,
      # as incompatible settings can result in closing of connection.
      # When duration of the 'keep-alive-time' is set to 0 or less the keep alive client parameters are disabled
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
      # allow-insecure will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
      allow-insecure: false

    tlsCACerts:
      # Certificate location absolute path
      path: ${GOPATH}/src/github.com/kongyixueyuan.com/kongyixueyuan/fixtures/crypto-config/peerOrganizations/org1.kevin.kongyixueyuan.com/tlsca/tlsca.org1.kevin.kongyixueyuan.com-cert.pem

#
# Fabric-CA is a special kind of Certificate Authority provided by Hyperledger Fabric which allows
# certificate management to be done via REST APIs. Application may choose to use a standard
# Certificate Authority instead of Fabric-CA, in which case this section would not be specified.
#
certificateAuthorities:
  ca.org1.kevin.kongyixueyuan.com:
    url: http://localhost:7054
    tlsCACerts:
      # Certificate location absolute path
      path: ${GOPATH}/src/github.com/kongyixueyuan.com/kongyixueyuan/fixtures/crypto-config/peerOrganizations/org1.kevin.kongyixueyuan.com/ca/ca.org1.kevin.kongyixueyuan.com-cert.pem

    # Fabric-CA supports dynamic user enrollment via REST APIs. A "root" user, a.k.a registrar, is
    # needed to enroll and invoke new users.
    registrar:
      enrollId: admin
      enrollSecret: adminpw
    # [Optional] The optional name of the CA.
    caName: ca.org1.kevin.kongyixueyuan.com

entityMatchers:
  peer:
    - pattern: (\w*)peer0.org1.kevin.kongyixueyuan.com(\w*)
      urlSubstitutionExp: localhost:7051
      eventUrlSubstitutionExp: localhost:7053
      sslTargetOverrideUrlSubstitutionExp: peer0.org1.kevin.kongyixueyuan.com
      mappedHost: peer0.org1.kevin.kongyixueyuan.com

    - pattern: (\w*)peer1.org1.kevin.kongyixueyuan.com(\w*)
      urlSubstitutionExp: localhost:7151
      eventUrlSubstitutionExp: localhost:7153
      sslTargetOverrideUrlSubstitutionExp: peer1.org1.kevin.kongyixueyuan.com
      mappedHost: peer1.org1.kevin.kongyixueyuan.com

  orderer:
    - pattern: (\w*)orderer.kevin.kongyixueyuan.com(\w*)
      urlSubstitutionExp: localhost:7050
      sslTargetOverrideUrlSubstitutionExp: orderer.kevin.kongyixueyuan.com
      mappedHost: orderer.kevin.kongyixueyuan.com

  certificateAuthorities:
    - pattern: (\w*)ca.org1.kevin.kongyixueyuan.com(\w*)
      urlSubstitutionExp: http://localhost:7054
      mappedHost: ca.org1.kevin.kongyixueyuan.com

3.2、定義所需結構體

配置文件完成指定的配置信息之後,我們開始編寫代碼。

在項目的根目錄下添加一個名爲 sdkInit 的新目錄,我們將在這個文件夾中創建 SDK,並根據配置信息創建應用通道

$ mkdir sdkInit

爲了方便管理 Hyperledger Fabric 網絡環境,我們將在 sdkInit 目錄中創建一個 fabricInitInfo.go 的源代碼文件,用於定義一個結構體,包括 Fabric SDK 所需的各項相關信息

$ vim sdkInit/fabricInitInfo.go 

fabricInitInfo.go 源代碼如下:

package sdkInit

import (
	"github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt"
)

type InitInfo struct {
	ChannelID     string
	ChannelConfig string
	OrgAdmin      string
	OrgName       string
	OrdererOrgName	string
	OrgResMgmt *resmgmt.Client
}

3.3、創建SDK

sdkInit 目錄下新創建一個名爲 start.go 的go文件利用 vim 編輯器進行編輯:

$ vim sdkInit/start.go 
package sdkInit

import (
	"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
	"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
	"fmt"
	"github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt"
	mspclient "github.com/hyperledger/fabric-sdk-go/pkg/client/msp"
	"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/msp"
	"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry"

)


const ChaincodeVersion  = "1.0"


func SetupSDK(ConfigFile string, initialized bool) (*fabsdk.FabricSDK, error) {

	if initialized {
		return nil, fmt.Errorf("Fabric SDK已被實例化")
	}

	sdk, err := fabsdk.New(config.FromFile(ConfigFile))
	if err != nil {
		return nil, fmt.Errorf("實例化Fabric SDK失敗: %v", err)
	}

	fmt.Println("Fabric SDK初始化成功")
	return sdk, nil
}

func CreateChannel(sdk *fabsdk.FabricSDK, info *InitInfo) error {

	clientContext := sdk.Context(fabsdk.WithUser(info.OrgAdmin), fabsdk.WithOrg(info.OrgName))
	if clientContext == nil {
		return fmt.Errorf("根據指定的組織名稱與管理員創建資源管理客戶端Context失敗")
	}

	// New returns a resource management client instance.
	resMgmtClient, err := resmgmt.New(clientContext)
	if err != nil {
		return fmt.Errorf("根據指定的資源管理客戶端Context創建通道管理客戶端失敗: %v", err)
	}

	// New creates a new Client instance
	mspClient, err := mspclient.New(sdk.Context(), mspclient.WithOrg(info.OrgName))
	if err != nil {
		return fmt.Errorf("根據指定的 OrgName 創建 Org MSP 客戶端實例失敗: %v", err)
	}

	//  Returns: signing identity
	adminIdentity, err := mspClient.GetSigningIdentity(info.OrgAdmin)
	if err != nil {
		return fmt.Errorf("獲取指定id的簽名標識失敗: %v", err)
	}

	// SaveChannelRequest holds parameters for save channel request
	channelReq := resmgmt.SaveChannelRequest{ChannelID:info.ChannelID, ChannelConfigPath:info.ChannelConfig, SigningIdentities:[]msp.SigningIdentity{adminIdentity}}
	// save channel response with transaction ID
	 _, err = resMgmtClient.SaveChannel(channelReq, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint(info.OrdererOrgName))
	if err != nil {
		return fmt.Errorf("創建應用通道失敗: %v", err)
	}

	fmt.Println("通道已成功創建,")

	info.OrgResMgmt = resMgmtClient

	// allows for peers to join existing channel with optional custom options (specific peers, filtered peers). If peer(s) are not specified in options it will default to all peers that belong to client's MSP.
	err = info.OrgResMgmt.JoinChannel(info.ChannelID, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint(info.OrdererOrgName))
	if err != nil {
		return fmt.Errorf("Peers加入通道失敗: %v", err)
	}

	fmt.Println("peers 已成功加入通道.")
	return nil
}

在這個階段,我們只初始化一個客戶端,它將與 peer,CA 和 orderer進行通信。 還創建了一個指定的應用通道, 並將 Peer 節點加入到此通道中

3.4、編寫測試代碼

爲了確保客戶端能夠初始化所有組件,將在啓動網絡的情況下進行簡單的測試。 爲了做到這一點,我們需要編寫 Go 代碼,在項目根目錄下新創建一個 main.go 的主文件並編輯內容

$ cd $GOPATH/src/github.com/kongyixueyuan.com/kongyixueyuan
$ vim main.go

main.go 文件完整源代碼如下:

package main

import (
	"os"
	"fmt"
	"github.com/kongyixueyuan.com/kongyixueyuan/sdkInit"
)

const (
	configFile = "config.yaml"
	initialized = false
	SimpleCC = "simplecc"
)

func main() {

	initInfo := &sdkInit.InitInfo{

		ChannelID: "kevinkongyixueyuan",
		ChannelConfig: os.Getenv("GOPATH") + "/src/github.com/kongyixueyuan.com/kongyixueyuan/fixtures/artifacts/channel.tx",

		OrgAdmin:"Admin",
		OrgName:"Org1",
		OrdererOrgName: "orderer.kevin.kongyixueyuan.com",

	}

	sdk, err := sdkInit.SetupSDK(configFile, initialized)
	if err != nil {
		fmt.Printf(err.Error())
		return
	}

	defer sdk.Close()

	err = sdkInit.CreateChannel(sdk, initInfo)
	if err != nil {
		fmt.Println(err.Error())
		return
	}

}

四、 滿足依賴

4.1、 安裝dep工具

在運行應用程序之前,需要將 Go 源代碼時行編譯,但在開始編譯之前,我們需要使用一個 vendor 目錄來包含應用中所需的所有的依賴關係。 在我們的GOPATH中,我們有Fabric SDK Go和其他項目。 在嘗試編譯應用程序時,Golang 會在 GOPATH 中搜索依賴項,但首先會檢查項目中是否存在vendor 文件夾。 如果依賴性得到滿足,那麼 Golang 就不會去檢查 GOPATH 或 GOROOT。 這在使用幾個不同版本的依賴關係時非常有用(可能會發生一些衝突,比如在例子中有多個BCCSP定義,通過使用像dep這樣的工具在vendor目錄中來處理這些依賴關係。

將如下環境變量設置到用戶的環境文件中(.bashrc)中

$ vim ~/.bashrc

export PATH=$PATH:$GOPATH/bin

執行 source 命令

$ source ~/.bashrc

安裝 dep 工具

$ go get -u github.com/golang/dep/cmd/dep

4.2、下載所需依賴

dep 工具安裝好之後我們來安裝應用所需要的依賴

使用 dep 命令需要一個名爲 Gopkg.toml 的配置文件指定依賴信息

創建一個名爲Gopkg.toml的文件並將其複製到裏面:

$ vim Gopkg.toml
ignored = ["github.com/kongyixueyuan.com/kongyixueyuan/chaincode"]

[[constraint]]
  # Release v1.0.0-alpha4
  name = "github.com/hyperledger/fabric-sdk-go"
  revision = "768766943cc713f3ffcc439be3d4c99d4f72c6c5"

使用dep限制在 vendor 中指定希望SDK的特定版本。

保存該文件,然後執行 dep ensure 命令,該命令會自動將項目所需的依賴下載至當前的 vendor 目錄中(下載依賴可能需要一段時間):

$ dep ensure

提醒:dep ensure 命令執行由於時間比較長甚至會失敗(網絡原因),所以執行一次後即可,在後面的Makefile中可註釋@dep ensure命令。

4.3、測試Fabric-SDK

所在依賴下載安裝完成後,我們就可以進行測試

首先啓動網絡:

$ cd fixtures
$ docker-compose up -d

然後編譯並運行:

$ cd ..
$ go build
$ ./kongyixueyuan

命令執行後輸出結果如下圖所示:
在這裏插入圖片描述

如果出現上圖的輸出結果,則說明執行成功,否則需要根據出現的錯誤提示進行相應的處理。

4.4、 利用Makefile

Fabric SDK生成一些文件,如證書,二進制文件和臨時文件。 關閉網絡不會完全清理環境,當需要重新啓動時,這些文件將被使用以避免重複的構建過程。 對於開發,可以快速測試,但對於真正的測試環境,需要清理所有內容並從頭開始。爲提高效率我們將上述流程寫成Makefile。

Makefile 文件完整內容如下:

.PHONY: all dev clean build env-up env-down run

all: clean build env-up run

dev: build run

##### BUILD
build:
	@echo "Build ..."
	@dep ensure
	@go build
	@echo "Build done"

##### ENV
env-up:
	@echo "Start environment ..."
	@cd fixtures && docker-compose up --force-recreate -d
	@echo "Environment up"

env-down:
	@echo "Stop environment ..."
	@cd fixtures && docker-compose down
	@echo "Environment down"

##### RUN
run:
	@echo "Start app ..."
	@./kongyixueyuan

##### CLEAN
clean: env-down
	@echo "Clean up ..."
	@rm -rf /tmp/kongyixueyuan-* kongyixueyuan
	@docker rm -f -v `docker ps -a --no-trunc | grep "kongyixueyuan" | cut -d ' ' -f 1` 2>/dev/null || true
	@docker rmi `docker images --no-trunc | grep "kongyixueyuan" | cut -d ' ' -f 1` 2>/dev/null || true
	@echo "Clean up done"

現在完成任務:

  1. 整個環境將被清理乾淨,
  2. go程序將被編譯,
  3. 之後將部署網絡
  4. 最後該應用程序將啓動並運行。

要使用它,請進入項目的根目錄並使用make命令:

  • 任務allmakemake all
  • 任務clean :清理一切並釋放網絡( make clean
  • 任務build :只需構建應用程序( make build
  • 任務env-up :只需建立網絡( make env-up

五、鏈碼開發

爲了便於測試及簡化代碼,我們實現一個簡單的鏈碼功能,能夠實現對分類賬本中的數據進行設置(PutState(k,v))及相應的查詢(GetState(k))功能即可。

編寫鏈碼

創建一個存放鏈碼文件的 chaincode 目錄,然後在該目錄下創建一個 main.go 的文件並對其進行編輯

$ mkdir chaincode
$ vim chaincode/main.go

main.go 文件內容如下:

package main

import (
	"github.com/hyperledger/fabric/core/chaincode/shim"
	"github.com/hyperledger/fabric/protos/peer"
	"fmt"
)

type SimpleChaincode struct {

} 

func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response{

	return shim.Success(nil)
}

func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response{
	fun, args := stub.GetFunctionAndParameters()

	var result string
	var err error
	if fun == "set"{
		result, err = set(stub, args)
	}else{
		result, err = get(stub, args)
	}
	if err != nil{
		return shim.Error(err.Error())
	}
	return shim.Success([]byte(result))
}

func set(stub shim.ChaincodeStubInterface, args []string)(string, error){

	if len(args) != 3{
		return "", fmt.Errorf("給定的參數錯誤")
	}

	err := stub.PutState(args[0], []byte(args[1]))
	if err != nil{
		return "", fmt.Errorf(err.Error())
	}

	err = stub.SetEvent(args[2], []byte{})
	if err != nil {
		return "", fmt.Errorf(err.Error())
	}

	return string(args[0]), nil

}

func get(stub shim.ChaincodeStubInterface, args []string)(string, error){
	if len(args) != 1{
		return "", fmt.Errorf("給定的參數錯誤")
	}
	result, err := stub.GetState(args[0])
	if err != nil{
		return "", fmt.Errorf("獲取數據發生錯誤")
	}
	if result == nil{
		return "", fmt.Errorf("根據 %s 沒有獲取到相應的數據", args[0])
	}
	return string(result), nil

}

func main(){
	err := shim.Start(new(SimpleChaincode))
	if err != nil{
		fmt.Printf("啓動SimpleChaincode時發生錯誤: %s", err)
	}
}

鏈碼編寫好以後,我們需要使用 Fabric-SDK-Go 提供的相關 API 來實現對鏈碼的安裝及實例化操作,而無需在命令提示符中輸入煩鎖的相關操作命令。

六、鏈碼安裝及實例化

6.1、聲明結構體

新建一個結構體,聲明在 sdkInit/fabricInitInfo.go 文件中

$ vim sdkInit/fabricInitInfo.go

fabricInitInfo.go 文件完整內容如下:

package sdkInit

import (
	"github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt"
)

type InitInfo struct {
	ChannelID     string
	ChannelConfig string
	OrgAdmin      string
	OrgName       string
	OrdererOrgName	string
	OrgResMgmt *resmgmt.Client

	ChaincodeID	string
	ChaincodeGoPath	string
	ChaincodePath	string
	UserName	string
}

6.2、 使用Fabric-SDK安裝及實例化鏈碼

編輯 sdkInit/start.go 文件,利用Fabric-SDK提供的接口,對鏈碼進行安裝及實例化

$ vim sdkInit/start.go

start.go 文件中添加如下內容

import (
    [......]
    
	"github.com/hyperledger/fabric-sdk-go/pkg/fab/ccpackager/gopackager"
	"github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/common/cauthdsl"
	"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
)

func InstallAndInstantiateCC(sdk *fabsdk.FabricSDK, info *InitInfo) (*channel.Client, error) {
	fmt.Println("開始安裝鏈碼......")
	// creates new go lang chaincode package
	ccPkg, err := gopackager.NewCCPackage(info.ChaincodePath, info.ChaincodeGoPath)
	if err != nil {
		return nil, fmt.Errorf("創建鏈碼包失敗: %v", err)
	}

	// contains install chaincode request parameters
	installCCReq := resmgmt.InstallCCRequest{Name: info.ChaincodeID, Path: info.ChaincodePath, Version: ChaincodeVersion, Package: ccPkg}
	// allows administrators to install chaincode onto the filesystem of a peer
	_, err = info.OrgResMgmt.InstallCC(installCCReq, resmgmt.WithRetry(retry.DefaultResMgmtOpts))
	if err != nil {
		return nil, fmt.Errorf("安裝鏈碼失敗: %v", err)
	}

	fmt.Println("指定的鏈碼安裝成功")
	fmt.Println("開始實例化鏈碼......")

	//  returns a policy that requires one valid
	ccPolicy := cauthdsl.SignedByAnyMember([]string{"org1.kevin.kongyixueyuan.com"})

	instantiateCCReq := resmgmt.InstantiateCCRequest{Name: info.ChaincodeID, Path: info.ChaincodePath, Version: ChaincodeVersion, Args: [][]byte{[]byte("init")}, Policy: ccPolicy}
	// instantiates chaincode with optional custom options (specific peers, filtered peers, timeout). If peer(s) are not specified
	_, err = info.OrgResMgmt.InstantiateCC(info.ChannelID, instantiateCCReq, resmgmt.WithRetry(retry.DefaultResMgmtOpts))
	if err != nil {
		return nil, fmt.Errorf("實例化鏈碼失敗: %v", err)
	}

	fmt.Println("鏈碼實例化成功")

	clientChannelContext := sdk.ChannelContext(info.ChannelID, fabsdk.WithUser(info.UserName), fabsdk.WithOrg(info.OrgName))
	// returns a Client instance. Channel client can query chaincode, execute chaincode and register/unregister for chaincode events on specific channel.
	channelClient, err := channel.New(clientChannelContext)
	if err != nil {
		return nil, fmt.Errorf("創建應用通道客戶端失敗: %v", err)
	}

	fmt.Println("通道客戶端創建成功,可以利用此客戶端調用鏈碼進行查詢或執行事務.")

	return channelClient, nil
}

6.3、 在main中調用

編輯 main.go 文件

$ vim main.go

main.go 完整內容如下:

package main

import (
	"os"
	"fmt"
	"github.com/kongyixueyuan.com/kongyixueyuan/sdkInit"
)

const (
	configFile = "config.yaml"
	initialized = false
	SimpleCC = "simplecc"
)

func main() {

	initInfo := &sdkInit.InitInfo{

		ChannelID: "kevinkongyixueyuan",
		ChannelConfig: os.Getenv("GOPATH") + "/src/github.com/kongyixueyuan.com/kongyixueyuan/fixtures/artifacts/channel.tx",

		OrgAdmin:"Admin",
		OrgName:"Org1",
		OrdererOrgName: "orderer.kevin.kongyixueyuan.com",

		ChaincodeID: SimpleCC,
		ChaincodeGoPath: os.Getenv("GOPATH"),
		ChaincodePath: "github.com/kongyixueyuan.com/kongyixueyuan/chaincode/",
		UserName:"User1",
	}

	sdk, err := sdkInit.SetupSDK(configFile, initialized)
	if err != nil {
		fmt.Printf(err.Error())
		return
	}

	defer sdk.Close()

	err = sdkInit.CreateChannel(sdk, initInfo)
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	channelClient, err := sdkInit.InstallAndInstantiateCC(sdk, initInfo)
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	fmt.Println(channelClient)

}

6.4、測試

執行 make 命令

$ make

輸出如下:
在這裏插入圖片描述
在此,我們已經成功搭建了Fabric的網絡環境,並通過 fabric-sdk-go 創建了應用通道,將peer加入通道,在peer上安裝並實例化了鏈碼。那麼如何在真正的應用程序中實現鏈碼的調用,對分類賬本中的狀態進行操作,fabric-sdk 不僅提供了相應的強大功能,而且還給開發人員設計提供了相應的API 接口,以方便開發人員隨時調用。做爲開發設計人員,我們不僅要考慮用戶操作的方便性及可交互性,還需要考慮應用程序後期的可擴展性及維護性,爲此我們將爲應用增加一個業務層,所有的客戶請求都由業務層發送給鏈碼,通過對鏈碼的調用,進而實現對分類賬本狀態的操作。

七、在業務層調用鏈碼

7.1、 事件處理

在項目根目錄下創建一個 service 目錄作爲業務層,在業務層中,我們使用 Fabric-SDK-Go 提供的接口對象調用相應的 API 以實現對鏈碼的訪問,最終實現對分類賬本中的狀態進行操作。

$ cd $GOPATH/src/github.com/kongyixueyuan.com/kongyixueyuan
$ mkdir service

service 目錄下創建 domain.go 文件並進行編輯, 聲明一個結構體及對事件相關而封裝的源代碼

$ vim service/domain.go

domain.go 文件完整內容如下:

package service

import (
	"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
	"fmt"
	"time"
	"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab"
)

type ServiceSetup struct {
	ChaincodeID	string
	Client	*channel.Client
}

func regitserEvent(client *channel.Client, chaincodeID, eventID string) (fab.Registration, <-chan *fab.CCEvent) {

	reg, notifier, err := client.RegisterChaincodeEvent(chaincodeID, eventID)
	if err != nil {
		fmt.Println("註冊鏈碼事件失敗: %s", err)
	}
	return reg, notifier
}

func eventResult(notifier <-chan *fab.CCEvent, eventID string) error {
	select {
	case ccEvent := <-notifier:
		fmt.Printf("接收到鏈碼事件: %v\n", ccEvent)
	case <-time.After(time.Second * 20):
		return fmt.Errorf("不能根據指定的事件ID接收到相應的鏈碼事件(%s)", eventID)
	}
	return nil
}

7.2、 調用鏈碼添加狀態

service 目錄下創建 SimpleService.go 文件

$ vim service/SimpleService.go

SimpleService.go 文件中編寫內容如下,通過一個 SetInfo 函數實現鏈碼的調用,向分類賬本中添加狀態的功能:

package service

import (
	"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
)

func (t *ServiceSetup) SetInfo(name, num string) (string, error) {

	eventID := "eventSetInfo"
	reg, notifier := regitserEvent(t.Client, t.ChaincodeID, eventID)
	defer t.Client.UnregisterChaincodeEvent(reg)

	req := channel.Request{ChaincodeID: t.ChaincodeID, Fcn: "set", Args: [][]byte{[]byte(name), []byte(num), []byte(eventID)}}
	respone, err := t.Client.Execute(req)
	if err != nil {
		return "", err
	}

	err = eventResult(notifier, eventID)
	if err != nil {
		return "", err
	}

	return string(respone.TransactionID), nil
}

測試添加狀態

編輯 main.go 文件

$ vim main.go

main.go 中創建一個對象,並調用 SetInfo 函數,內容如下:

package main

import (
	[......]
	"github.com/kongyixueyuan.com/kongyixueyuan/service"
)

[......]
	//===========================================//

	serviceSetup := service.ServiceSetup{
		ChaincodeID:SimpleCC,
		Client:channelClient,
	}

	msg, err := serviceSetup.SetInfo("hanxiaodong", "kongyixueyuan")
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(msg)
	}

	//===========================================//

}

執行 make 命令運行應用程序

$ make

執行後如下圖所示:
在這裏插入圖片描述

7.3、 調用鏈碼查詢狀態

通過上面的 setInfo(name, num string) 函數,實現了向分類賬本中添加狀態,那麼我們還需要實現從該分類賬本中根據指定的 key 查詢出相應的狀態,編輯 service/SimpleService.go 文件,向該文件中添加實現查詢狀態的相應代碼。

$ vim service/SimpleService.go

定義一個 GetInfo 函數,接收一個字符串類型的參數,該函數實現通過調用鏈碼而查詢狀態的功能,該函數完整代碼如下:

[......]

func (t *ServiceSetup) GetInfo(name string) (string, error){

	req := channel.Request{ChaincodeID: t.ChaincodeID, Fcn: "get", Args: [][]byte{[]byte(name)}}
	respone, err := t.Client.Query(req)
	if err != nil {
		return "", err
	}

	return string(respone.Payload), nil
}

測試查詢狀態

編輯 main.go 文件

$ vim main.go

main.go 文件中添加調用代碼如下內容:

[......]
	
	msg, err = serviceSetup.GetInfo("hanxiaodong")
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(msg)
	}

	//===========================================//

}

執行 make 命令運行應用程序

$ make

執行後如下圖所示:
在這裏插入圖片描述

八、實現Web應用

8.1、目錄結構

爲了讓普通用戶可以方便地使用應用程序,選擇開發成爲一個Web應用,新建web目錄,包含三個其他目錄的目錄。將使用 MVC(Model(模型)-View(視圖) - Controller(控制器))模式使其更具可讀性及擴展性、維護性。模型將是區塊鏈部分,視圖是模板,控制器由controllers目錄中的功能提供。具體目錄結構如下圖所示:
在這裏插入圖片描述
web/controller 目錄

controller/controllerHandler.go : 用於接收並處理各種客戶端請求的源代碼文件

package controller

import (
	"net/http"
	"github.com/kongyixueyuan.com/kongyixueyuan/service"
)

type Application struct {
	Fabric *service.ServiceSetup
}

func (app *Application) IndexView(w http.ResponseWriter, r *http.Request){
	showView(w, r, "index.html", nil)
}

func (app *Application) SetInfoView(w http.ResponseWriter, r *http.Request)  {
	showView(w, r, "setInfo.html", nil)
}

// 根據指定的 key 設置/修改 value 信息
func (app *Application) SetInfo(w http.ResponseWriter, r *http.Request)  {
	// 獲取提交數據
	name := r.FormValue("name")
	num := r.FormValue("num")

	// 調用業務層, 反序列化
	transactionID, err := app.Fabric.SetInfo(name, num)

	// 封裝響應數據
	data := &struct {
		Flag bool
		Msg string
	}{
		Flag:true,
		Msg:"",
	}
	if err != nil {
		data.Msg = err.Error()
	}else{
		data.Msg = "操作成功,交易ID: " + transactionID
	}

	// 響應客戶端
	showView(w, r, "setInfo.html", data)
}

// 根據指定的 Key 查詢信息
func (app *Application) QueryInfo(w http.ResponseWriter, r *http.Request)  {
	// 獲取提交數據
	name := r.FormValue("name")

	// 調用業務層, 反序列化
	msg, err := app.Fabric.GetInfo(name)

	// 封裝響應數據
	data := &struct {
		Msg string
	}{
		Msg:"",
	}
	if err != nil {
		data.Msg = "沒有查詢到Jack對應的信息"
	}else{
		data.Msg = "查詢成功: " + msg
	}
	// 響應客戶端
	showView(w, r, "queryReq.html", data)
}

controller/controllerResponse:用於編寫響應客戶端請求的源代碼文件

package controller

import (
	"net/http"
	"path/filepath"
	"html/template"
	"fmt"
)

func showView(w http.ResponseWriter, r *http.Request, templateName string, data interface{})  {
	page := filepath.Join("web", "tpl", templateName)

	// 創建模板實例
	resultTemplate, err := template.ParseFiles(page)
	if err != nil {
		fmt.Println("創建模板實例錯誤: ", err)
		return
	}

	// 融合數據
	err = resultTemplate.Execute(w, data)
	if err != nil {
		fmt.Println("融合模板數據時發生錯誤", err)
		return
	}
}

web/static目錄下包括三個子目錄,分別爲:

web/static/css :用於存放頁面佈局及顯示樣式所需的 CSS 文件

web/static/js :用於存放編寫的與用戶交互的 JavaScript 源碼文件

web/static/img:用戶存放頁面顯示所需的所有圖片文件

web/tpl 目錄下包括三個靜態 HTML 頁面文件,分別爲:

web/tpl/index.html: 用戶訪問的首頁面

web/tpl/queryReq.html: 用於顯示顯示查詢結果的頁面

web/tpl/setInfo.html: 用戶設置/修改狀態的頁面

web/webServer.go:用於指定啓動Web服務及相應的路由信息

/**
  author: kevin
 */
package web

import (
        "net/http"
        "fmt"
        "github.com/kongyixueyuan.com/kongyixueyuan/web/controller"
)

func  WebStart(app *controller.Application)  {

        fs := http.FileServer(http.Dir("web/static"))
        http.Handle("/static/", http.StripPrefix("/static/", fs))

        http.HandleFunc("/", app.IndexView)
        http.HandleFunc("/index.html", app.IndexView)
        http.HandleFunc("/setInfo.html", app.SetInfoView)
        http.HandleFunc("/setReq", app.SetInfo)
        http.HandleFunc("/queryReq", app.QueryInfo)

        fmt.Println("啓動Web服務, 監聽端口號: 9000")

        err := http.ListenAndServe(":9000", nil)
        if err != nil {
                fmt.Println("啓動Web服務錯誤")
        }

}

這些代碼可以在網上找一些相似的代碼改過來。

8.2、編寫頁面

編寫下面三個html文件,內容在網上找一下改一下就行了。

  • index.html
  • queryReq.html
  • setInfo.html

8.3、啓動Web服務

最後編輯 main.go ,以便啓動Web界面實現Web應用程序

$ vim main.go

添加如下內容:

import(
	[......]
	"github.com/kongyixueyuan.com/kongyixueyuan/web"
	"github.com/kongyixueyuan.com/kongyixueyuan/web/controller"
)

func main(){}
	[......]
	
	app := controller.Application{
		Fabric: &serviceSetup,
	}
	web.WebStart(&app)
}

執行 make 命令啓動Web應用:
在這裏插入圖片描述

8.4、 頁面訪問

打開瀏覽器訪問: 127.0.0.1:9000

因爲我們這是一個簡單的 Web 應用示例,所以頁面不會要求達到多麼美觀的地步,只是能夠實現相應的功能即可。根據訪問的地址,首先進入 index.html 頁面,該 index.html 頁面提供了兩個鏈接(也可以通過頁面頂部的菜單中訪問),用於實現在在分類賬本中進行狀態查詢或對分類賬本中的狀態進行修改的操作(在此不實現添加狀態的操作)。
在這裏插入圖片描述

因爲我們在業務層中測試過一次,通過調用業務層向分類賬中添加了一條狀態, 所以現在分類帳中有一個 key 爲 Hanxiaodongvalue 爲 Kongyixueyuan 的鍵值對數據,可以點擊 查詢信息 的鏈接實現查詢
在這裏插入圖片描述

點擊頁面中的 設置/修改 鏈接後進入一個表單頁面,該頁面提供了一個更改狀態的表單,表單中的 key 爲固定值,用戶需要輸入對應的 Val,之後點擊提交按鈕發送請求。

在 Val 輸入框中輸入一個值,如 ChainDesk 後點擊提交按鈕,表單被提交到服務器,服務器處理完畢將返回操作成功的交易ID並將其顯示在頁面中。
在這裏插入圖片描述

我們可以通過點擊頁面中的 查詢信息 鏈接來查看狀態是否更改成功
在這裏插入圖片描述
後臺狀況

九、總結

目前將區塊鏈開發的整體流程從後端到前端走了一遍,過程中學習了go、鏈碼、docker、前端等知識,爲後期更好開發做了更好的儲備。後期的開發主要圍繞上邊的流程進行,主要還是要參考網上源代碼,多學習代碼。

十、參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章