beego 是一个快速开发 Go 应用的 HTTP 框架,他可以用来快速开发 API、Web 及后端服务等各种应用,是一个 RESTful 的框架,主要设计灵感来源于 tornado、sinatra 和 flask 这三个框架,但是结合了 Go 本身的一些特性(interface、struct 嵌入等)而设计的一个框架。
我们首先要做的就是设置我们的路由,然后根据路由依次实现对应的处理函数。
package routers import ( "school-credit/controllers" "github.com/astaxie/beego" ) func init() { beego.Router("/addStuInfo", &controllers.MainController{}, "post: AddStuInfo") beego.Router("/getStuInfo", &controllers.MainController{}, "get: GetStuInfo") beego.Router("/addSchInfo", &controllers.MainController{}, "post: AddSchInfo") beego.Router("/getSchInfo", &controllers.MainController{}, "get: GetSchInfo") beego.Router("/addCreInfo", &controllers.MainController{}, "post: AddCreInfo") beego.Router("/getCreInfo", &controllers.MainController{}, "get: GetCreInfo") }接下来在controller层应该以此实现如下函数:
func (c *MainController) AddStuInfo() { } func (c *MainController) GetStuInfo() { } func (c *MainController) AddSchInfo() { } func (c *MainController) GetSchInfo() { } func (c *MainController) AddCreInfo() { } func (c *MainController) GetCreInfo() { }我们以添加和查询学生基本信息为例,先获取前端传来的信息,再调用model层的SDK实现向区块中写入或读取数据,再将操作信息传给前端页面:
func (this *MainController) AddStuInfo() { //获取前端传入的数据 StuName := this.GetString("StuName") StuSex := this.GetString("StuSex") NativePlace := this.GetString("NativePlace") Birthday := this.GetString("Birthday") Nation := this.GetString("Nation") IDNum := this.GetString("IDNum") //判断ID是否为空 if IDNum == "" { return } AdmissionDate := this.GetString("AdmissionDate") GraduationTime := this.GetString("GraduationTime") SchName := this.GetString("SchName") SubName := this.GetString("SubName") //将参数放入字符串数组 var args []string args = append(args, "addStuInfo") args = append(args, StuName) args = append(args, StuSex) args = append(args, NativePlace) args = append(args, Birthday) args = append(args, Nation) args = append(args, IDNum) args = append(args, AdmissionDate) args = append(args, GraduationTime) args = append(args, SchName) args = append(args, SubName) //TODO 调用model层函数实现数据上链 } func (c *MainController) GetStuInfo() { var args []string IDNum := this.GetString("IDNum") args = append(args, "getStuInfo") args = append(args, IDNum) //TODO 调用model层函数实现链上数据查询 }接下来我们需要编写model层的功能代码,以支持上层开发
fabric go sdk是Hyperledger Fabric官方提供的Go语言开发包,应用程序可以利用fabric go sdk与fabric网络进行交互并访问链码。
首先我们需要将fabric生成的证书文件(crypto-config)以及通道文件(channel-artifacts)复制到项目目录中,后续需要使用到这些文件。 我们可以参考$GOPATH/src/github.com/hyperledger/fabric-sdk-go/test/fixtures/config/config_test.yaml下的模板文件进行SDK配置文件的编写
client使用sdk与fabric网络交互,需要告诉sdk两类信息:
我是谁:即当前client的信息,包含所属组织、密钥和证书文件的路径等, 这是每个client专用的信息。对方是谁:即fabric网络结构的信息,channel、org、orderer和peer等 的怎么组合起当前fabric网络的,这些结构信息应当与configytx.yaml中是一致的。这是通用配置,每个客户端都可以拿来使用。另外,这部分信息并不需要是完整fabric网络信息,如果当前client只和部分节点交互,那配置文件中只需要包含所使用到的网络信息。参考文章 fabric sdk go新手入门
相关配置如下:
# 配置文件的名字,可以不写 name: "school-credit-network" version: 1.0.0 # client 相关配置 client: # 此 SDK 实例属于哪个组织 organization: OrgSchool # 日志级别 logging: level: info # 证书所在目录 cryptoconfig: path: /home/surface/workspace/src/school-credit/conf/crypto-config # 这种方式就是把用户名和密码直接存储在本地的一个文件中,而用户和密码对通过一个别名来引用,这样可以避免密码明文格式可能会存在的安全问题 credentialStore: path: /tmp/schoolcredit-service-store # 区块链密码服务提供者,指定加密策略 BCCSP: security: enabled: true default: provider: "SW" hashAlgorithm: "SHA2" softVerify: true level: 256 tlsCerts: # 证书池策略,默认为false,提高身份认证速率 systemCertPool: true client: keyfile: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgschool.rjxy.cn/users/User1@orgschool.rjxy.cn/tls/client.key certfile: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgschool.rjxy.cn/users/User1@orgschool.rjxy.cn/tls/client.crt # channel 相关配置 channels: # channelID schoolchannel: # orderer 组织必须指定 orderers: - orderer.rjxy.cn # 添加到该 channel 中的组织的 peer 列表 peers: peer0.orgschool.rjxy.cn: endorsingPeer: true chaincodeQuery: true ledgerQuery: true eventSource: true peer1.orgschool.rjxy.cn: endorsingPeer: true chaincodeQuery: true ledgerQuery: true eventSource: true policies: queryChannelConfig: minResponses: 1 maxTargets: 1 retryOpts: attempts: 5 initialBackoff: 500ms maxBackoff: 5s backoffFactor: 2.0 # channelID unionchannel: # orderer 组织必须指定 orderers: - orderer.rjxy.cn # 添加到该 channel 中的组织的 peer 列表 peers: peer0.orgschool.rjxy.cn: endorsingPeer: true chaincodeQuery: true ledgerQuery: true eventSource: true peer1.orgschool.rjxy.cn: endorsingPeer: true chaincodeQuery: true ledgerQuery: true eventSource: true peer0.orgenterprise.rjxy.cn: endorsingPeer: true chaincodeQuery: true ledgerQuery: true eventSource: true peer1.orgenterprise.rjxy.cn: endorsingPeer: true chaincodeQuery: true ledgerQuery: true eventSource: true peer0.orgcredit.rjxy.cn: endorsingPeer: true chaincodeQuery: true ledgerQuery: true eventSource: true peer1.orgcredit.rjxy.cn: endorsingPeer: true chaincodeQuery: true ledgerQuery: true eventSource: true policies: queryChannelConfig: minResponses: 1 maxTargets: 1 retryOpts: attempts: 5 initialBackoff: 500ms maxBackoff: 5s backoffFactor: 2.0 # organizations 相关配置 organizations: OrgSchool: # configtx.yaml organizations -> ID mspid: OrgSchoolMSP cryptoPath: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgschool.rjxy.cn/users/{userName}@orgschool.rjxy.cn/msp peers: - peer0.orgschool.rjxy.cn - peer1.orgschool.rjxy.cn OrgEnterprise: # configtx.yaml organizations -> ID mspid: OrgEnterpriseMSP cryptoPath: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgenterprise.rjxy.cn/users/{userName}@orgenterprise.rjxy.cn/msp peers: - peer0.orgenterprise.rjxy.cn - peer1.orgenterprise.rjxy.cn OrgCredit: # configtx.yaml organizations -> ID mspid: OrgCreditMSP cryptoPath: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgcredit.rjxy.cn/users/{userName}@orgcredit.rjxy.cn/msp peers: - peer0.orgcredit.rjxy.cn - peer1.orgcredit.rjxy.cn Orderer: mspID: OrdererMSP cryptoPath: /home/surface/workspace/src/school-credit/conf/crypto-config/ordererOrganizations/rjxy.cn/users/Admin@rjxy.cn/msp # orderer 相关配置 orderers: orderer.rjxy.cn: url: localhost:7050 grpcOptions: ssl-target-name-override: orderer.rjxy.cn keep-alive-time: 0s keep-alive-timeout: 20s keep-alive-permit: false fail-fast: false allow-insecure: false tlsCACerts: path: /home/surface/workspace/src/school-credit/conf/crypto-config/ordererOrganizations/rjxy.cn/tlsca/tlsca.rjxy.cn-cert.pem # peer 相关配置 peers: peer0.orgschool.rjxy.cn: url: localhost:7051 eventUrl: localhost:7053 grpcOptions: ssl-target-name-override: peer0.orgschool.rjxy.cn keep-alive-time: 0s keep-alive-timeout: 20s keep-alive-permit: false fail-fast: false allow-insecure: false tlsCACerts: path: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgschool.rjxy.cn/tlsca/tlsca.orgschool.rjxy.cn-cert.pem peer1.orgschool.rjxy.cn: url: localhost:8051 eventUrl: localhost:8053 grpcOptions: ssl-target-name-override: peer1.orgschool.rjxy.cn keep-alive-time: 0s keep-alive-timeout: 20s keep-alive-permit: false fail-fast: false allow-insecure: false tlsCACerts: path: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgschool.rjxy.cn/tlsca/tlsca.orgschool.rjxy.cn-cert.pem peer0.orgenterprise.rjxy.cn: url: localhost:9051 eventUrl: localhost:9053 grpcOptions: ssl-target-name-override: peer0.orgenterprise.rjxy.cn keep-alive-time: 0s keep-alive-timeout: 20s keep-alive-permit: false fail-fast: false allow-insecure: false tlsCACerts: path: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgenterprise.rjxy.cn/tlsca/tlsca.orgenterprise.rjxy.cn-cert.pem peer1.orgenterprise.rjxy.cn: url: localhost:10051 eventUrl: localhost:10053 grpcOptions: ssl-target-name-override: peer1.orgenterprise.rjxy.cn keep-alive-time: 0s keep-alive-timeout: 20s keep-alive-permit: false fail-fast: false allow-insecure: false tlsCACerts: path: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgenterprise.rjxy.cn/tlsca/tlsca.orgenterprise.rjxy.cn-cert.pem peer0.orgcredit.rjxy.cn: url: localhost:11051 eventUrl: localhost:11053 grpcOptions: ssl-target-name-override: peer0.orgcredit.rjxy.cn keep-alive-time: 0s keep-alive-timeout: 20s keep-alive-permit: false fail-fast: false allow-insecure: false tlsCACerts: path: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgcredit.rjxy.cn/tlsca/tlsca.orgcredit.rjxy.cn-cert.pem peer1.orgcredit.rjxy.cn: url: localhost:12051 eventUrl: localhost:12053 grpcOptions: ssl-target-name-override: peer1.orgcredit.rjxy.cn keep-alive-time: 0s keep-alive-timeout: 20s keep-alive-permit: false fail-fast: false allow-insecure: false tlsCACerts: path: /home/surface/workspace/src/school-credit/conf/crypto-config/peerOrganizations/orgcredit.rjxy.cn/tlsca/tlsca.orgcredit.rjxy.cn-cert.pem我们在models层操作需要用到 Fabric_SDK ,所以我们首先要做的就是先拿到 SDK 句柄,创建channel,添加节点,安装链代码,实例化链代码之手,才能进行数据的操作。首先我们定义一个结构体,该结构体里面存放一些与创建Fabric-SDK 有关的配置信息,格式如下:
package models import ( "github.com/astaxie/beego" "github.com/hyperledger/fabric-sdk-go/pkg/client/channel" "github.com/hyperledger/fabric-sdk-go/pkg/client/event" "github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt" "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk" ) //定义存放与Fabric-SDK有关配置值信息的结构体 type FabricSetup struct { ConfigFile string // 初始化 SDK 对应的配置文件 OrgID string //组织节点ID OrdererID string // orderer节点 ChannelID string // ChannelID ChainCodeID string // ChainCodeID ChannelConfig string // 与channel相关的配置文件 ChaincodeGoPath string // GOPATH 环境变量 ChaincodePath string // ChainCode 存放路径 OrgAdmin string // Admin 用户名 OrgName string // 组织名称 UserName string // 普通用户名 eventID string // eventID initialized bool //是否已经初始化 client *channel.Client //pkg/client/channel支持访问Fabric网络上的通道。channel客户端实例提供与指定通道上的Peer节点进行交互的处理函数。channel客户端可以在指定通道上查询链码,执行链码以及注册或注销链码事件。如果应用程序需要与Fabric网络的多条通道进行交互,需要为每条通道创建一个单独的通道客户端实例。 resMgmtClient *resmgmt.Client //resmgmt支持在Fabric网络上创建和更新资源。resmgmt允许管理员创建、更新通道,并允许Peer节点加入通道。管理员还可以在Peer节点上执行与链码相关的操作,例如安装,实例化和升级链码。 sdk *fabsdk.FabricSDK //fabsdk是Fabric SDK的主要包,fabsdk支持客户端使用Hyperledger Fabric区块链网络。fabsdk基于配置创建上下文环境,上下文环境会在client包使用。 event *event.Client //event包支持访问Fabric网络上的通道事件。事件客户端可以接收区块事件,过滤区块事件,链码事件和交易状态事件。 } type Application struct { beego.Controller FabricSetup *FabricSetup } // 学生基本信息 type StuInfo struct { StuName string `json:"stuname"` // 学生姓名 StuSex string `json:"stusex"` // 学生性别 NativePlace string `json:"nativeplace"` //籍贯 Birthday string `json:"birthday"` // 出生日期 Nation string `json:"nation"` // 民族 IDNum string `json:"idnum"` // 身份证号 AdmissionDate string `json:"admissiondate"` // 入学日期 GraduationTime string `json:"graduationtime"` // 毕业日期 SchName string `json:"schname"` // 学校名称 SubName string `json:"subname"` // 专业名称 } // 学校记录信息 type SchInfo struct { StuGPA string `json:"stugpa"` // 学生绩点 ExcellentRecord string `json:"excellentrecord"` // 优良记录 BadRecord string `json:"badrecord"` //不良记录 StuLoan string `json:"stuloan"` // 助学贷款记录 GraduationComment string `json:"graduationcomment"` // 毕业评价 } // 征信机构信息 type CreInfo struct { BankOverdraft string `json:"bankoverdraft"` // 信用卡透支记录 AntCreditPayOverDue string `json:"antcreditpayoverdue"` // 花呗逾期记录 DidiTaxiArrears string `json:"diditaxiarrears"` //滴滴欠款记录 SesameCredit string `json:"sesamecredit"` // 芝麻信用积分 } // 学生征信档案 type StuCreInfo struct { StuInfo StuInfo `json:"stuinfo"` // 学生基本信息 SchInfo SchInfo `json:"schinfo"` // 学校记录信息 CreInfo CreInfo `json:"creinfo"` //征信机构信息 }下一步我们新建setup.go文件,在里面利用fabric-sdk-go设计FabricSetup结构体的方法,包括SDK的初始化,链码的安装和实例化以及资源的释放。代码如下:
package models import ( "fmt" "github.com/hyperledger/fabric-sdk-go/pkg/client/channel" "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" "github.com/hyperledger/fabric-sdk-go/pkg/fab/ccpackager/gopackager" "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk" "github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/common/cauthdsl" "github.com/pkg/errors" ) //初始化配置文件以及客户端方法 func (setup *FabricSetup) Initialize() error { //判断是否已经初始化 if setup.initialized == true { return errors.New("sdk already initialized") } //通过配置文件初始化SDK sdk, err := fabsdk.New(config.FromFile(setup.ConfigFile)) if err != nil { return errors.WithMessage(err, "failed to create sdk") } setup.sdk = sdk fmt.Println("SDK created") //通过 SDK 实例,基于用户和组织创建上下文[为 创建 resMgmtCli 做准备] resourceManagerClientcontext := setup.sdk.Context(fabsdk.WithUser(setup.OrgAdmin), fabsdk.WithOrg(setup.OrgName)) // 创建资源管理客户端 - resMgmtCli resMgmtClient, err := resmgmt.New(resourceManagerClientcontext) if err != nil { return errors.WithMessage(err, "failed to create channel management client from Admin identity") } setup.resMgmtClient = resMgmtClient fmt.Println("ResourceManager client created") //利用资源管理客户端,创建 channel req := resmgmt.SaveChannelRequest{ ChannelID: setup.ChannelID, ChannelConfigPath: setup.ChannelConfig, } txID, err := setup.resMgmtClient.SaveChannel(req) if err != nil || txID.TransactionID == ""{ return errors.WithMessage(err, "failed to save channel") } fmt.Println("Channel created") // 把组织添加到 channel 中的时候,一般制定一些重试的策略,和指定 orderer节点的网络位置 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("Initialize Successful!") return nil } //安装链代码以及实例化方法 func (setup *FabricSetup) InstallAndInstantiate() error { //准备安装链代码的参数 ccPkg, err := gopackager.NewCCPackage(setup.ChaincodePath, setup.ChaincodeGoPath) if err != nil { return errors.WithMessage(err, "Chaincode packager error") } installRequest := resmgmt.InstallCCRequest{ Name: setup.ChainCodeID, Path: "chaincode", Version: "1.0", Package: ccPkg, } //安装链代码 _, err = setup.resMgmtClient.InstallCC(installRequest) if err != nil { fmt.Println("chaincode install error") } fmt.Println("chanincode install success") //实例化链代码,实例化之前需要制定一个背书策略 ccpolity := cauthdsl.SignedByAnyMember([]string{"OrgSchoolMSP"}) txID, err := setup.resMgmtClient.InstantiateCC( setup.ChannelID, resmgmt.InstantiateCCRequest{ Name: setup.ChainCodeID, Path: setup.ChaincodeGoPath, Version: "1.0", Args: nil, Policy: ccpolity, }, ) if err != nil || txID.TransactionID == "" { fmt.Println("Instantiate chaincode error!") return err } fmt.Println("chaincode instantiate success!") //实例化完成之后就可以创建一个客户端client来操作账本数据了 clientContext := 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") fmt.Println("Chaincode Installation & Instantiation Successful") return nil } //资源释放方法 func (setup *FabricSetup) CloseSDK() { setup.sdk.Close() }再接下来我们创建fabricinit.go文件,完成关于FabricSetup结构体的初始化工作,代码如下:
package models import ( "fmt" "os" ) var App Application //init一旦加载,所有与创建sdk相关的一些配置就被初始化 func init(){ fSetup := FabricSetup{ ConfigFile: "conf/schoolconfig.yaml", OrgAdmin: "Admin", UserName: "User1", OrgName: "OrgSchool", ChainCodeID: "unionchaincode", ChaincodePath: "school-credit/chaincode/union", ChaincodeGoPath: os.Getenv("GOPATH"), ChannelID: "unionchannel", ChannelConfig: "/home/surface/workspace/src/school-credit/conf/channel-artifacts/unionchannel.tx", OrdererID: "OrgSchoolMSP", } //调用方法初始化sdk,创建通道,加入通道 err := fSetup.Initialize() if err != nil { fmt.Printf("Unable Initizlize SDK,%v!\n",err) return } //调用方法安装链码以及实例化链码 err = fSetup.InstallAndInstantiate() if err != nil { fmt.Printf("Unable InstallAndInstantiate,%v!\n",err) } defer fSetup.CloseSDK() App = Application{ FabricSetup : &fSetup, } }现在我们已经可以在model层利用SDK在其所对应的channel中调用链码的方法了,我们以添加学生信息为例,创建addStuInfo.go文件,其中代码如下:
package models import "github.com/hyperledger/fabric-sdk-go/pkg/client/channel" func (this *Application) AddStuInfo(args []string) (string, error) { //定义二维byte数组转换字符数组类型作为channel.Request的fcn参数 var tempArgs [][]byte for i :=1; i < len(args); i++ { tempArgs = append(tempArgs, []byte(args[i])) } request := channel.Request{ChaincodeID: this.FabricSetup.ChainCodeID, Fcn: args[0], Args: tempArgs} response, err := this.FabricSetup.client.Execute(request) if err != nil { //添加失败 return"", err } //添加成功 return string(response.TransactionID), nil }完成model层方法的编写,我们则可以在controller层调用它,完成数据的添加和查询操作。此时我们完善controller层的代码,如下:
func (this *MainController) AddStuInfo() { //获取前端传入的数据 StuName := this.GetString("StuName") StuSex := this.GetString("StuSex") NativePlace := this.GetString("NativePlace") Birthday := this.GetString("Birthday") Nation := this.GetString("Nation") IDNum := this.GetString("IDNum") //判断ID是否为空 if IDNum == "" { return } AdmissionDate := this.GetString("AdmissionDate") GraduationTime := this.GetString("GraduationTime") SchName := this.GetString("SchName") SubName := this.GetString("SubName") //将参数放入字符串数组 var args []string args = append(args, "addStuInfo") args = append(args, StuName) args = append(args, StuSex) args = append(args, NativePlace) args = append(args, Birthday) args = append(args, Nation) args = append(args, IDNum) args = append(args, AdmissionDate) args = append(args, GraduationTime) args = append(args, SchName) args = append(args, SubName) //TODO 调用model层函数实现数据上链 ret, err := models.App.AddStuInfo(args) if err != nil { fmt.Println("AddStuInfo error...") } fmt.Println("<--- 添加学生个人信息结果 --->", ret) }至此我们的后端代码已经编写完成,可以将其部署在服务器上通过web端访问,查看是否跑通,后续我们将会利用vue进行简单的前端页面设计。