HyperLedger Node SDK-食品溯源项目实战

这篇文章将会介绍用Hyperledger node.js sdk开发一个简单的食品溯源项目流程。

本项目只能溯源食品,不能溯源食品配料

开始之前

首先,需要先明确一点,使用hyperledger node.js sdk开发简单的web app,流程和普通的node.js项目开发基本一致,不同的是我们需要编写chaincode来进行数据的增删改查,以及需要确保已安装Hyperledger Fabric v1.1。

因此,如果你稍微懂一点web开发的知识,那么本文将会很好理解。

项目架构

前端使用AngularJs构建单页web,后台使用node.js,编写chaincode来与数据进行交互

三种不同的URL

本项目总共有三种不同的URL

app.get('/', function(req, res) {
    tuna.index(req, res);
});//第一种,只获取HTML页面
app.post('/re_form', function(req, res) {
    var function_name = 'addProInfo'
    tuna.re_form(req, res, function_name);
});//第二种,获取表单信息,并进行页面重定向
app.get('/source/:id', function(req, res) {
    var function_name = 'getProInfo'
    tuna.get_tuna(req, res, function_name);
});//第三种,返回Json数据

第一种:仅仅是获取HTML界面

流程:

定义URL->定义函数处理->返回HTML页面

定义URL

app.get('/', function(req, res) {
    tuna.index(req, res);
});

定义函数处理返回HTML页面

index: function(req, res) {
    res.render('index')
},

第二种:提交表单

这一部分稍有麻烦,因为我们要编写chaincode来进行数据的增删改查,稍微有点麻烦,因为我们要对chaincode进行数据传输,这时候就需要Hyperledger Node.js SDK。

定义URL->定义函数处理(表单所填写信息我们要通过SDK传给chain code)

定义URL

app.post('/re_form', function(req, res) {
    var function_name = 'addProInfo'//调用chaincode中的addProInfo函数
    tuna.re_form(req, res, function_name);
});

表单HTML

<form method="post" action="/re_form" novalidate>
    <div class="section">食品信息</div>
    <div class="inner-wrap">
        <!--对于每一个表单信息,定义name为field1,这样我们就可以拿到一个数组-->

	<label>食品编号 <input type="text" name="field1" /></label>
        <label>食品名称 <input type="text" name="field1" /></label>
        <label>食品规格 <input type="text" name="field1" /></label>
	<label>食品生产日期 <input type="text" name="field1" /></label>
	<label>食品保质期 <input type="text" name="field1" /></label>
	<label>食品批次号 <input type="text" name="field1" /></label>
	<label>食品生产许可证编号<input type="text" name="field1" /></label>
	<label>食品生产商名称 <input type="text" name="field1" /></label>
	<label>食品生产价格<input type="text" name="field1" /></label>
	<label>食品生产所在地<input type="text" name="field1" /></label>
    </div>
    <div class="button-section">
     <input type="submit" name="提交" />
    </div>
</form>

定义函数处理,整个函数代码太长,因此只提取最关键的几行来进行说明,我们无需关心如何将数据传送到chaincode。我们只需关心,当表单提交完之后,我们要调用chaincode中的哪个函数,函数所需的参数又是哪些?

const request = {
    //targets : --- letting this default to the peers assigned to the channel
    chaincodeId: 'source-app',
    fcn: function_name, //函数名,调用chaincode中的函数
    args: req.body.field1,//参数,参数为数组
    chainId: 'mychannel',
    txId: tx_id
};
//...
req.flash('success', '发布成功!')
res.redirect('/')//重定向,并显示flash信息

是时候看一下chaincode了

type FoodInfo struct{
    FoodID string `json:FoodID`                             //食品ID
    FoodProInfo ProInfo `json:FoodProInfo`                  //生产信息
    FoodIngInfo []IngInfo `json:FoodIngInfo`                  //配料信息
    FoodLogInfo LogInfo `json:FoodLogInfo`                  //物流信息
}

type ProInfo struct{
    FoodName string `json:FoodName`                         //食品名称
    FoodSpec string `json:FoodSpec`                         //食品规格
    FoodMFGDate string `json:FoodMFGDate`                   //食品出产日期
    FoodEXPDate string `json:FoodEXPDate`                   //食品保质期
    FoodLOT string `json:FoodLOT`                           //食品批次号
    FoodQSID string `json:FoodQSID`                         //食品生产许可证编号
    FoodMFRSName string `json:FoodMFRSName`                 //食品生产商名称
    FoodProPrice string `json:FoodProPrice`                 //食品生产价格
    FoodProPlace string `json:FoodProPlace`                 //食品生产所在地
}

func (a *FoodChainCode) addProInfo(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    var err error 
    var FoodInfos FoodInfo//申请一个结构体

    if len(args)!=10{
        return shim.Error("Incorrect number of arguments.")
    }
    FoodInfos.FoodID = args[0]
    if FoodInfos.FoodID == ""{
        return shim.Error("FoodID can not be empty.")
    }
    
    
    FoodInfos.FoodProInfo.FoodName = args[1]
    FoodInfos.FoodProInfo.FoodSpec = args[2]
    FoodInfos.FoodProInfo.FoodMFGDate = args[3]
    FoodInfos.FoodProInfo.FoodEXPDate = args[4]
    FoodInfos.FoodProInfo.FoodLOT = args[5]
    FoodInfos.FoodProInfo.FoodQSID = args[6]
    FoodInfos.FoodProInfo.FoodMFRSName = args[7]
    FoodInfos.FoodProInfo.FoodProPrice = args[8]
    FoodInfos.FoodProInfo.FoodProPlace = args[9]
    ProInfosJSONasBytes,err := json.Marshal(FoodInfos)//数据转化为Json格式
    if err != nil{
        return shim.Error(err.Error())
    }

    err = stub.PutState(FoodInfos.FoodID,ProInfosJSONasBytes)//对数据进行存储
    if err != nil{
        return shim.Error(err.Error())
    }

    return shim.Success(nil)
}

第三种:返回Json数据

定义URL->定义URL处理函数(前面已经提到我们利用chaincode进行增删改查,因此我们仍需Hyperledger Node.js SDK)

定义URL

app.get('/source/:id', function(req, res) {
    var function_name = 'getProInfo'
    tuna.get_tuna(req, res, function_name);
});

定义URL处理函数(仍只需关心要调用chaincode中的哪个函数,函数所需的参数又是哪些?)

const request = {
    chaincodeId: 'source-app',
    txId: tx_id,
    fcn: function_name,//函数名
    args: [key]//函数参数
};

调用chaincode中的函数

func(a *FoodChainCode) getProInfo (stub shim.ChaincodeStubInterface,args []string) pb.Response{
    
    if len(args) != 1{
        return shim.Error("Incorrect number of arguments.")
    }
    FoodID := args[0]
    resultsIterator,err := stub.GetHistoryForKey(FoodID)
    if err != nil {
        return shim.Error(err.Error())
    }
    defer resultsIterator.Close()
    
    var foodProInfo ProInfo

    for resultsIterator.HasNext(){
        var FoodInfos FoodInfo
        response,err :=resultsIterator.Next()
        if err != nil {
            return shim.Error(err.Error())
        }
        json.Unmarshal(response.Value,&FoodInfos)
        if FoodInfos.FoodProInfo.FoodName != ""{
            foodProInfo = FoodInfos.FoodProInfo
            continue
        }
    }
    jsonsAsBytes,err := json.Marshal(foodProInfo)   
    if err != nil {
        return shim.Error(err.Error())
    }
    return shim.Success(jsonsAsBytes)
}

获取Json数据示例

AngularJs单页web

现在我们有了获取Json数据的URL,这样就可以通过AngularJs进行页面渲染

比如,对于食品查询页面,我们希望它没有进行查询前长这样

查询后,我们希望页面长这样

HTML页面

<body ng-app="application" ng-controller="appController">
	<div class="form-wrapper">
		<input type="text" id="search" placeholder="食品编号..." ng-model="query_id" required>
		<input type="submit" value="搜索" id="submit" ng-click="querySource()">
	</div>
	<table cellspacing="0" id="query_source">
		<tr>
			<th>食品名称</th>
			<th>食品规格</th>
			<th>食品生产日期</th>
			<th>食品保质期</th>
			<th>食品批次号</th>
			<th>食品生产许可证编号</th>
			<th>食品生产商名称</th>
			<th>食品生产价格</th>
			<th>食品生产所在地</th>
		</tr>
		<tr>
          		<td>{{query_source.FoodName}}</td>
          		<td>{{query_source.FoodSpec}}</td>
          		<td>{{query_source.FoodMFGDate}}</td>
          		<td>{{query_source.FoodEXPDate}}</td>
		  	<td>{{query_source.FoodLOT}}</td>
		  	<td>{{query_source.FoodQSID}}</td>
		  	<td>{{query_source.FoodMFRSName}}</td>
		  	<td>{{query_source.FoodProPrice}}</td>
		  	<td>{{query_source.FoodProPlace}}</td>
        	</tr>
	</table>
</body>

JS脚本

'use strict';

var app = angular.module('application', []);

// Angular Controller
app.controller('appController', function($scope, appFactory){

	$("#success_holder").hide();
	$("#success_create").hide();
	$("#error_holder").hide();
	$("#error_query").hide();
	$scope.querySource = function(){
		var id = $scope.query_id;
		appFactory.querySource(id, function(data){
			$scope.query_source = data;
			if ($scope.query_tuna == "Could not locate tuna"){
				console.log()
				$("#error_query").show();
			} else{
				$("#error_query").hide();
			}
		});
	}
});
app.factory('appFactory', function($http){
	var factory = {};
	factory.querySource = function(id, callback){
    	$http.get('/source/'+id).success(function(output){
			callback(output)
		});
	}
});
zhazhalaila/hyperledger-simple-appgithub.com图标

编辑于 2019-01-02

文章被以下专栏收录