链代码的设计
我们已经完成了组织节点的部署,接下来我么就要针对项目来设计相应的链代码,也就是设计想要存入区块中的数据的数据结构。在Fabric账本中数据都是以key,value的形式进行存放的,key为string类型,value可以为json格式,所以它可以很简单也可以很复杂,具体针对实际情况进行设计。
对于我们校园征信的项目,为减轻编程工作量,我们简化数据内容,以unionchannel为例,通道中中至少要保存以下数据:
数据内容参考高校学生档案管理规范:太原理工大学学生诚信档案管理办法
如图所示学生征信档案内容包含学生基本信息,学校记录信息,第三方征信平台信息。前两项数据应由学校维护,后者应由加入联盟链的第三方的征信机构维护,加入联盟的企业可通过身份证号进行档案的查询,所以该数据放在unionchannel的账本中由三方共同维护和使用。其他各组织的不公开数据由各自channel中的账本进行读写和维护。
在链码中对应的结构体声明如下:
学生基本信息应包含学生姓名,性别,籍贯,出生日期,民族,身份证号,入学日期,毕业日期,学校名称,专业名称
// 学生基本信息
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"` // 芝麻信用积分
}
将三个信息结构体封装到StuCreInfo结构体当中
// 学生征信档案
type StuCreInfo struct {
StuInfo StuInfo `json:"stuinfo"` // 学生基本信息
SchInfo SchInfo `json:"schinfo"` // 学校记录信息
CreInfo CreInfo `json:"creinfo"` //征信机构信息
}
为了降低复杂程度,我们将数据类型都设置成string类型,其他组织各自channel中要存储的数据同样参考组织实际需要进行设计,与unionchannel类似。
链代码的实现
每个链码程序都必须实现链码接口 ,接口中的方法会在响应传来的交易时被调用。Init方法会在链码接收到instantiate(实例化)或者upgrade(升级)交易时被调用,执行必要的初始化操作,包括初始化应用的状态;Invoke方法会在响应调用交易时被调用以执行交易。
链码在开发过程中需要实现链码接口,交易的类型决定了哪个接口函数将会被调用,如instantiate和upgrade类型会调用链码的Init接口,而invoke类型的交易则调用链码的Invoke接口。链码的接口定义如下:
type Chaincode interface {
Init(stub ChaincodeStubInterface) pb.Response
Invoke(stub ChaincodeStubInterface) pb.Response
}
shim.ChaincodeStubInterface接口用于访问及修改账本,并实现链码之间的互相调用,为编写链码的业务逻辑提供了大量实用的方法。
链码的必要结构如下:
package main
//引入必要的包
import(
"github.com/hyperledger/fabric/core/chaincode/shim"
pb"github.com/hyperledger/fabric/protos/peer"
)
//声明一个结构体
type SimpleChaincode struct {}
//为结构体添加Init方法
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response{
//在该方法中实现链码初始化或升级时的处理逻辑
//编写时可灵活使用stub中的API
}
//为结构体添加Invoke方法
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response{
//在该方法中实现链码运行中被调用或查询时的处理逻辑
//编写时可灵活使用stub中的API
}
//主函数,需要调用shim.Start( )方法
func main() {
err:=shim.Start(new(SimpleChaincode))
if err != nil {
fmt.Printf("Error starting Simple chaincode: %s", err)
}
}
接下来我们来实现项目的链代码:
package union
import(
"encoding/json"
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
)
//学生征信链代码
type Unionchaincode struct{
}
// 学生基本信息
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"` //征信机构信息
}
//Init函数
func (this *Unionchaincode) Init(stub shim.ChaincodeStubInterface) peer.Response{
return shim.Success([]byte(""))
}
//Invoke函数
func (this *Unionchaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response{
// 通过函数名匹配对应的链代码函数调用
function, params := stub.GetFunctionAndParameters()
if function == "addStuInfo" {
// 添加学生基本信息
return this.addStuInfo(stub, params)
} else if function == "getStuInfo" {
// 获取学生基本信息
return this.getStuInfo(stub, params)
} else if function == "addSchInfo" {
// 添加学校填写信息
return this.addSchInfo(stub, params)
} else if function == "getSchInfo" {
// 获取学校填写信息
return this.getSchInfo(stub, params)
} else if function == "addCreInfo" {
// 添加征信机构信息
return this.addCreInfo(stub, params)
} else if function == "getCreInfo" {
// 获取征信机构信息
return this.getCreInfo(stub, params)
}
// 没有任何函数被匹配到,返回错误消息
fmt.Println("==== fn = ", function)
return shim.Error("Received unknow function invocation")
}
//main函数
func main() {
err:=shim.Start(new(Unionchaincode))
if err != nil {
fmt.Printf("Error starting Union chaincode: %s", err)
}
}
//StuInfo中的IDNum作为key
func (this *Unionchaincode) addStuInfo(stub shim.ChaincodeStubInterface, params []string) peer.Response{
//定义一个学生档案结构体
var stucreinfo StuCreInfo
//判断参数个数是否正确
if len(params) !=10 {
return shim.Error("Incorrect number oof arguments.")
}
//判断身份证号是否为空
if params[5] == "" {
return shim.Error("IdNum can't be empty.")
}
//将传入的参数分别赋值给key和及结构体
key := params[5]
stucreinfo.StuInfo.StuName = params[0]
stucreinfo.StuInfo.StuSex = params[1]
stucreinfo.StuInfo.NativePlace = params[2]
stucreinfo.StuInfo.Birthday = params[3]
stucreinfo.StuInfo.Nation = params[4]
stucreinfo.StuInfo.IDNum = params[5]
stucreinfo.StuInfo.AdmissionDate = params[6]
stucreinfo.StuInfo.GraduationTime = params[7]
stucreinfo.StuInfo.SchName = params[8]
stucreinfo.StuInfo.SubName = params[9]
//将结构体转换为json格式
stucreinfobytes, err := json.Marshal(stucreinfo)
if err != nil {
shim.Error(err.Error())
}
//将key,value上传到链上
err = stub.PutState(key, stucreinfobytes)
if err != nil {
shim.Error(err.Error())
}
return shim.Success([]byte("addStuInfo: OK"))
}
func (this *Unionchaincode) getStuInfo(stub shim.ChaincodeStubInterface, params []string) peer.Response {
//判断参数个数是否正确
if len(params) != 1 {
return shim.Error("Incorrect number of arguments.")
}
//将参数赋值给key
key := params[0]
//获取历史信息迭代器
resultIterator, err := stub.GetHistoryForKey(key)
if err != nil {
shim.Error(err.Error())
}
var stuInfo StuInfo
//释放迭代器
defer resultIterator.Close()
//遍历结果集
if resultIterator.HasNext() {
var stuCreInfo StuCreInfo
response, err := resultIterator.Next()
if err != nil {
return shim.Error(err.Error())
}
err = json.Unmarshal(response.Value, &stuCreInfo)
if err != nil {
shim.Error(err.Error())
}
if stuCreInfo.StuInfo.IDNum != "" {
stuInfo = stuCreInfo.StuInfo
}
}
stuInfobytes, err := json.Marshal(stuInfo)
if err != nil {
return shim.Error(err.Error())
}
//返回StuInfo的json格式数据
return shim.Success(stuInfobytes)
}
以上代码是学生基本信息的添加与查询,其他信息的功能实现与其类似,这里不再赘述。链代码的设计和已经完成,那么Fabric端的相关工作已经完成,在后续我们将进行后端的业务逻辑的代码编写以及fabric-sdk-go的应用。