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操作数据,并为用户提供服务。

你可能感兴趣的:(HyperLedger,Fabric,区块链)