1. 程式人生 > >Fabric實踐(二):使用者收入支出記錄Chaincode

Fabric實踐(二):使用者收入支出記錄Chaincode

摘要

在上一篇文章中實現了一個簡單的使用者登陸驗證的Chaincode,接下實現用於記錄使用者收支情況的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操作資料,併為使用者提供服務。