基於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的應用,因此對這些我們採取網上開源的或者直接應用示例文件的,有時間的話再研究這個。 主要內容就是在前期cryptogen
和configtxhen
工具的輔助和配置文件的內容下生成genesis.block
channel.tx
以及各種信息如下圖,這裏不進行重點講解。
2.2、配置docker-compose.yml文件
在 fixtures
目錄下創建一個 docker-compose.yml
文件並編輯
$ vim docker-compose.yml
-
將
network下的basic
修改爲default
version: '2' networks: default: services:
-
編輯 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
-
編輯 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
-
編輯Peer部分
-
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
-
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
-
-
其餘可根據實際情況進行添加
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"
現在完成任務:
- 整個環境將被清理乾淨,
- go程序將被編譯,
- 之後將部署網絡
- 最後該應用程序將啓動並運行。
要使用它,請進入項目的根目錄並使用make
命令:
- 任務
all
:make
或make 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 爲 Hanxiaodong
,value 爲 Kongyixueyuan
的鍵值對數據,可以點擊 查詢信息
的鏈接實現查詢
點擊頁面中的 設置/修改
鏈接後進入一個表單頁面,該頁面提供了一個更改狀態的表單,表單中的 key 爲固定值,用戶需要輸入對應的 Val,之後點擊提交按鈕發送請求。
在 Val 輸入框中輸入一個值,如 ChainDesk
後點擊提交按鈕,表單被提交到服務器,服務器處理完畢將返回操作成功的交易ID並將其顯示在頁面中。
我們可以通過點擊頁面中的 查詢信息
鏈接來查看狀態是否更改成功
後臺狀況
九、總結
目前將區塊鏈開發的整體流程從後端到前端走了一遍,過程中學習了go、鏈碼、docker、前端等知識,爲後期更好開發做了更好的儲備。後期的開發主要圍繞上邊的流程進行,主要還是要參考網上源代碼,多學習代碼。