Origins

​ 当今生产环境,溯源系统主要用于食品溯源,进口产品溯源,品牌产品溯源的领域,因为在这些领域经常会出现一些假冒伪劣产品,以次充好,所以利用区块链实现产品溯源就显得格外重要.

​ 我们要做的案例所能解决的问题:我们可以通过房屋溯源项目,解决现实生活中的一些租房乱象,使用户在签订租房合同之前就能够对将要租住的房子有个大致的认识。当然,我们的这个小项目毕竟只是demo 级别,功能有限,我们旨在拿它学习我们的fabric。

程序架构的设计

如下图所示:

​ 本项目 APP_Service 采用 beego 框架实现,调用原生 Fabric-SDK-Go 来完成访问账本数据的目的。

​ 在程序的业务开始之前,我们需要完成Fabric_Services 环境的搭建,然后利用 SDK 将我们编写好了的链代码上传到前面我们所配置的 Fabric 环境当中去 。

Fabric-Services 环境准备

安装软件环境

此项目在 Linux 环境下部署:

1、安装git

​ 2、安装 golang

​ 3、安装 docker

​ 4、docker-compose

编写配置文件

约定

本项目中设定三个组织:土管局,房管局,租房平台

每个组织中的 peer 节点数:2

每个组织对应一个单独的channel: tgjchannel , fgjchannel , zfptchannel

三个组织有一个共同的 channel:unionchannel

根域名使用:itcast.cn

1. crypto-config.yaml

切换工作目录

$ cd $GOPATH/origin_fabric_servic

使用下面命令生成 文件模板

cryptogen showtemplate > crypto-config.yaml

根据实际定义修改内容,最终结果为:

OrdererOrgs:- Name: OrdererDomain: itcast.cnSpecs:- Hostname: ordererPeerOrgs:# 房管局组织- Name: OfgjDomain: ofgj.itcast.cn# 是否支持 NodejsEnableNodeOUs: true# 组织下面peer节点的个数Template:Count: 2# 创建的普通用户的个数Users:Count: 1# 土管局组织- Name: OtgjDomain: otgj.itcast.cn# 是否支持 NodejsEnableNodeOUs: true# 组织下面peer节点的个数Template:Count: 2# 创建的普通用户的个数Users:Count: 1# 租房平台组织- Name: OzfptDomain: ozfpt.itcast.cn# 是否支持 NodejsEnableNodeOUs: true# 组织下面peer节点的个数Template:Count: 2# 创建的普通用户的个数Users:Count: 1
  • 在crypto-config 目录下生成证书目录:
$ cryptogen generate --config=./crypto-config.yaml

2. configtx.yaml - 创始块/通道文件

从 fabric 配置文件例子中获取模板

$ cp $GOPATH/src/github.com/hyperledger-fabric/fabric-samples/first-network/configtx.yaml ./

修改后色内容:

Profiles:ThreeOrgsOrdererGenesis:Capabilities:<<: *ChannelCapabilitiesOrderer:<<: *OrdererDefaultsOrganizations:- *OrdererCapabilities:<<: *OrdererCapabilities# 联盟Consortiums:# 三个组织的联盟ThreeOrgsConsortium:   # 名字可改Organizations:- *Ofgj- *Otgj- *Ozfpt# 房管局私有链OfgjConsortium:Organizations:- *Ofgj# 土改局私有链OtgjConsortium:Organizations:- *Otgj#租房平台私有链OzfptConsortium:Organizations:- *Ozfpt# 生成 unionchannelunionOrgschannel:Consortium: ThreeOrgsConsortiumApplication:<<: *ApplicationDefaultsOrganizations:- *Ofgj- *Otgj- *OzfptCapabilities:<<: *ApplicationCapabilities#生成 fgjchannelfgjOrgchannel:Consortium:OfgjConsortiumApplication:<<: *ApplicationDefaultsOrganizations:- *OfgjCapabilities:<<: *ApplicationCapabilities#生成 tgjchanneltgjOrgchannel:Consortium:OtgjConsortiumApplication:<<: *ApplicationDefaultsOrganizations:- *OtgjCapabilities:<<: *ApplicationCapabilities#生成 zfptchannelzfptOrgchannel:Consortium:OzfptConsortiumApplication:<<: *ApplicationDefaultsOrganizations:- *OzfptCapabilities:<<: *ApplicationCapabilitiesOrganizations:               # 固定的,不能改- &Orderer              # 相当于定义了一个变量,其他地方可以引用它Name: Orderer          # 排序节点的组织名ID: itcast.cn         # 排序节点组织的IDMSPDir: crypto-config/ordererOrganizations/itcast.cn/msp # 组织的 MSP 账号- &Ofgj                 # 房管局组织Name: OfgjMSP            # 组织名称ID: ofgj.itcast.cn        # 组织ID,该属性在设置背书策略时需要使用MSPDir: crypto-config/peerOrganizations/ofgj.itcast.cn/msp    #组织的 MSP 账号AnchorPeers:         # 锚节点- Host: peer0.ofgj.itcast.cnPort: 7051- &OtgjName: OtgjMSPID: otgj.itcast.cnMSPDir: crypto-config/peerOrganizations/otgj.itcast.cn/mspAnchorPeers:- Host: peer0.otgj.itcast.cnPort: 7051- &OzfptName: OzfptMSPID: ozfpt.itcast.cnMSPDir: crypto-config/peerOrganizations/ozfpt.itcast.cn/mspAnchorPeers:- Host: peer0.ozfpt.itcast.cnPort: 7051Orderer: &OrdererDefaults# 共识机制 == 排序算法OrdererType: solo    # 共识机制 - solo 算法之支持一个排序节点Addresses:          # orderer 节点的网络位置- orderer.itcast.cn:7050BatchTimeout: 5s    # 产生一个区块的时间单位BatchSize:MaxMessageCount: 10      # 交易的最大数量,交易数量达到之后会产生一个区块AbsoluteMaxBytes: 98 MB # 数据量达到该制定的值,也会产生一个区块PreferredMaxBytes: 512 KB # 首选 MaxBytes Kafka:            # 本例中的排序算法指定的是solo,此项可以不指定Brokers:- 127.0.0.1:9092    # 指定 Kafka 排序集群所在的网络位置MaxChannels: 0Organizations:# 此处不需要修改,默认即可
Application: &ApplicationDefaultsOrganizations:# Fabric_v1.1 之后的内容,全部设为true,是为了兼容 1.1 前面的版本
Capabilities:Global: &ChannelCapabilitiesV1_1: trueOrderer: &OrdererCapabilitiesV1_1: trueApplication: &ApplicationCapabilitiesV1_1: true
  • 执行命令生成文件:

    • 生成创世块文件

      $ mkdir channel-artifacts
      $ configtxgen -profile ThreeOrgsOrdererGenesis -outputBlock ./channel-artifacts/genesis.block
      
    • 生成通道文件

      # 生成 unionchannel 文件
      $ echo "---------------- Create unionchannel.tx file BEGIN -------------------"
      $ configtxgen -profile UnionOrgsChannel -outputCreateChannelTx ./channel-artifacts/unionchannel.tx -channelID unionchannel
      $ echo "---------------- Create unionchannel.tx file END -------------------"# 生成 fgjchannel
      $ echo "---------------- Create fgjchannel.tx file BEGIN -------------------"
      $ configtxgen -profile fgjOrgchannel -outputCreateChannelTx ./channel-artifacts/fgjchannel.tx -channelID fgjchannel
      $ echo "---------------- Create fgjchannel.tx file END -------------------"# 生成 tgjchannel
      $ echo "---------------- Create tgjchannel.tx file BEGIN -------------------"
      $ configtxgen -profile tgjOrgchannel -outputCreateChannelTx ./channel-artifacts/tgjchannel.tx -channelID tgjchannel
      $ echo "---------------- Create tgjchannel.tx file END -------------------"# 生成 tgjchannel
      $ echo "---------------- Create zfptchannel.tx file BEGIN -------------------"
      $ configtxgen -profile zfptOrgchannel -outputCreateChannelTx ./channel-artifacts/zfptchannel.tx -channelID zfptchannel
      $ echo "---------------- Create zfptchannel.tx file END -------------------"
      
    • 生成锚节点更新文件

      此操作是可选的

      $ echo "---------------- Create AnchorPeerUpdate.tx file BEGIN -----------------"
      $ configtxgen -profile UnionOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/OfgjMSPanchors.tx -channelID unionchannel -asOrg OfgjMSP
      $ configtxgen -profile UnionOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/OtgjMSPanchors.tx -channelID unionchannel -asOrg OtgjMSP
      $ configtxgen -profile UnionOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/OzfptMSPanchors.tx -channelID unionchannel -asOrg OzfptMSP
      $ echo "---------------- Create AnchorPeerUpdate.tx file BEGIN ------------------"
      

    以上命令执行完毕后查看生成的结果,如果一下问及那都可以生成成功,说明以上操作都没有问题:

    channel-artifacts/
    ├── fgjchannel.tx
    ├── OfgjMSPanchors.tx
    ├── OtgjMSPanchors.tx
    ├── OzfptMSPanchors.tx
    ├── unionchannel.tx
    └── zfptchannel.tx
    

3. docker-compose.yaml 文件

复制模板文件,然后基于go模版文件修改

$ cp $GOPATH/src/github.com/hyperledger-fabric/fabric-samples/first-network/docker-compose-cli.yaml

修改后的结果:

version: '2'networks:ZFW_suyuan:services:orderer.itcast.cn:image: hyperledger/fabric-orderer:latestcontainer_name: orderer.itcast.cnenvironment:- ORDERER_GENERAL_LOGLEVEL=debug          # 日志级别- ORDERER_GENERAL_LISTENADDRESS=0.0.0.0  # orderer节点监听的地址- ORDERER_GENERAL_LISTENPORT=7050          # orderer 默认监听7050,监听的端口号可以修改- ORDERER_GENERAL_GENESISPROFILE=Orderer   - ORDERER_GENERAL_GENESISMETHOD=file       # 创世块的来源,file表示来自于文件 # 指定创世块文件的路径- ORDERER_GENERAL_GENESISFILE=/var/hyperledger/orderer/orderer.genesis.block - ORDERER_GENERAL_LOCALMSPID=itcast.cn   # Orderer组织的ID[Organizations -> Orderer -> ID]# 当前节点的 MSP 账号路径- ORDERER_GENERAL_LOCALMSPDIR=/var/hyperledger/orderer/msp - ORDERER_GENERAL_TLS_ENABLED=true     # 通信的时候是否使用 TLS 加密 - 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/fabriccommand: orderervolumes:- ./channel-artifacts/genesis.block:/var/hyperledger/orderer/orderer.genesis.block- ./crypto-config/ordererOrganizations/itcast.cn/orderers/orderer.itcast.cn/msp:/var/hyperledger/orderer/msp- ./crypto-config/ordererOrganizations/itcast.cn/orderers/orderer.itcast.cn/tls:/var/hyperledger/orderer/tlsports:- 7050:7050networks:default:aliases:- orderer.itcast.cn# ofgjpeer0.ofgj.itcast.cn:image: hyperledger/fabric-peer:latestcontainer_name: peer0.ofgj.itcast.cnenvironment:- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock- CORE_PEER_ID=peer0.ofgj.itcast.cn          # peer 节点的名字- CORE_PEER_ADDRESS=peer0.ofgj.itcast.cn:7051 # 当前 peer 节点的地址信息- CORE_VM_DOCKER_ATTACHSTDOUT=true- CORE_LOGGING_LEVEL=DEBUG- CORE_PEER_ADDRESSAUTODETECT=true# 为了别其他节点感知到,如果不设置,其他节点不知道该节点的存在- CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.ofgj.itcast.cn:7051- CORE_PEER_GOSSIP_USELEADERELECTION=true     # leader 节点自动选举机制- CORE_PEER_GOSSIP_ORGLEADER=false            # 当前节点是不是 leader 节点,结果应由选举得出所以此处给出 false- CORE_PEER_GOSSIP_SKIPHANDSHAKE=true# 当前组织的ID[Organizations -> ID]- CORE_PEER_LOCALMSPID=ofgj.itcast.cn- CORE_PEER_PROFILE_ENABLED=true- CORE_PEER_MSPCONFIGPATH=/var/hyperledger/msp- CORE_PEER_TLS_ENABLED=true- CORE_PEER_TLS_SERVERHOSTOVERRIDE=peer0.ofgj.itcast.cn- 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.crtworking_dir: /opt/gopath/src/github.com/hyperledger/fabric/peercommand: peer node startvolumes:- /var/run/:/host/var/run/- ./crypto-config/peerOrganizations/ofgj.itcast.cn/peers/peer0.ofgj.itcast.cn/msp:/var/hyperledger/msp- ./crypto-config/peerOrganizations/ofgj.itcast.cn/peers/peer0.ofgj.itcast.cn/tls:/var/hyperledger/tlsports:- 7051:7051- 7053:7053depends_on:- orderer.itcast.cnlinks:- orderer.itcast.cnnetworks:default:aliases:- peer0.ofgj.itcast.cnpeer1.ofgj.itcast.cn:image: hyperledger/fabric-peer:latestcontainer_name: peer1.ofgj.itcast.cnenvironment:- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock- CORE_VM_DOCKER_ATTACHSTDOUT=true- CORE_LOGGING_LEVEL=DEBUG- 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.ofgj.itcast.cn- CORE_PEER_ADDRESSAUTODETECT=true- CORE_PEER_ADDRESS=peer1.ofgj.itcast.cn:7051- CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer1.ofgj.itcast.cn:7051- CORE_PEER_GOSSIP_USELEADERELECTION=true- CORE_PEER_GOSSIP_ORGLEADER=false- CORE_PEER_GOSSIP_SKIPHANDSHAKE=true- CORE_PEER_LOCALMSPID=ofgj.itcast.cn- CORE_PEER_MSPCONFIGPATH=/var/hyperledger/msp- CORE_PEER_TLS_SERVERHOSTOVERRIDE=peer1.ofgj.itcast.cnworking_dir: /opt/gopath/src/github.com/hyperledger/fabric/peercommand: peer node startvolumes:- /var/run/:/host/var/run/- ./crypto-config/peerOrganizations/ofgj.itcast.cn/peers/peer1.ofgj.itcast.cn/msp:/var/hyperledger/msp- ./crypto-config/peerOrganizations/ofgj.itcast.cn/peers/peer1.ofgj.itcast.cn/tls:/var/hyperledger/tlsports:- 8051:7051- 8053:7053depends_on:- orderer.itcast.cnlinks:- orderer.itcast.cnnetworks:default:aliases:- peer1.ofgj.itcast.cn# otgjpeer0.otgj.itcast.cn:image: hyperledger/fabric-peer:latestcontainer_name: peer0.otgj.itcast.cnenvironment:- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock- CORE_VM_DOCKER_ATTACHSTDOUT=true- CORE_LOGGING_LEVEL=DEBUG- 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.otgj.itcast.cn- CORE_PEER_ADDRESSAUTODETECT=true- CORE_PEER_ADDRESS=peer0.otgj.itcast.cn:7051- CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.otgj.itcast.cn:7051- CORE_PEER_GOSSIP_USELEADERELECTION=true- CORE_PEER_GOSSIP_ORGLEADER=false- CORE_PEER_GOSSIP_SKIPHANDSHAKE=true- CORE_PEER_LOCALMSPID=otgj.itcast.cn- CORE_PEER_MSPCONFIGPATH=/var/hyperledger/msp- CORE_PEER_TLS_SERVERHOSTOVERRIDE=peer0.otgj.itcast.cnworking_dir: /opt/gopath/src/github.com/hyperledger/fabric/peercommand: peer node startvolumes:- /var/run/:/host/var/run/- ./crypto-config/peerOrganizations/otgj.itcast.cn/peers/peer0.otgj.itcast.cn/msp:/var/hyperledger/msp- ./crypto-config/peerOrganizations/otgj.itcast.cn/peers/peer0.otgj.itcast.cn/tls:/var/hyperledger/tlsports:- 9051:7051- 9053:7053depends_on:- orderer.itcast.cnlinks:- orderer.itcast.cnnetworks:default:aliases:- peer0.otgj.itcast.cnpeer1.otgj.itcast.cn:image: hyperledger/fabric-peer:latestcontainer_name: peer1.otgj.itcast.cnenvironment:- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock- CORE_VM_DOCKER_ATTACHSTDOUT=true- CORE_LOGGING_LEVEL=DEBUG- 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.otgj.itcast.cn- CORE_PEER_ADDRESSAUTODETECT=true- CORE_PEER_ADDRESS=peer1.otgj.itcast.cn:7051- CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer1.otgj.itcast.cn:7051- CORE_PEER_GOSSIP_USELEADERELECTION=true- CORE_PEER_GOSSIP_ORGLEADER=false- CORE_PEER_GOSSIP_SKIPHANDSHAKE=true- CORE_PEER_LOCALMSPID=otgj.itcast.cn- CORE_PEER_MSPCONFIGPATH=/var/hyperledger/msp- CORE_PEER_TLS_SERVERHOSTOVERRIDE=peer1.otgj.itcast.cnworking_dir: /opt/gopath/src/github.com/hyperledger/fabric/peercommand: peer node startvolumes:- /var/run/:/host/var/run/- ./crypto-config/peerOrganizations/otgj.itcast.cn/peers/peer1.otgj.itcast.cn/msp:/var/hyperledger/msp- ./crypto-config/peerOrganizations/otgj.itcast.cn/peers/peer1.otgj.itcast.cn/tls:/var/hyperledger/tlsports:- 10051:7051- 10053:7053depends_on:- orderer.itcast.cnlinks:- orderer.itcast.cnnetworks:default:aliases:- peer1.otgj.itcast.cn# ozfptpeer0.ozfpt.itcast.cn:image: hyperledger/fabric-peer:latestcontainer_name: peer0.ozfpt.itcast.cnenvironment:- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock- CORE_VM_DOCKER_ATTACHSTDOUT=true- CORE_LOGGING_LEVEL=DEBUG- 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.ozfpt.itcast.cn- CORE_PEER_ADDRESSAUTODETECT=true- CORE_PEER_ADDRESS=peer0.ozfpt.itcast.cn:7051- CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.ozfpt.itcast.cn:7051- CORE_PEER_GOSSIP_USELEADERELECTION=true- CORE_PEER_GOSSIP_ORGLEADER=false- CORE_PEER_GOSSIP_SKIPHANDSHAKE=true- CORE_PEER_LOCALMSPID=ozfpt.itcast.cn- CORE_PEER_MSPCONFIGPATH=/var/hyperledger/msp- CORE_PEER_TLS_SERVERHOSTOVERRIDE=peer0.ozfpt.itcast.cnworking_dir: /opt/gopath/src/github.com/hyperledger/fabric/peercommand: peer node startvolumes:- /var/run/:/host/var/run/- ./crypto-config/peerOrganizations/ozfpt.itcast.cn/peers/peer0.ozfpt.itcast.cn/msp:/var/hyperledger/msp- ./crypto-config/peerOrganizations/ozfpt.itcast.cn/peers/peer0.ozfpt.itcast.cn/tls:/var/hyperledger/tlsports:- 11051:7051- 11053:7053depends_on:- orderer.itcast.cnlinks:- orderer.itcast.cnnetworks:default:aliases:- peer0.ozfpt.itcast.cnpeer1.ozfpt.itcast.cn:image: hyperledger/fabric-peer:latestcontainer_name: peer1.ozfpt.itcast.cnenvironment:- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock- CORE_VM_DOCKER_ATTACHSTDOUT=true- CORE_LOGGING_LEVEL=DEBUG- 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.ozfpt.itcast.cn- CORE_PEER_ADDRESSAUTODETECT=true- CORE_PEER_ADDRESS=peer1.ozfpt.itcast.cn:7051- CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer1.ozfpt.itcast.cn:7051- CORE_PEER_GOSSIP_USELEADERELECTION=true- CORE_PEER_GOSSIP_ORGLEADER=false- CORE_PEER_GOSSIP_SKIPHANDSHAKE=true- CORE_PEER_LOCALMSPID=ozfpt.itcast.cn- CORE_PEER_MSPCONFIGPATH=/var/hyperledger/msp- CORE_PEER_TLS_SERVERHOSTOVERRIDE=peer1.ozfpt.itcast.cnworking_dir: /opt/gopath/src/github.com/hyperledger/fabric/peercommand: peer node startvolumes:- /var/run/:/host/var/run/- ./crypto-config/peerOrganizations/ozfpt.itcast.cn/peers/peer1.ozfpt.itcast.cn/msp:/var/hyperledger/msp- ./crypto-config/peerOrganizations/ozfpt.itcast.cn/peers/peer1.ozfpt.itcast.cn/tls:/var/hyperledger/tlsports:- 12051:7051- 12053:7053depends_on:- orderer.itcast.cnlinks:- orderer.itcast.cnnetworks:default:aliases:- peer1.ozfpt.itcast.cn

4. 启动网络

由于使用的配置文件名称是docker-compose.yaml,是docker-compose的默认值,所以可以使用下面的命令来启 动网络:

  • $ docker-compose up -d
    

我们本例中有1个orderer 节点,6个peer节点,所以如果7个docker 容器能够全部启动成功,那么我们的fabric 网络环境就已经启动成功,如下图所示:

$ docker-compose ps

到目前为止,整个 fabric网络环境已经跑起来了.我们前面在学习 Fabric 的时候,在启动 docker 容器的时候,同时启动了一个 cli 容器,它能够利用 CLI 工具完成 channel,chaincode 的一些操作,如下:

  • 创建通道, 通过客户端节点来完成

  • 将每个组织的每个节点都加入到通道中 -> 客户端来完成的

    • 一个客户端同时只能连接一个peer节点
  • 给每个peer节点安装智能合约 -> 链代码(程序: go, node.js, java)

  • 对智能合约进行初始化 , 对应智能合约中的 Init 函数

    • 只需要在任意节点初始化一次, 数据会自动同步的各个组织的各个节点
  • 对数据进行查询 -> 读

  • 对数据进行调用 -> 写

经过前面的讲解我们都知道, 一个客户端只能连接一个指定的节点, 如果想要该客户端连接其他节点, 那么就必须修改当前客户端中相关的环境变量

此项目中,我们不在使用 cli 工具,而是通过 Fabric_SDK_go 来完成相应的工作.

APP_Service 应用开发

在应用层,我们首先要做的是要,设计并编写链代码,我们需要借助 hyperledger/fabric 框架。那么,首先我们需要把 fabric源码 下载到$GOPATH/src/github.com/hyperledger 目录下

之前已经下载过了的,此步可以忽略

接下来我们就可以编写我们的链代码了。那么在这之前我们有必要去提前设计一下我们的链代码。

链代码的设计

如下图所示,在 账本中,我们至少要保存如下这些数据:

如图所示,房源信息中包括房屋信息,房屋周围的社区信息,以及与该房源相关的订单信息。这些信息都是通过以 RentingID 为key 存放进入我们的超级账本当中去的,所以,在我们的链代码中需要声明与之对应的结构体,来储存相应的信息。

对应的结构体声明如下:

  • HouseInfo 用于存储与房屋信息相关的一些数据,在此项目场景中,此数据应由“房管局组织”维护,所以,对该数据的操作可以走 fgjchannel.
// 房屋信息  ofgj
type HouseInfo struct {HouseID    string `json:"house_id"`    // 房产证编号HouseOwner string `json:"house_owner"` // 房主RegDate    string `json:"reg_date"`    // 登记日期HouseArea  string `json:"house_area"`  // 住房面积HouseUsed  string `json:"house_used"`  // 房屋设计用途IsMortgage string `json:"is_mortgage"` // 是否抵押
}
  • AreaInfo 用于存储与该房源相关的一些社区信息,用户可以通过该信息了解到一些能够影响将要租住房源的外界信息、基础建设等信息.此数据应由“土管局组织”维护,所以,对该数据的操作可以走 tgjchannel.
// 社区信息 otgj
type AreaInfo struct {AreaID       string `json:"area_id"`        // 社区编号AreaAddress  string `json:"area_address"`   // 房源所在区域BasicNetWork string `json:"basic_net_work"` // 区域基础网络编号CPoliceName  string `json:"c_police_name"`  // 社区民警姓名CPoliceNum   string `json:"c_police_num"`   // 社区民警工号
}
  • OrderInfo 用于存储与房源相关的历史租住信息,用户可以通过信息了解到房屋的以往租金等信息。此数据应由“租房平台组织”维护,所以,对该数据的操作可以走 zfptchannel.
// 订单信息  ozfpt
type OrderInfo struct {DocHash   string `json:"doc_hash"`   // 电子合同HashOrderID   string `json:"order_id"`   // 订单编号RenterID  string `json:"renter_id"`  // 承租人信息RentMoney string `json:"rent_money"` // 租金BeginDate string `json:"begin_date"` // 开始日期EndDate   string `json:"end_date"`   // 结束日期Note      string `json:"note"`       // 备注
}

由此,我们我们可以得出 “房源信息” 结构体的 声明原型:

// 房源信息
type RentingHouseInfo struct {RentingID        string    `json:"renting_id"`         // 统一编码RentingHouseInfo HouseInfo `json:"renting_house_info"` // 房屋信息RentingAreaInfo  AreaInfo  `json:"renting_area_info"`  //区域信息RentingOrderInfo OrderInfo `json:"renting_order_info"` //订单信息
}

链代码的实现

现在我们已经清楚了 chaincode 需要操作的一些数据模型,下面就可以编写我们的链代码了。在开始写chaincode 之前,我们需要声明一个 chaincode 结构体,并重载它的 Init 和 Invoke 方法,其中Invoke 方法中解析出请求的内容,调用不同的请求,这是我们编写chaincode的模板写法儿.

type HouseChainCode struct {// 房屋溯源链代码
}
// Init 方法
func (this *HouseChainCode) Init(stub shim.ChaincodeStubInterface) peer.Response {}
// Invoke 方法
func (this *HouseChainCode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {// 获取 函数名 和 参数列表fn, parameters := stub.GetFunctionAndParameters()// 通过函数名匹配对应的链代码函数调用if fn == "addHouseInfo" {// 添加房源信息return this.addHouseInfo(stub, parameters)} else if fn == "getHouseInfo" {// 获取房源信息return this.getHouseInfo(stub, parameters)} else if fn == "addOrderInfo" {// 添加订单信息return this.addOrderInfo(stub, parameters)} else if fn == "getOrderInfo" {// 获取订单信息return this.getOrderInfo(stub, parameters)} else if fn == "addAreaInfo" {// 添加社区信息return this.addAreaInfo(stub, parameters)} else if fn == "getAreaInfo" {// 获取社区信息return this.getAreaInfo(stub, parameters)}// 没有任何函数被匹配到,返回错误消息fmt.Println("==== fn = ", fn)return shim.Error("Received unknow function invocation")
}

我们通过项目的展示,已经基本清楚项目的大致模型,下面一段代码是 addHouseCode 函数的实现,它的主要功能是实现将用户输入的房屋信息,写入超级账本当中去,它通过接收用户参数,并将数据以 rentingID 为key,以 args的接送编码数据为value的形式写进账本当中去.

// 添加房屋信息
func (this *HouseChainCode) addHouseInfo(stub shim.ChaincodeStubInterface, args []string) peer.Response {// 定义一个房屋信息结构体var HouseInfos RentingHouseInfoif len(args) != 7 {return shim.Error("Incorrect number oof arguments.")}HouseInfos.RentingID = args[0]if HouseInfos.RentingID == "" {return shim.Error("RentingId can't be empty.")}HouseInfos.RentingHouseInfo.HouseID = args[1]HouseInfos.RentingHouseInfo.HouseOwner = args[2]HouseInfos.RentingHouseInfo.RegDate = args[3]HouseInfos.RentingHouseInfo.HouseArea = args[4]HouseInfos.RentingHouseInfo.HouseUsed = args[5]HouseInfos.RentingHouseInfo.IsMortgage = args[6]HouseInfosJsonBytes, err := json.Marshal(HouseInfos)if err != nil {return shim.Error(err.Error())}err = stub.PutState(HouseInfos.RentingID, HouseInfosJsonBytes)if err != nil {return shim.Error(err.Error())}return shim.Success([]byte("ok"))
}

前面,我们已经将房屋信息写进了区块链,下面一段代码是将刚才写入区块链中的数据读出来,以达到数据参看的目的.

// 查看房屋信息
func (this *HouseChainCode) getHouseInfo(stub shim.ChaincodeStubInterface, args []string) peer.Response {// 判断参数个数if len(args) != 1 {return shim.Error("Incorrect number of arguments.")}// 获取唯一标识码rentingID := args[0]// 获得查询结果迭代器resultItreator, err := stub.GetHistoryForKey(rentingID)if err != nil {return shim.Error(err.Error())}// 释放迭代器资源defer resultItreator.Close()var rentHouseInfo HouseInfo// 使用迭代器遍历查询结果集for resultItreator.HasNext() {var HouseInfos RentingHouseInfo// 取一条结果response, err := resultItreator.Next()if err != nil {return shim.Error(err.Error())}err = json.Unmarshal(response.Value, &HouseInfos)if err != nil {return shim.Error(err.Error())}if HouseInfos.RentingHouseInfo.HouseOwner != "" {rentHouseInfo = HouseInfos.RentingHouseInfocontinue}}jsonAsBytes, err := json.Marshal(rentHouseInfo)if err != nil {return shim.Error(err.Error())}// 返回数据查询结果return shim.Success(jsonAsBytes)
}

以上两端代码仅仅只是与房屋相关的信息,其他两个我们可以参照这两个去写。

APP_Service的框架搭建

到目前为止呢,我们有关与 Fabric_Services 相关的工作都应做完了,下面我们就需要针对于 APP_Services 做功能的实现了。我们的 APP_Services 采用的是beego框架,所以,我们首先要做的就是设置我们的路由,然后根据路由一次实现对应的处理函数。

1. route配置

​ 根据链代码中 Invoke 函数分支调用模型,路由设计如下:

func init() {beego.Router("/", &controllers.MainController{})beego.Router("/form_area",&controllers.MainController{},"get:FormArea")beego.Router("/form_house",&controllers.MainController{},"get:FormHouse")beego.Router("/form_orderer",&controllers.MainController{},"get:FormOrderer")beego.Router("/area_search",&controllers.MainController{},"get:AreaSearch")beego.Router("/house_search",&controllers.MainController{},"get:HouseSearch" )beego.Router("/orderer_search",&controllers.MainController{},"get:OrdererSearch")
}

2. controller函数原型

​ 根据设计好的路由,controllers 模块中对应的函数实现:

// 发布房源信息
func (this *MainController) FormHouse() {}
// 查询房源信息
func (this *MainController) HouseSearch() {}
// 发布社区信息
func (this *MainController) FormArea() {}
// 查询社区信息
func (this *MainController) AreaSearch() {}
// 发布订单信息
func (this *MainController) FormOrderer() {}
// 查询订单信息
func (this *MainController) OrdererSearch() {}

​ 下面拿 “房源信息” 为例,我们需要在函数内部获取前端用户的输入,并调用models层业务逻辑,完成数据的账本记入,具体实现如下:

// 发布房源信息
func (this *MainController) FormHouse() {// 响应 /form_house 请求,将 form_house.html 页面返回给客户端this.TplName = "form_house.html"rentingID := this.GetString("rentingID")if rentingID == "" {return} else {// 获取用户输入fczbh := this.GetString("fczbh")fzxm := this.GetString("fzxm")djrq := this.GetString("djrq")zfmj := this.GetString("zfmj")fwsjyt := this.GetString("fwsjyt")sfdy := this.GetString("sfdy")// 组织参数var args []stringargs = append(args, "addHouseInfo")args = append(args, rentingID)args = append(args, fczbh)args = append(args, fzxm)args = append(args, djrq)args = append(args, zfmj)args = append(args, fwsjyt)args = append(args, sfdy)//TODO  调用models 层业务逻辑,把数据记入超级账本}// 最后返回在首页this.TplName = "index.html"
}

上面 TODO 需要借助models层业务逻辑,所以接下来我们去写models层实现,以支撑上层的开发

3. 编写 SDK 配置文件

我们可以参照$GOPATH/src/github.com/hyperledger/fabric-sdk-go/pkg/core/config/testdata/template/config.yaml 模板文件完成我们SDK配置文件的编写,同时 fabric-sdk-go 给我们提供了两个demo程序,我们也可以把它们的 配置文件拿过来参考,这两个文件在如下目录中,

$GOPATH/src/github.com/hyperledger/fabric-sdk-go/test/fixtures/config/config_test.yaml
$GOPATH/src/github.com/hyperledger/fabric-sdk-go/pkg/core/config/testdata/config_test.yaml

相关配置如下:

# 配置文件的名字,可以不写
name: "suyuan-service-network"version: 1.0.0# client 相关配置
client:# 此 SDK 实例属于哪个组织organization: Ofgj# 日志级别logging:level: info# 证书所在目录cryptoconfig:path: /home/stu/workspace/src/origins/conf/crypto-config# 这种方式就是把用户名和密码直接存储在本地的一个文件中,而用户和密码对通过一个别名来引用,这样可以避免密码明文格式可能会存在的安全问题credentialStore:path: /tmp/suyuan-service-store# 区块链密码服务提供者,指定加密策略BCCSP:security:enabled: truedefault:provider: "SW"hashAlgorithm: "SHA2"softVerify: truelevel: 256tlsCerts:# 证书池策略,默认为false,提高身份认证速率systemCertPool: trueclient:keyfile: /home/stu/workspace/src/origins/conf/crypto-config/peerOrganizations/ofgj.itcast.cn/users/User1@ofgj.itcast.cn/tls/client.keycertfile: /home/stu/workspace/src/origins/conf/crypto-config/peerOrganizations/ofgj.itcast.cn/users/User1@ofgj.itcast.cn/tls/client.crt# channel 相关配置
channels:# channelIDfgjchannel:# orderer 组织必须指定orderers:- orderer.itcast.cn# 添加到该 channel 中的组织的 peer 列表peers:peer0.ofgj.itcast.cn:endorsingPeer: truechaincodeQuery: trueledgerQuery: trueeventSource: truepeer1.ofgj.itcast.cn:endorsingPeer: truechaincodeQuery: trueledgerQuery: trueeventSource: truepolicies:queryChannelConfig:minResponses: 1maxTargets: 1retryOpts:attempts: 5initialBackoff: 500msmaxBackoff: 5sbackoffFactor: 2.0# organizations 相关配置
organizations:ofgj:# configtx.yaml organizations -> IDmspid: ofgj.itcast.cncryptoPath: /home/stu/workspace/src/origins/conf/crypto-config/peerOrganizations/ofgj.itcast.cn/users/{userName}@ofgj.itcast.cn/msppeers:- peer0.ofgj.itcast.cn- peer1.ofgj.itcast.cnordererorg:mspID: itcast.cncryptoPath: /home/stu/workspace/src/origins/conf/crypto-config/ordererOrganizations/itcast.cn/users/Admin@itcast.cn/msp# orderer 相关配置
orderers:orderer.itcast.cn:url: localhost:7050grpcOptions:ssl-target-name-override: orderer.itcast.cnkeep-alive-time: 0skeep-alive-timeout: 20skeep-alive-permit: falsefail-fast: falseallow-insecure: falsetlsCACerts:path: /home/stu/workspace/src/origins/conf/crypto-config/ordererOrganizations/itcast.cn/tlsca/tlsca.itcast.cn-cert.pem# peer 相关配置
peers:peer0.ofgj.itcast.cn:url: grpc://localhost:7051eventUrl: localhost:7053grpcOptions:ssl-target-name-override: peer0.ofgj.itcast.cnkeep-alive-time: 0skeep-alive-timeout: 20skeep-alive-permit: falsefail-fast: falseallow-insecure: falsetlsCACerts:path: /home/stu/workspace/src/origins/conf/crypto-config/peerOrganizations/ofgj.itcast.cn/tlsca/tlsca.ofgj.itcast.cn-cert.pempeer1.ofgj.itcast.cn:url: localhost:8051eventUrl: localhost:8053grpcOptions:ssl-target-name-override: peer1.ofgj.itcast.cnkeep-alive-time: 0skeep-alive-timeout: 20skeep-alive-permit: falsefail-fast: falseallow-insecure: falsetlsCACerts:path: /home/stu/workspace/src/origins/conf/crypto-config/peerOrganizations/ofgj.itcast.cn/tlsca/tlsca.ofgj.itcast.cn-cert.pem

4. models设计

我们在models层操作需要用到 Fabric_SDK ,所以我们首先要做的就是先拿到 SDK 句柄,创建channel,添加节点,安装链代码,实例化链代码之手,才能进行数据的操作。首先我们定义一个结构体,该结构体里面存放一些与创建Fabric-SDK 有关的配置信息,格式如下:

// FabricSetup implementation
type FabricSetup struct {ConfigFile      string             // 初始化 SDK 对应的配置文件OrdererID       string                // orderer节点ChannelID       string              // ChannelIDChainCodeID     string              // ChainCodeIDChannelConfig   string                // 与channel相关的配置文件ChaincodeGoPath string                // GOPATH 环境变量ChaincodePath   string                // ChainCode 存放路径OrgAdmin        string             // Admin 用户名OrgName         string              // 组织名称UserName        string               // 普通用户名eventID         string              // eventIDclient          *channel.Client       admin           *resmgmt.Clientsdk             *fabsdk.FabricSDKevent           *event.Client
}

我们新建一个 SDK_Init.go 文件,在里面定义我们的结构体变量,并初始化它:

 fSetup := FabricSetup{// Network parametersOrdererID: "orderer.itcast.cn",// Channel parametersChannelID:     "fgjchannel",ChannelConfig: os.Getenv("GOPATH") + "/src/origins/conf/channel-artifacts/fgjchannel.tx",// Chaincode parametersChainCodeID:     "mycc",ChaincodeGoPath: os.Getenv("GOPATH"),ChaincodePath:   "origins/chaincode",OrgAdmin:        "Admin",OrgName:         "ofgj",ConfigFile:      "conf/config.yaml",// User parametersUserName: "User1",eventID:"eventInvoke",}

到此,我们只是设置好了一些与SDK相关的一些配置信息,我们还需要调用SDK—AP去完成我们上面所说的内容,新建setup.go 文件,显示内容如下:

package modelsimport ("fmt""github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt""github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry""github.com/hyperledger/fabric-sdk-go/pkg/core/config"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/fabsdk""github.com/pkg/errors"
)// SDK 初始化
func (setup *FabricSetup) Initialize() error{// 1> 利用配置文件初始化一个 SDK,我们使用 fabsdk.New 方法sdk,err := fabsdk.New(config.FromFile(setup.ConfigFile))if err!=nil{return errors.WithMessage(err,"failed to create SDK")}fmt.Println("SDK created")setup.sdk = sdk// 2> 利用第一步得到的 SDK ,创建 channel// 在创建channel之前我们需要创建一个资源管理客户端// 创建上下文resourceManagerClientContext := setup.sdk.Context(fabsdk.WithUser(setup.OrgAdmin),fabsdk.WithOrg(setup.OrgName))// 创建资源管理客户端resMgmtClient,err := resmgmt.New(resourceManagerClientContext)if err!=nil{return errors.WithMessage(err,"failed to chreate channel manager client from Admin identity!")}setup.resMgmtClient = resMgmtClientfmt.Println("Resource managerment client created!")// 3> 创建一个 MSP 客户端,相当于创建一个组织的句柄// 创建 ofgj 组织的 MSP clientmspClient, err := mspclient.New(sdk.Context(), mspclient.WithOrg(setup.OrgName))if err != nil {return errors.WithMessage(err, "failed to create MSP client")}// 对该组织中的 Admin 用户进行身份签署adminIdentity, err := mspClient.GetSigningIdentity(setup.OrgAdmin)if err != nil {return errors.WithMessage(err, "failed to get admin signing identity")}req := resmgmt.SaveChannelRequest{ChannelID:setup.channelID,ChannelConfigPath:setup.channelConfigPath,SigningIdentities:[]msp.SigningIdentity{adminIdentity}}txID,err := setup.resMgmtClient.SaveChannel(req,resmgmt.WithOrdererEndpoint(setup.OrdererID))if err!=nil||txID.TransactionID==""{return errors.WithMessage(err,"Failed to create channel!")}fmt.Println("Channel Created!")// 4> 将 MSP 句柄对应的组织节点加入到先前创建的 channel,默认会讲所有的组织添加到channel中err = setup.resMgmtClient.JoinChannel(setup.channelID,resmgmt.WithRetry(retry.DefaultResMgmtOpts),resmgmt.WithOrdererEndpoint(setup.OrdererID))if err!=nil{return errors.WithMessage(err,"Failed to join channel!")}fmt.Println("Joined channel!")fmt.Println("Initialization Successful!")return nil
}// chaincode 的安装和实例化
func (setup *FabricSetup) InstallAndInstantiateCC() error {// Create the chaincode package that will be sent to the peersccPkg, err := packager.NewCCPackage(setup.ChaincodePath, setup.ChaincodeGoPath)if err != nil {return errors.WithMessage(err, "failed to create chaincode package")}fmt.Println("ccPkg created")// Install example cc to org peersinstallCCReq := resmgmt.InstallCCRequest{Name: setup.ChainCodeID, Path: setup.ChaincodePath, Version: "0", Package: ccPkg}_, err = setup.admin.InstallCC(installCCReq, resmgmt.WithRetry(retry.DefaultResMgmtOpts))if err != nil {return errors.WithMessage(err, "failed to install chaincode")}fmt.Println("Chaincode installed")// Set up chaincode policyccPolicy := cauthdsl.SignedByAnyMember([]string{"ofgj.itcast.cn"})resp, err := setup.admin.InstantiateCC(setup.ChannelID, resmgmt.InstantiateCCRequest{Name: setup.ChainCodeID, Path: setup.ChaincodeGoPath, Version: "0", Args: [][]byte{[]byte("init")}, Policy: ccPolicy})if err != nil || resp.TransactionID == "" {return errors.WithMessage(err, "failed to instantiate the chaincode")}fmt.Println("Chaincode instantiated")// Channel client is used to query and execute transactionsclientContext := setup.sdk.ChannelContext(setup.ChannelID, fabsdk.WithUser(setup.UserName))setup.client, err = channel.New(clientContext)if err != nil {return errors.WithMessage(err, "failed to create new channel client")}fmt.Println("Channel client created")// Creation of the client which will enables access to our channel eventssetup.event, err = event.New(clientContext)if err != nil {return errors.WithMessage(err, "failed to create new event client")}fmt.Println("Event client created")fmt.Println("Chaincode Installation & Instantiation Successful")return nil
}func (setup *FabricSetup) CloseSDK() {setup.sdk.Close()
}

现在,我们的 chaincode 已经被安装在了 Fabric_Service 上,那么我们就可以在 models 层调用 SDK 完成数据的操作了,我们还以 FormHouse 为例,在models 层新建 addHouseItem.go 文件,用以将 房屋信息记入区块链,实现如下:

func (this *Application)AddHouseItem(args[]string)(string,error){tempDataMap := make(map[string][]byte)tempDataMap["result"]=[]byte("Transient data in AddHouseItem")reg,_,err := this.FabricSetup.event.RegisterChaincodeEvent(this.FabricSetup.ChainCodeID,this.FabricSetup.eventID)if err!=nil{errors.WithMessage(err,"RegisterChaincodeEvent err...")return "",err}defer this.FabricSetup.event.Unregister(reg)var tempArgs [][]bytefor i:=1;i<len(args);i++{tempArgs = append(tempArgs,[]byte(args[i]))}request := channel.Request{ChaincodeID:this.FabricSetup.ChainCodeID,Fcn:args[0],Args:[][]byte{[]byte(args[1]),[]byte(args[2]),[]byte(args[3]),[]byte(args[4]),[]byte(args[5]),[]byte(args[6]),[]byte(args[7])},TransientMap:tempDataMap}response ,err := this.FabricSetup.client.Execute(request)if err!=nil{// 资产转移失败return "",err}return string(response.TransactionID),nil
}

这时我们在 controller 层的 FormHouse() 函数就可以写成如下形式:

// 发布房源信息
func (this *MainController) FormHouse() {// 响应 /form_house 请求,将 form_house.html 页面返回给客户端this.TplName = "form_house.html"rentingID := this.GetString("rentingID")if rentingID == "" {return} else {// 获取用户输入fczbh := this.GetString("fczbh")fzxm := this.GetString("fzxm")djrq := this.GetString("djrq")zfmj := this.GetString("zfmj")fwsjyt := this.GetString("fwsjyt")sfdy := this.GetString("sfdy")// 组织参数var args []stringargs = append(args, "addHouseInfo")args = append(args, rentingID)args = append(args, fczbh)args = append(args, fzxm)args = append(args, djrq)args = append(args, zfmj)args = append(args, fwsjyt)args = append(args, sfdy)// 调用models 层业务逻辑,把数据记入超级账本ret, err := models.App.AddHouseItem(args)if err != nil {fmt.Println("AddHouseItem err...")}fmt.Println("<--- 添加房源信息结果 --->:", ret)}// 最后返回在首页this.TplName = "index.html"
}

args[1]),[]byte(args[2]),[]byte(args[3]),[]byte(args[4]),[]byte(args[5]),[]byte(args[6]),[]byte(args[7])},TransientMap:tempDataMap}
response ,err := this.FabricSetup.client.Execute(request)
if err!=nil{
// 资产转移失败
return “”,err
}

return string(response.TransactionID),nil

}


这时我们在 controller 层的 FormHouse() 函数就可以写成如下形式:```go
// 发布房源信息
func (this *MainController) FormHouse() {// 响应 /form_house 请求,将 form_house.html 页面返回给客户端this.TplName = "form_house.html"rentingID := this.GetString("rentingID")if rentingID == "" {return} else {// 获取用户输入fczbh := this.GetString("fczbh")fzxm := this.GetString("fzxm")djrq := this.GetString("djrq")zfmj := this.GetString("zfmj")fwsjyt := this.GetString("fwsjyt")sfdy := this.GetString("sfdy")// 组织参数var args []stringargs = append(args, "addHouseInfo")args = append(args, rentingID)args = append(args, fczbh)args = append(args, fzxm)args = append(args, djrq)args = append(args, zfmj)args = append(args, fwsjyt)args = append(args, sfdy)// 调用models 层业务逻辑,把数据记入超级账本ret, err := models.App.AddHouseItem(args)if err != nil {fmt.Println("AddHouseItem err...")}fmt.Println("<--- 添加房源信息结果 --->:", ret)}// 最后返回在首页this.TplName = "index.html"
}

现在,我们一个完整的流程已经跑通,剩下的功能我们按照同样的方法实现即可。

区块链房屋溯源项目搭建教程相关推荐

  1. java计步器算法_区块链+计步运动项目,主要采用以太坊、智能合约、springboot以及小程序等技术...

    runlife_api 项目介绍 区块链+计步运动项目,主要采用以太坊.智能合约.springboot以及小程序等技术 兑换步数算法 按照每总量剩余一半的时候兑换难度增加一倍,如目前按照平均步数511 ...

  2. 2018世界杯那么火,区块链预测类项目了解一下?

    北京时间7月12日,世界杯半决赛第2场在莫斯科卢日尼基球场展开较量,克罗地亚加时2比1逆转英格兰,首度打进世界杯决赛,将与法国争夺冠军. 世界杯的火爆带动了区块链预测类项目的风靡,小编为大家精心整理了 ...

  3. 20多家银行加入“新加坡-香港”区块链贸易网络项目

    点击上方"蓝色字"可关注我们! 暴走时评:新加坡和香港的金融监管局在发展金融科技方面一直比较有代表性.其联合开展的区块链交易网络项目如今迎来了20多名新成员--全部是银行和金融机构 ...

  4. 浅析区块链应用系统——区块链追踪溯源应用

    区块链发展被写入"十四五"规划标志着区块链已经上升为国家新兴技术战略.上层建筑的肯定对于区块链应用的落地打了一针催化剂. 区块链应用系统从最初的数字金融领域逐渐渗透到政务民生.供应 ...

  5. 区块链产品溯源技术应用落地

    食品安全问题层出不穷,"假货"."食品安全"问题被频频爆出,如何保障饮食安全.避免受骗上当是人们一直在研究却始终无法根治的大问题. 商品市场可谓"中毒 ...

  6. 格莱美全明星团队推出区块链音乐版权项目TUNE

    点击上方 "蓝色字" 可关注我们! 编辑:铅笔盒 近日,由格莱美大奖获得者与音乐制作人组成的全明星团队,正式推出区块链音乐版权项目TUNE,该消息一经传出便已刷爆娱乐圈.因为,这是 ...

  7. 9. Fabric2.2 区块链农产品溯源系统 - 多组织集群部署

    区块链农产品溯源系统涉及多个角色,包括农户.加工厂.物流机构.零售商.消费者,他们属于不同的组织结构,需要部署区块链节点,这里需要把消费者剔除,消费者是不会部署区块链节点的,本小节我们将构建4组织的区 ...

  8. 最新发布!2018年区块链数字货币项目最赚钱方法排行榜

    2018年大年初二,聚集了帅初.沈波.蔡文胜.徐小平.薛蛮子.陈伟星.朱啸虎.易理华.慕岩等商界大佬的微信群"三点钟无眠区块链"中对于区块链的探讨和争议,让区块链成为与春晚相媲美的 ...

  9. 快速简洁的Vue+Electron项目搭建教程

    Vue+Electron项目搭建教程 最近写一个项目,需要使用Electron集成Vue,特此记录搭建过程 Vue+Electron 常用搭建方式 在之前,通常我们会使用electron-vue脚手架 ...

最新文章

  1. apache 重定向tomcat端口_Tomcat
  2. 计算机工程 文章没有创新,【计算机基础论文】小议计算机专业人才创新力的培育(共1911字)...
  3. Firefox 44.0将在Linux上启用H.264支持:GTK3仍缺席
  4. 【试验】三个用于日常监控开发库与对应测试库的存储过程
  5. CentOS中提示rz命令找不到安装lrzsz来解决
  6. CentOS 6.x 快速安装L2TP ***
  7. 知识点 组合数学 卡特兰数
  8. python调用窗口找到文件,使用Python在Mac OS X中查找当前活动窗口
  9. EMNLP21' | 细粒度情感分析新突破 —— 通过有监督对比学习方法学习隐式情感...
  10. Kafka配置3--Windows下配置Kafka集群
  11. 怎样将数据发送到前端_大数据从哪里来?
  12. 普渡大学计算机硕士申请条件,普渡大学计算机科学硕士录取条件有哪些?需要这些申请材料...
  13. ftp服务器文件访问路径,ftp服务器访问路径格式
  14. Python学习(八):返回值变量的作用域参数的传递浅拷贝和深拷贝类对象和方法
  15. Python模块之logging详解
  16. 面试官:为啥需要枚举?枚举有什么作用?怎么用枚举实现单例?
  17. zigbee加PA信号增强方案:rfx2401+cc2530
  18. 教你这么理解 『假脱机打印机系统』
  19. pc前端js调起电脑本地应用程序(需要客户端配合 自定义URL Protocol 协议 )
  20. 致所有看到这段话的朋友们

热门文章

  1. 单声道80W/立体声40W内置DSP数字功放+升压IC蓝牙音箱
  2. 数字电视共享器的系统应用
  3. 51单片机LCD屏补充知识
  4. pygame的mask测试_作者:李兴球
  5. 烤氏历史第9集:《世通兴衰录:被遗忘的帝国》-4.大江东流
  6. 利用Python爬取各大高校信息并可视化分析,让你填志愿选学校不再迷茫
  7. 今天花了些时间捯饬了下Silverlight,可是DEBUG时出了这么个状况
  8. Unity 自动化1.0(代码,预制体生成到指定模块)
  9. leetcode589.N叉树的前序遍历C++
  10. 当男孩变成男人的时候…