最近接触了Hyperledger Fabric,官网给的app并没有界面,不过有一个单页版的项目,在此基础上做出了一个简易的 web app
github项目地址
功能:
- 发布食品
- 添加配料信息
- 中转食品
- 查询食品信息
- 查询配料信息
- 查询中转信息
前言
本篇文章只会对我认为比较重要地部分进行大致地讲解,不会讲解细节,如果有什么疑惑的地方请在评论区进行评论,会根据评论情况进行下一篇文章的编写
项目架构
前端使用AngularJs来进行页面渲染,后台使用Node.js返回Json数据,数据存储在Hyperledger Fabric提供的数据库
启动脚本详解
source-app/server.js
const middlewares = [
express.static(path.join(__dirname, 'public')),//静态文件
bodyParser.urlencoded({ extended: true }),//解析POST所传参数
cookieParser(),//使用Cookie来进行Flash文字演示
session({
secret: 'super-secret-key',
key: 'super-secret-cookie',
resave: false,
saveUninitialized: false,
cookie: { maxAge: 60000}
}),
flash()
]
app.use(middlewares)//激活中间件
app.use(express.static(path.join(__dirname, 'views')));//使用模板
app.set('view engine', 'ejs');//配置模板引擎
require('./routes.js')(app);
URL分为三个部分
Part 1
获取HTML
source-app/routes.js
app.get('/', function(req, res) {
tuna.index(req, res);
});
...
Part 2
表单
表单提交流程
填写表单->POST到特定的URL->处理表单信息->重定向到首页
表单的HTML代码编写
source-app/views/form.ejs
POST到特定的URL
source-app/routes.js
app.post('/re_form', function(req, res) {
var function_name = 'addProInfo'//调用chaincode中的函数
tuna.re_form(req, res, function_name);
});
表单处理
source-app/controller.js
const request = {
chaincodeId: 'source-app',
fcn: function_name,//调用chaincode中的函数
args: req.body.field1,//获取表单所填信息(函数所需参数)
chainId: 'mychannel',
txId: tx_id
};
调用Chaincode
chaincode/source-app/source-app.go
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)
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)
}
Part 3
返回Json
source-app/routes.js
app.get('/source/:id', function(req, res) {
var function_name = 'getProInfo'//调用chaincode函数
tuna.get_tuna(req, res, function_name);
});
source-app/controller.js
const request = {
chaincodeId: 'source-app',
txId: tx_id,
fcn: function_name,//调用chaincode函数
args: [key]//函数所需参数
};
调用Chaincode
chaincode/source-app/source-app.go
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)//转为Json格式
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(jsonsAsBytes)
}
例子
访问http://120.27.18.178:3389/source/1001,可以得到以下数据
{"FoodName":"苹果","FoodSpec":"123456","FoodMFGDate":"2018-8-27","FoodEXPDate":"一月","FoodLOT":"123","FoodQSID":"456","FoodMFRSName":"啦啦啦","FoodProPrice":"2","FoodProPlace":"郑州"}
AngularJs与单页页面
如果我们没有进行查询,我们希望页面呈现为这样
当我们查询之后,我们希望页面呈现为这样
HTML代码编写
source-app/views/search.ejs
食品名称
食品规格
食品生产日期
食品保质期
食品批次号
食品生产许可证编号
食品生产商名称
食品生产价格
食品生产所在地
{{query_source.FoodName}}
{{query_source.FoodSpec}}
{{query_source.FoodMFGDate}}
{{query_source.FoodEXPDate}}
{{query_source.FoodLOT}}
{{query_source.FoodQSID}}
{{query_source.FoodMFRSName}}
{{query_source.FoodProPrice}}
{{query_source.FoodProPlace}}
AngularJs脚本编写
source-app/public/js/app.js
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)
});
}
});
可以不用搞懂basic-network文件夹(我自己也不懂,不过并不影响写项目,如果读者懂,欢迎指导)
如果有什么不懂的地方或想搞懂更细节的地方,请在评论区留言,下一篇文章会依据评论来定方向
参考链接
Education
Writing Your First Application
欢迎star