基于Hyperledger Fabric的学位学历认证管理系统

1.部署环境

  • Linux Ubuntu(root 用户)
  • Golang 语言 go1.12以上均可
  • docker 18.09.7及以上
  • docker-compose 1.22.0及以上

2.环境配置

2.1安装 docker 以及 docker-compose

使用 docker 可以方便的解决程序依赖的环境问题;并且后续需要使用到的 Hyperledger Fabric 框架官方也提供了相应的 docker 的容器。 安装 docker 命令:

1
sudo apt install docker.io

好的下载完毕,为了验证 docker 成功安装结果输入下面命令

1
docker version

为了方便管理多个 docker 容器,还需要安装 docker-compose:

1
sudo apt install docker-compose

好的下载完毕,为了验证 docker-compose是否成功安装结果输入下面命令

1
docker-compose version

2.2安装 golang

区块链框架Hyperledger Fabric 目前支持Java、Go 等主流编程语言并提供了相应的SDK,但是支持最全面的还是 Golang,因此采用 Go 语言来进行开发是比较好的选择;

  1. 安装Golang:

    1
    wget https://go.dev/dl/go1.17.9.linux-amd64.tar.gz
  2. 使用 tar 命令将压缩文件解压至指定路径/usr/local/下(注意:请切换root用户下再进行解压不然会报权限不足的错误)

    1
    tar -zxvf go1.17.9.linux-amd64.tar.gz -C /usr/local
  3. sudo vim /etc/hosts,添加(没有vim可以使用vi):

    1
    2
    3
    127.0.0.1  orderer.example.com
    127.0.0.1 peer0.org1.example.com
    127.0.0.1 peer1.org1.example.com

    保存退出。

  4. sudo gedit /root/.bashrc文件,设置环境变量 GOHOME 以及 GOROOT:

    1
    2
    3
    export GOPATH=/root/go
    export GOROOT=/usr/local/go
    export PATH=$GOROOT/bin:$PATH
  5. 激活环境变量:

    1
    source /etc/profile
  6. 验证安装成功,使用 go version 结果如图所示:

3.项目部署

  1. 创建保存项目的文件夹:

    1
    mkdir -p $GOPATH/src
  2. 进入文件夹:

    1
    cd $GOPATH/src
  3. 从 github 仓库克隆项目到education文件夹:

    1
    git clone https://github.com/Pistachiout/Academic-Degree-BlockChain.git education [https://github.com/sxguan/education.git]
  4. 赋予权限并进入项目目录:

    1
    sudo chmod -R +x ./education && cd education
  5. 添加项目开发需要依赖的 Golang 包:

    1
    go mod tidy

    命令可能会执行失败,此时设置代理即可:

    1
    go env -w GOPROXY=https://goproxy.cn

4.启动项目

由于每次启动流程相对固定,首先进入root用户,并配置环境,然后启动项目在项目的目录下运行 clean_docker.sh 脚本即可启动项目:

1
2
3
4
5
6
7
8
9
10
sudo -s
export GOPATH=/root/go
export GOROOT=/usr/local/go
export PATH=$GOROOT/bin:$PATH
source /root/.bashrc
cd $GOPATH/src/education
export GO111MODULE=on
export GOPROXY=https://goproxy.io
go mod tidy
./clean_docker.sh

再开一个终端,按以下步骤启动Fabric浏览器

1
2
3
4
5
6
7
sudo -s
export GOPATH=/root/go
export GOROOT=/usr/local/go
export PATH=$GOROOT/bin:$PATH
source /root/.bashrc
cd $GOPATH/src/education/explorer
docker-compose -f docker-compose.yaml up -d

使用docker-compose down -v关闭浏览器,Ctrl + C 停止Web服务

(浏览器启动)

通过浏览器访问localhost:8080即可进入 区块链浏览器

(数据库系统启动)

通过浏览器访问 localhost:9000端口即可进入 web 端,结果如图所示:

CouchDB 数据库1:peer0节点7051端口 http://localhost:5984/_utils

CouchDB 数据库2:peer1节点9051端口 http://localhost:7984/_utils

关闭项目

Ctrl+C即可

重启项目

./clean_docker.sh(会清除所有数据)

停止项目(会清除所有数据)

1
2
3
4
5
sudo docker rm -f $(sudo docker ps -aq)
sudo docker network prune
sudo docker volume prune
cd fixtures
docker-compose down -v && docker-compose down --rmi all

强行删除容器

1
2
docker stop $(docker ps -aq)
docker rm $(docker ps -aq)

5、目录结构

  1. config.yaml配置文件:描述组织结构、通道结构、所有配置文件、证书的路径
  2. chaincode:链码文件所在的目录
    • vendor:依赖包
    • edu.go:链码(智能合约)
  3. fixtures:fabric基础网络所有文件所在的目录
  4. img:存放前端的图片
  5. sdkInit:sdk核心代码
    • sdkSetting.go:实现了包括Setup、CreateAndJoinChannel、CreateCCLifecycle等基础功能的函数
    • integration.go:实现了包括DiscoverLocalPeers、InitService等基础功能的函数
    • sdkInfo.go:定义了一些组织和SDK中用到的结构体
    • set/get.go:复制调用链码的两个函数
  6. service:服务端代码,封装了合约接口的调用
    • eduService.go:调用链码的关键所在
  7. explorer:区块链浏览器文件及配置

6、接口

登录 http://43.153.62.213:9000/login
根据身份证查询 http://43.153.62.213:9000/query2
修改信息 http://43.153.62.213:9000/modify
添加信息 http://43.153.62.213:9000/addEdu
根据证书编号和姓名查询 http://43.153.62.213:9000/query

(修改以上项目)

1、测试上链

(1)添加结构体

1
2
3
4
type User struct {
UserName string `json:"username"`
Password string `json:"password"`
}

(2) 添加service层调用链码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
func (t *ServiceSetup) SaveUser(user User) (string, error) {

eventID := "eventAddUser"
reg, notifier := regitserEvent(t.Client, t.ChaincodeID, eventID)
defer t.Client.UnregisterChaincodeEvent(reg)

// 将edu对象序列化成为字节数组
b, err := json.Marshal(user)
if err != nil {
return "", fmt.Errorf("指定的edu对象序列化时发生错误")
}

req := channel.Request{ChaincodeID: t.ChaincodeID, Fcn: "addUser", Args: [][]byte{b, []byte(eventID)}}
respone, err := t.Client.Execute(req)
if err != nil {
return "", err
}

err = eventResult(notifier, eventID)
if err != nil {
return "", err
}

return string(respone.TransactionID), nil
}



func (t *ServiceSetup) FindUserInfoByUsername(username string) ([]byte, error){

req := channel.Request{ChaincodeID: t.ChaincodeID, Fcn: "queryUserInfoByUsername", Args: [][]byte{[]byte(username)}}
respone, err := t.Client.Query(req)
if err != nil {
return []byte{0x00}, err
}

return respone.Payload, nil
}

(3)智能合约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
type User struct {
UserName string `json:"username"`
Password string `json:"password"`
}

func PutUser(stub shim.ChaincodeStubInterface, user User) ([]byte, bool) {
b, err := json.Marshal(user)
if err != nil {
return nil, false
}

// 保存edu状态
err = stub.PutState(user.UserName, b)
if err != nil {
return nil, false
}
return b, true
}

func GetUserInfo(stub shim.ChaincodeStubInterface, username string) (User, bool) {
var user User
// 根据身份证号码查询信息状态
b, err := stub.GetState(username)
if err != nil {
return user, false
}

if b == nil {
return user, false
}

// 对查询到的状态进行反序列化
err = json.Unmarshal(b, &user)
if err != nil {
return user, false
}
// 返回结果
return user, true
}


func (t *EducationChaincode) addUser(stub shim.ChaincodeStubInterface, args []string) peer.Response {

if len(args) != 2{
return shim.Error("给定的参数个数不符合要求")
}

var user User
err := json.Unmarshal([]byte(args[0]), &user)
if err != nil {
return shim.Error("反序列化信息时发生错误")
}

// 查重: 身份证号码必须唯一
_, exist := GetUserInfo(stub, user.UserName)
if exist {
return shim.Error("要添加的身份证号码已存在")
}

_, bl := PutUser(stub, user)
if !bl {
return shim.Error("保存信息时发生错误")
}

err = stub.SetEvent(args[1], []byte{})
if err != nil {
return shim.Error(err.Error())
}

return shim.Success([]byte("信息添加成功"))
}



func (t *EducationChaincode) queryUserInfoByUsername(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 1 {
return shim.Error("给定的参数个数不符合要求")
}

// 根据name查询edu状态
b, err := stub.GetState(args[0])
if err != nil {
return shim.Error("根据name查询信息失败")
}

if b == nil {
return shim.Error("根据name没有查询到相关的信息")
}

// 对查询到的状态进行反序列化
var user User
err = json.Unmarshal(b, &user)
if err != nil {
return shim.Error("反序列化user信息失败")
}

result, err := json.Marshal(user)
if err != nil {
return shim.Error("序列化user信息时发生错误")
}
return shim.Success(result)
}