在上一篇文章中实现了一个简单的用户登陆验证的Chaincode,接下实现用于记录用户收支情况的Chaincode
/**
* file:journal_chaincode.go
**/
package main
import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos/peer"
)
type JournalChaincode struct{}
type Journal struct {
Id string `json:"id"`
Date int `json:"date"`
Cost int `json:"cost"`
Label string `json:"label"`
Description string `json:"description"`
UserName string `json:"username"`
Type string `json:"type"` // expenditure/earning
}
type ResInfo struct {
Status bool `json:"status"`
Msg string `json:"msg"`
}
func (t *ResInfo) error(msg string) {
t.Status = false
t.Msg = msg
}
func (t *ResInfo) ok(msg string) {
t.Status = true
t.Msg = msg
}
func (t *ResInfo) response() pb.Response {
resJson, err := json.Marshal(t)
if err != nil {
return shim.Error("Failed to generate json result " + err.Error())
}
return shim.Success(resJson)
}
func main() {
err := shim.Start(new(JournalChaincode))
if err != nil {
fmt.Printf("Error starting Simple chaincode: %s", err)
}
}
type process func(shim.ChaincodeStubInterface, []string) *ResInfo
// Init initializes chaincode
// ===========================
func (t *JournalChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
return shim.Success(nil)
}
func (t *JournalChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
function, args := stub.GetFunctionAndParameters()
fmt.Println("invoke is running " + function)
// Handle different functions
if function == "newJournal" { //create a new marble
return t.newJournal(stub, args)
} else if function == "delJournal" {
return t.delJournal(stub, args)
} else if function == "updateJournal" {
return t.updateJournal(stub, args)
} else if function == "queryJournal" {
return t.queryJournal(stub, args)
}
fmt.Println("invoke did not find func: " + function) //error
return shim.Error("Received unknown function invocation")
}
func (t *JournalChaincode) newJournal(stub shim.ChaincodeStubInterface, args []string) pb.Response {
return t.handleProcess(stub, args, 7, func(shim.ChaincodeStubInterface, []string) *ResInfo {
ri := &ResInfo{true, ""}
_id := args[0]
_date, _ := strconv.Atoi(args[1])
_cost, _ := strconv.Atoi(args[2])
_label := args[3]
_description := args[4]
_username := args[5]
_type := args[6]
_journal := &Journal{_id, _date, _cost, _label, _description, _username, _type}
_ejson, err := json.Marshal(_journal)
if err != nil {
ri.error(err.Error())
} else {
_old, err := stub.GetState(_id)
if err != nil {
ri.error(err.Error())
} else if _old != nil {
ri.error("the journal has exists")
} else {
err := stub.PutState(_id, _ejson)
if err != nil {
ri.error(err.Error())
} else {
ri.ok("")
}
}
}
return ri
})
}
func (t *JournalChaincode) delJournal(stub shim.ChaincodeStubInterface, args []string) pb.Response {
return t.handleProcess(stub, args, 1, func(shim.ChaincodeStubInterface, []string) *ResInfo {
ri := &ResInfo{true, ""}
_id := args[0]
_journal, err := stub.GetState(_id)
if err != nil {
ri.error(err.Error())
} else {
if _journal == nil {
ri.ok("Warnning journal does not exists")
} else {
err := stub.DelState(_id)
if err != nil {
ri.error(err.Error())
} else {
ri.ok("")
}
}
}
return ri
})
}
func (t *JournalChaincode) updateJournal(stub shim.ChaincodeStubInterface, args []string) pb.Response {
return t.handleProcess(stub, args, 7, func(shim.ChaincodeStubInterface, []string) *ResInfo {
ri := &ResInfo{true, ""}
_id := args[0]
_date, _ := strconv.Atoi(args[1])
_cost, _ := strconv.Atoi(args[2])
_label := args[3]
_description := args[4]
_username := args[5]
_type := args[6]
newJournal := &Journal{_id, _date, _cost, _label, _description, _username, _type}
_journal, err := stub.GetState(_id)
if err != nil {
ri.error(err.Error())
} else {
if _journal == nil {
ri.error("Error the journal does not exists")
} else {
_ejson, err := json.Marshal(newJournal)
if err != nil {
ri.error(err.Error())
} else {
err := stub.PutState(_id, _ejson)
if err != nil {
ri.error(err.Error())
} else {
ri.ok("")
}
}
}
}
return ri
})
}
func (t *JournalChaincode) queryJournal(stub shim.ChaincodeStubInterface, args []string) pb.Response {
return t.handleProcess(stub, args, 1, func(shim.ChaincodeStubInterface, []string) *ResInfo {
ri := &ResInfo{true, ""}
queryString := args[0]
queryResults, err := getQueryResultForQueryString(stub, queryString)
if err != nil {
ri.error(err.Error())
} else {
ri.ok(string(queryResults))
}
return ri
})
}
func getQueryResultForQueryString(stub shim.ChaincodeStubInterface, queryString string) ([]byte, error) {
fmt.Printf("- getQueryResultForQueryString queryString:\n%s\n", queryString)
resultsIterator, err := stub.GetQueryResult(queryString)
if err != nil {
return nil, err
}
defer resultsIterator.Close()
// buffer is a JSON array containing QueryRecords
var buffer bytes.Buffer
buffer.WriteString("[")
bArrayMemberAlreadyWritten := false
for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return nil, err
}
// Add a comma before array members, suppress it for the first array member
if bArrayMemberAlreadyWritten == true {
buffer.WriteString(",")
}
buffer.WriteString("{\"Key\":")
buffer.WriteString("\"")
buffer.WriteString(queryResponse.Key)
buffer.WriteString("\"")
buffer.WriteString(", \"Record\":")
// Record is a JSON object, so we write as-is
buffer.WriteString(string(queryResponse.Value))
buffer.WriteString("}")
bArrayMemberAlreadyWritten = true
}
buffer.WriteString("]")
fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String())
return buffer.Bytes(), nil
}
func (t *JournalChaincode) handleProcess(stub shim.ChaincodeStubInterface, args []string, expectNum int, f process) pb.Response {
res := &ResInfo{false, ""}
err := t.checkArgs(args, expectNum)
if err != nil {
res.error(err.Error())
} else {
res = f(stub, args)
}
return res.response()
}
func (t *JournalChaincode) checkArgs(args []string, expectNum int) error {
if len(args) != expectNum {
return fmt.Errorf("Incorrect number of arguments. Expecting " + strconv.Itoa(expectNum))
}
for p := 0; p < len(args); p++ {
if len(args[p]) <= 0 {
return fmt.Errorf(strconv.Itoa(p+1) + "nd argument must be a non-empty string")
}
}
return nil
}
本文中的Chaincode与上一篇文章中最大的区别是新增了如下类型和方法
type process func(shim.ChaincodeStubInterface, []string) *ResInfo
func (t *JournalChaincode) handleProcess(stub shim.ChaincodeStubInterface, args []string, expectNum int, f process) pb.Response
上一篇文章中的Chaincode有很多重复代码,尤其是在参数检查部分,所以在本文中我们利用go语言可以将方法作为函数参数传递的特性,使用上述方法以减少重复代码。
type Journal struct {
Id string `json:"id"`
Date int `json:"date"`
Cost int `json:"cost"`
Label string `json:"label"`
Description string `json:"description"`
UserName string `json:"username"`
Type string `json:"type"` // expenditure/earning
}
这个结构体就是用于保存用户收入/支出记录的结构体。
Id 应该为每一条记录都不相同的,
Date 为记录日期,可以是时间戳或者保存日期如 20180101
Cost 为金额,注意这里用的是int 之所以不用float或者double 是因为计算机不能精确地对浮点型进行计算,所以这里用int ,如花费人民币的话,那么cost的单位就应该是是分
Label 为该记录的标签,如“餐饮”,“购物”
Description 为该记录的描述信息
Type 为类型 expenditure 表示 支出 earning 表示收入
Chaincode中提供了对记录的 “新增”、“更新”、“删除”、”查询”功能。
类似于上一篇文章,我们需要将该Chaincode目录映射的cli中。
- ~/Workspace/fabric-study/journal:/opt/gopath/src/github.com/chaincode/journal
使用以下命令进行测试
export CHANNEL_NAME=mychannel
peer chaincode install -n journal -v 1.0 -p github.com/chaincode/journal
peer chaincode instantiate -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C $CHANNEL_NAME -n journal -v 1.0 -c '{"Args":["init"]}' -P "OR ('Org0MSP.peer','Org1MSP.peer')"
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C $CHANNEL_NAME -n journal -c '{"Args":["newJournal","123456","20180102","1000","canyin","wufan","alphags","expenditure"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C $CHANNEL_NAME -n journal -c '{"Args":["newJournal","235142","20180102","1000","canyin","wufan","alphags","earning"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C $CHANNEL_NAME -n journal -c '{"Args":["updateJournal","235142","20180103","2000","canyin","wufan","alphags","earning"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C $CHANNEL_NAME -n journal -c '{"Args":["queryJournal","{\"selector\": {\"username\":\"alphags\",\"date\":{\"$gt\":20180102}}}"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C $CHANNEL_NAME -n journal -c '{"Args":["delJournal","235142"]}'
这里需要注意,我们通过Fauxton查看couchdb中的数据时可以看到,我们的记录是明文存储的,为了保证匿名性,其实我们是需要对用户名进行加密后再存储的。
至此我们准备好了两个Chaincode 一个用于用户验证,一个用于记录收支信息。接下来我们要使用nodejs 搭建一个简单的web服务器,该web服务操作通过上述两个Chaincode操作数据,并为用户提供服务。