├── LICENSE ├── README.md ├── application ├── Dockerfile ├── build.sh ├── docker-compose.yml ├── server │ ├── api │ │ └── v1 │ │ │ ├── account.go │ │ │ ├── donating.go │ │ │ ├── hello.go │ │ │ ├── realEstate.go │ │ │ └── selling.go │ ├── app │ ├── blockchain │ │ └── sdk.go │ ├── config.yaml │ ├── go.mod │ ├── go.sum │ ├── main.go │ ├── model │ │ └── model.go │ ├── pkg │ │ ├── app │ │ │ └── response.go │ │ └── cron │ │ │ └── cron.go │ └── routers │ │ └── router.go ├── start.sh ├── stop.sh └── web │ ├── LICENSE │ ├── babel.config.js │ ├── build │ └── index.js │ ├── jsconfig.json │ ├── package.json │ ├── postcss.config.js │ ├── public │ ├── favicon.ico │ └── index.html │ ├── src │ ├── App.vue │ ├── api │ │ ├── account.js │ │ ├── donating.js │ │ ├── realEstate.js │ │ └── selling.js │ ├── assets │ │ └── 404_images │ │ │ ├── 404.png │ │ │ └── 404_cloud.png │ ├── components │ │ ├── Breadcrumb │ │ │ └── index.vue │ │ ├── Hamburger │ │ │ └── index.vue │ │ └── SvgIcon │ │ │ └── index.vue │ ├── icons │ │ ├── index.js │ │ ├── svg │ │ │ ├── addRealestate.svg │ │ │ ├── donating.svg │ │ │ ├── donatingAll.svg │ │ │ ├── donatingDonor.svg │ │ │ ├── donatingGrantee.svg │ │ │ ├── realestate.svg │ │ │ ├── selling.svg │ │ │ ├── sellingAll.svg │ │ │ ├── sellingBuy.svg │ │ │ └── sellingMe.svg │ │ ├── svgo.yml │ │ └── tx.png │ ├── layout │ │ ├── components │ │ │ ├── AppMain.vue │ │ │ ├── Navbar.vue │ │ │ ├── Sidebar │ │ │ │ ├── FixiOSBug.js │ │ │ │ ├── Item.vue │ │ │ │ ├── Link.vue │ │ │ │ ├── Logo.vue │ │ │ │ ├── SidebarItem.vue │ │ │ │ └── index.vue │ │ │ └── index.js │ │ ├── index.vue │ │ └── mixin │ │ │ └── ResizeHandler.js │ ├── main.js │ ├── permission.js │ ├── router │ │ └── index.js │ ├── settings.js │ ├── store │ │ ├── getters.js │ │ ├── index.js │ │ └── modules │ │ │ ├── account.js │ │ │ ├── app.js │ │ │ ├── permission.js │ │ │ └── settings.js │ ├── styles │ │ ├── element-ui.scss │ │ ├── index.scss │ │ ├── mixin.scss │ │ ├── sidebar.scss │ │ ├── transition.scss │ │ └── variables.scss │ ├── utils │ │ ├── auth.js │ │ ├── get-page-title.js │ │ ├── index.js │ │ ├── request.js │ │ └── validate.js │ └── views │ │ ├── 404.vue │ │ ├── donating │ │ ├── all │ │ │ └── index.vue │ │ ├── donor │ │ │ └── index.vue │ │ └── grantee │ │ │ └── index.vue │ │ ├── login │ │ └── index.vue │ │ ├── realestate │ │ ├── add │ │ │ └── index.vue │ │ └── list │ │ │ └── index.vue │ │ └── selling │ │ ├── all │ │ └── index.vue │ │ ├── buy │ │ └── index.vue │ │ └── me │ │ └── index.vue │ ├── vue.config.js │ └── yarn.lock ├── chaincode ├── api │ ├── account.go │ ├── donating.go │ ├── hello.go │ ├── realEstate.go │ └── selling.go ├── chaincode.go ├── chaincode_test.go ├── go.mod ├── go.sum ├── model │ └── model.go └── pkg │ └── utils │ └── fabric.go └── network ├── config ├── BENZAnchor.tx ├── appchannel.tx └── genesis.block ├── configtx.yaml ├── crypto-config.yaml ├── docker-compose-base.yaml ├── docker-compose.yaml ├── explorer ├── config.json ├── connection-profile │ ├── network.json │ └── network_temp.json ├── docker-compose.yaml ├── start.sh └── stop.sh ├── hyperledger-fabric-darwin-amd64-1.4.12 └── bin │ ├── configtxgen │ └── cryptogen ├── hyperledger-fabric-linux-amd64-1.4.12 └── bin │ ├── configtxgen │ └── cryptogen ├── start.sh └── stop.sh /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 cool 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Design-and-implementation-of-copyright-management-system-based-on-blockchain 2 | 为解决传统数字版权中心化引起的一系列易篡改、效率低、不透明的问题,本文按照软件工程的方法和流程进行分析和设计,实现了一个基于区块链存储的版权管理系统,利用区块链网络的P2P结构实现了去中心化,无需通过版权机构,即可保证版权信息的安全。利用节点间交易生成交易哈希作为数字作品的指纹,由于哈希运算解密困难,可以保证不可篡改。并且打上时间戳,确保区块信息可溯源。为版权持有者自我维 权提供了非常有力的证据,可以在极大程度上打击盗版等侵犯知识产权的不法行为。本系统利用超级账本Hyperledge Fabric 网络中节点已经达成共识的特点,相比于比特币区块链无需工作量证明即可生成凭证。利用链代码实现智能合约,智能合约中编写了自动执行、强制执行的程序,还规定了准入的节点地址,只有被承认的地址才能参与网络内的交易,具有一定的局限性。 3 | 本项目前端使用的是vue框架,后台是go语言链码,链码和fabric网络进行交互。 4 | 希望可以在学习过程中不断迭代优化这个项目。 5 | -------------------------------------------------------------------------------- /application/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.14 AS app 2 | ENV GO111MODULE=on 3 | ENV GOPROXY https://goproxy.cn,direct 4 | WORKDIR /root/togettoyou 5 | COPY server/. . 6 | RUN CGO_ENABLED=0 go build -v -o "app" . 7 | 8 | FROM node:lts-alpine AS web 9 | WORKDIR /root/togettoyou 10 | COPY web/package*.json ./ 11 | RUN yarn config set registry https://registry.npmmirror.com/ && yarn install 12 | COPY web/. . 13 | RUN yarn run build:prod 14 | 15 | FROM scratch 16 | WORKDIR /root/togettoyou/ 17 | COPY --from=app /root/togettoyou/app ./ 18 | COPY --from=app /root/togettoyou/config.yaml ./ 19 | COPY --from=web /root/togettoyou/dist/ ./dist/ 20 | ENTRYPOINT ["./app"] -------------------------------------------------------------------------------- /application/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose build -------------------------------------------------------------------------------- /application/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | 3 | networks: 4 | fabric_network: 5 | external: 6 | name: fabric_network 7 | 8 | services: 9 | fabric-realty.app: 10 | build: . 11 | image: fabric-realty/application:latest 12 | container_name: fabric-realty.app 13 | ports: 14 | - "8000:8000" 15 | volumes: 16 | - /usr/share/zoneinfo/Asia/Shanghai:/usr/share/zoneinfo/Asia/Shanghai 17 | - ./../network/crypto-config:/network/crypto-config 18 | networks: 19 | - fabric_network -------------------------------------------------------------------------------- /application/server/api/v1/account.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | bc "application/blockchain" 5 | "application/pkg/app" 6 | "bytes" 7 | "encoding/json" 8 | "fmt" 9 | "net/http" 10 | 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | type AccountIdBody struct { 15 | AccountId string `json:"accountId"` 16 | } 17 | 18 | type AccountRequestBody struct { 19 | Args []AccountIdBody `json:"args"` 20 | } 21 | 22 | func QueryAccountList(c *gin.Context) { 23 | appG := app.Gin{C: c} 24 | body := new(AccountRequestBody) 25 | //解析Body参数 26 | if err := c.ShouldBind(body); err != nil { 27 | appG.Response(http.StatusBadRequest, "失败", fmt.Sprintf("参数出错%s", err.Error())) 28 | return 29 | } 30 | var bodyBytes [][]byte 31 | for _, val := range body.Args { 32 | bodyBytes = append(bodyBytes, []byte(val.AccountId)) 33 | } 34 | //调用智能合约 35 | resp, err := bc.ChannelQuery("queryAccountList", bodyBytes) 36 | if err != nil { 37 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 38 | return 39 | } 40 | // 反序列化json 41 | var data []map[string]interface{} 42 | if err = json.Unmarshal(bytes.NewBuffer(resp.Payload).Bytes(), &data); err != nil { 43 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 44 | return 45 | } 46 | appG.Response(http.StatusOK, "成功", data) 47 | } 48 | -------------------------------------------------------------------------------- /application/server/api/v1/donating.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | bc "application/blockchain" 5 | "application/pkg/app" 6 | "bytes" 7 | "encoding/json" 8 | "fmt" 9 | "net/http" 10 | 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | type DonatingRequestBody struct { 15 | ObjectOfDonating string `json:"objectOfDonating"` //捐赠对象 16 | Donor string `json:"donor"` //捐赠人 17 | Grantee string `json:"grantee"` //受赠人 18 | } 19 | 20 | type DonatingListQueryRequestBody struct { 21 | Donor string `json:"donor"` 22 | } 23 | 24 | type DonatingListQueryByGranteeRequestBody struct { 25 | Grantee string `json:"grantee"` 26 | } 27 | 28 | type UpdateDonatingRequestBody struct { 29 | ObjectOfDonating string `json:"objectOfDonating"` //捐赠对象 30 | Donor string `json:"donor"` //捐赠人 31 | Grantee string `json:"grantee"` //受赠人 32 | Status string `json:"status"` //需要更改的状态 33 | } 34 | 35 | func CreateDonating(c *gin.Context) { 36 | appG := app.Gin{C: c} 37 | body := new(DonatingRequestBody) 38 | //解析Body参数 39 | if err := c.ShouldBind(body); err != nil { 40 | appG.Response(http.StatusBadRequest, "失败", fmt.Sprintf("参数出错%s", err.Error())) 41 | return 42 | } 43 | if body.ObjectOfDonating == "" || body.Donor == "" || body.Grantee == "" { 44 | appG.Response(http.StatusBadRequest, "失败", "ObjectOfDonating捐赠对象和Donor捐赠人和Grantee受赠人不能为空") 45 | return 46 | } 47 | var bodyBytes [][]byte 48 | bodyBytes = append(bodyBytes, []byte(body.ObjectOfDonating)) 49 | bodyBytes = append(bodyBytes, []byte(body.Donor)) 50 | bodyBytes = append(bodyBytes, []byte(body.Grantee)) 51 | //调用智能合约 52 | resp, err := bc.ChannelExecute("createDonating", bodyBytes) 53 | if err != nil { 54 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 55 | return 56 | } 57 | var data map[string]interface{} 58 | if err = json.Unmarshal(bytes.NewBuffer(resp.Payload).Bytes(), &data); err != nil { 59 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 60 | return 61 | } 62 | appG.Response(http.StatusOK, "成功", data) 63 | } 64 | 65 | func QueryDonatingList(c *gin.Context) { 66 | appG := app.Gin{C: c} 67 | body := new(DonatingListQueryRequestBody) 68 | //解析Body参数 69 | if err := c.ShouldBind(body); err != nil { 70 | appG.Response(http.StatusBadRequest, "失败", fmt.Sprintf("参数出错%s", err.Error())) 71 | return 72 | } 73 | var bodyBytes [][]byte 74 | if body.Donor != "" { 75 | bodyBytes = append(bodyBytes, []byte(body.Donor)) 76 | } 77 | //调用智能合约 78 | resp, err := bc.ChannelQuery("queryDonatingList", bodyBytes) 79 | if err != nil { 80 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 81 | return 82 | } 83 | // 反序列化json 84 | var data []map[string]interface{} 85 | if err = json.Unmarshal(bytes.NewBuffer(resp.Payload).Bytes(), &data); err != nil { 86 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 87 | return 88 | } 89 | appG.Response(http.StatusOK, "成功", data) 90 | } 91 | 92 | func QueryDonatingListByGrantee(c *gin.Context) { 93 | appG := app.Gin{C: c} 94 | body := new(DonatingListQueryByGranteeRequestBody) 95 | //解析Body参数 96 | if err := c.ShouldBind(body); err != nil { 97 | appG.Response(http.StatusBadRequest, "失败", fmt.Sprintf("参数出错%s", err.Error())) 98 | return 99 | } 100 | if body.Grantee == "" { 101 | appG.Response(http.StatusBadRequest, "失败", "必须指定AccountId查询") 102 | return 103 | } 104 | var bodyBytes [][]byte 105 | bodyBytes = append(bodyBytes, []byte(body.Grantee)) 106 | //调用智能合约 107 | resp, err := bc.ChannelQuery("queryDonatingListByGrantee", bodyBytes) 108 | if err != nil { 109 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 110 | return 111 | } 112 | // 反序列化json 113 | var data []map[string]interface{} 114 | if err = json.Unmarshal(bytes.NewBuffer(resp.Payload).Bytes(), &data); err != nil { 115 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 116 | return 117 | } 118 | appG.Response(http.StatusOK, "成功", data) 119 | } 120 | 121 | func UpdateDonating(c *gin.Context) { 122 | appG := app.Gin{C: c} 123 | body := new(UpdateDonatingRequestBody) 124 | //解析Body参数 125 | if err := c.ShouldBind(body); err != nil { 126 | appG.Response(http.StatusBadRequest, "失败", fmt.Sprintf("参数出错%s", err.Error())) 127 | return 128 | } 129 | if body.ObjectOfDonating == "" || body.Donor == "" || body.Grantee == "" || body.Status == "" { 130 | appG.Response(http.StatusBadRequest, "失败", "参数不能为空") 131 | return 132 | } 133 | var bodyBytes [][]byte 134 | bodyBytes = append(bodyBytes, []byte(body.ObjectOfDonating)) 135 | bodyBytes = append(bodyBytes, []byte(body.Donor)) 136 | bodyBytes = append(bodyBytes, []byte(body.Grantee)) 137 | bodyBytes = append(bodyBytes, []byte(body.Status)) 138 | //调用智能合约 139 | resp, err := bc.ChannelExecute("updateDonating", bodyBytes) 140 | if err != nil { 141 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 142 | return 143 | } 144 | var data map[string]interface{} 145 | if err = json.Unmarshal(bytes.NewBuffer(resp.Payload).Bytes(), &data); err != nil { 146 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 147 | return 148 | } 149 | appG.Response(http.StatusOK, "成功", data) 150 | } 151 | -------------------------------------------------------------------------------- /application/server/api/v1/hello.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "application/pkg/app" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func Hello(c *gin.Context) { 11 | appG := app.Gin{C: c} 12 | appG.Response(http.StatusOK, "成功", map[string]interface{}{ 13 | "msg": "Hello", 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /application/server/api/v1/realEstate.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | bc "application/blockchain" 5 | "application/pkg/app" 6 | "bytes" 7 | "encoding/json" 8 | "fmt" 9 | "net/http" 10 | 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | // type RealEstateRequestBody struct { 15 | // AccountId string `json:"accountId"` //操作人ID 16 | // Proprietor string `json:"proprietor"` //所有者(客户)(客户AccountId) 17 | // TotalArea float64 `json:"totalArea"` //总面积 18 | // LivingSpace float64 `json:"livingSpace"` //生活空间 19 | // } 20 | 21 | type RealEstateRequestBody struct { 22 | AccountId string `json:"accountId"` //操作人ID 23 | Proprietor string `json:"proprietor"` //所有者(客户)(客户AccountId) 24 | Carmodel string `json:"carmodel"` 25 | Car_scz string `json:"car_scz"` 26 | Car_scd string `json:"car_scd"` 27 | Car_scsj string `json:"car_scsj"` 28 | Car_lbjph string `json:"car_lbjph"` 29 | Car_lbjscz string `json:"car_lbjscz"` 30 | Car_lbjscd string `json:"car_lbjscd"` 31 | Car_lbjscsj string `json:"car_lbjscsj"` 32 | } 33 | 34 | type RealEstateQueryRequestBody struct { 35 | Proprietor string `json:"proprietor"` //所有者(客户)(客户AccountId) 36 | } 37 | 38 | func CreateRealEstate(c *gin.Context) { 39 | appG := app.Gin{C: c} 40 | body := new(RealEstateRequestBody) 41 | //解析Body参数 42 | if err := c.ShouldBind(body); err != nil { 43 | appG.Response(http.StatusBadRequest, "失败", fmt.Sprintf("参数出错%s", err.Error())) 44 | return 45 | } 46 | 47 | var bodyBytes [][]byte 48 | bodyBytes = append(bodyBytes, []byte(body.AccountId)) 49 | bodyBytes = append(bodyBytes, []byte(body.Proprietor)) 50 | bodyBytes = append(bodyBytes, []byte(body.Carmodel)) 51 | bodyBytes = append(bodyBytes, []byte(body.Car_scz)) 52 | bodyBytes = append(bodyBytes, []byte(body.Car_scd)) 53 | bodyBytes = append(bodyBytes, []byte(body.Car_scsj)) 54 | bodyBytes = append(bodyBytes, []byte(body.Car_lbjph)) 55 | bodyBytes = append(bodyBytes, []byte(body.Car_lbjscz)) 56 | bodyBytes = append(bodyBytes, []byte(body.Car_lbjscd)) 57 | bodyBytes = append(bodyBytes, []byte(body.Car_lbjscsj)) 58 | 59 | //调用智能合约 60 | resp, err := bc.ChannelExecute("createRealEstate", bodyBytes) 61 | if err != nil { 62 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 63 | return 64 | } 65 | var data map[string]interface{} 66 | if err = json.Unmarshal(bytes.NewBuffer(resp.Payload).Bytes(), &data); err != nil { 67 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 68 | return 69 | } 70 | appG.Response(http.StatusOK, "成功", data) 71 | } 72 | 73 | func QueryRealEstateList(c *gin.Context) { 74 | appG := app.Gin{C: c} 75 | body := new(RealEstateQueryRequestBody) 76 | //解析Body参数 77 | if err := c.ShouldBind(body); err != nil { 78 | appG.Response(http.StatusBadRequest, "失败", fmt.Sprintf("参数出错%s", err.Error())) 79 | return 80 | } 81 | var bodyBytes [][]byte 82 | if body.Proprietor != "" { 83 | bodyBytes = append(bodyBytes, []byte(body.Proprietor)) 84 | } 85 | //调用智能合约 86 | resp, err := bc.ChannelQuery("queryRealEstateList", bodyBytes) 87 | if err != nil { 88 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 89 | return 90 | } 91 | // 反序列化json 92 | var data []map[string]interface{} 93 | if err = json.Unmarshal(bytes.NewBuffer(resp.Payload).Bytes(), &data); err != nil { 94 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 95 | return 96 | } 97 | appG.Response(http.StatusOK, "成功", data) 98 | } 99 | -------------------------------------------------------------------------------- /application/server/api/v1/selling.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | bc "application/blockchain" 5 | "application/pkg/app" 6 | "bytes" 7 | "encoding/json" 8 | "fmt" 9 | "net/http" 10 | "strconv" 11 | 12 | "github.com/gin-gonic/gin" 13 | ) 14 | 15 | type SellingRequestBody struct { 16 | ObjectOfSale string `json:"objectOfSale"` //销售对象(正在出售的车辆RealEstateID) 17 | Seller string `json:"seller"` //发起销售人、卖家(卖家AccountId) 18 | Price float64 `json:"price"` //价格 19 | SalePeriod int `json:"salePeriod"` //智能合约的有效期(单位为天) 20 | } 21 | 22 | type SellingByBuyRequestBody struct { 23 | ObjectOfSale string `json:"objectOfSale"` //销售对象(正在出售的车辆RealEstateID) 24 | Seller string `json:"seller"` //发起销售人、卖家(卖家AccountId) 25 | Buyer string `json:"buyer"` //买家(买家AccountId) 26 | } 27 | 28 | type SellingListQueryRequestBody struct { 29 | Seller string `json:"seller"` //发起销售人、卖家(卖家AccountId) 30 | } 31 | 32 | type SellingListQueryByBuyRequestBody struct { 33 | Buyer string `json:"buyer"` //买家(买家AccountId) 34 | } 35 | 36 | type UpdateSellingRequestBody struct { 37 | ObjectOfSale string `json:"objectOfSale"` //销售对象(正在出售的车辆RealEstateID) 38 | Seller string `json:"seller"` //发起销售人、卖家(卖家AccountId) 39 | Buyer string `json:"buyer"` //买家(买家AccountId) 40 | Status string `json:"status"` //需要更改的状态 41 | } 42 | 43 | func CreateSelling(c *gin.Context) { 44 | appG := app.Gin{C: c} 45 | body := new(SellingRequestBody) 46 | //解析Body参数 47 | if err := c.ShouldBind(body); err != nil { 48 | appG.Response(http.StatusBadRequest, "失败", fmt.Sprintf("参数出错%s", err.Error())) 49 | return 50 | } 51 | if body.ObjectOfSale == "" || body.Seller == "" { 52 | appG.Response(http.StatusBadRequest, "失败", "ObjectOfSale销售对象和Seller发起销售人不能为空") 53 | return 54 | } 55 | if body.Price <= 0 || body.SalePeriod <= 0 { 56 | appG.Response(http.StatusBadRequest, "失败", "Price价格和SalePeriod智能合约的有效期(单位为天)必须大于0") 57 | return 58 | } 59 | var bodyBytes [][]byte 60 | bodyBytes = append(bodyBytes, []byte(body.ObjectOfSale)) 61 | bodyBytes = append(bodyBytes, []byte(body.Seller)) 62 | bodyBytes = append(bodyBytes, []byte(strconv.FormatFloat(body.Price, 'E', -1, 64))) 63 | bodyBytes = append(bodyBytes, []byte(strconv.Itoa(body.SalePeriod))) 64 | //调用智能合约 65 | resp, err := bc.ChannelExecute("createSelling", bodyBytes) 66 | if err != nil { 67 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 68 | return 69 | } 70 | var data map[string]interface{} 71 | if err = json.Unmarshal(bytes.NewBuffer(resp.Payload).Bytes(), &data); err != nil { 72 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 73 | return 74 | } 75 | appG.Response(http.StatusOK, "成功", data) 76 | } 77 | 78 | func CreateSellingByBuy(c *gin.Context) { 79 | appG := app.Gin{C: c} 80 | body := new(SellingByBuyRequestBody) 81 | //解析Body参数 82 | if err := c.ShouldBind(body); err != nil { 83 | appG.Response(http.StatusBadRequest, "失败", fmt.Sprintf("参数出错%s", err.Error())) 84 | return 85 | } 86 | if body.ObjectOfSale == "" || body.Seller == "" || body.Buyer == "" { 87 | appG.Response(http.StatusBadRequest, "失败", "参数不能为空") 88 | return 89 | } 90 | var bodyBytes [][]byte 91 | bodyBytes = append(bodyBytes, []byte(body.ObjectOfSale)) 92 | bodyBytes = append(bodyBytes, []byte(body.Seller)) 93 | bodyBytes = append(bodyBytes, []byte(body.Buyer)) 94 | //调用智能合约 95 | resp, err := bc.ChannelExecute("createSellingByBuy", bodyBytes) 96 | if err != nil { 97 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 98 | return 99 | } 100 | var data map[string]interface{} 101 | if err = json.Unmarshal(bytes.NewBuffer(resp.Payload).Bytes(), &data); err != nil { 102 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 103 | return 104 | } 105 | appG.Response(http.StatusOK, "成功", data) 106 | } 107 | 108 | func QuerySellingList(c *gin.Context) { 109 | appG := app.Gin{C: c} 110 | body := new(SellingListQueryRequestBody) 111 | //解析Body参数 112 | if err := c.ShouldBind(body); err != nil { 113 | appG.Response(http.StatusBadRequest, "失败", fmt.Sprintf("参数出错%s", err.Error())) 114 | return 115 | } 116 | var bodyBytes [][]byte 117 | if body.Seller != "" { 118 | bodyBytes = append(bodyBytes, []byte(body.Seller)) 119 | } 120 | //调用智能合约 121 | resp, err := bc.ChannelQuery("querySellingList", bodyBytes) 122 | if err != nil { 123 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 124 | return 125 | } 126 | // 反序列化json 127 | var data []map[string]interface{} 128 | if err = json.Unmarshal(bytes.NewBuffer(resp.Payload).Bytes(), &data); err != nil { 129 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 130 | return 131 | } 132 | appG.Response(http.StatusOK, "成功", data) 133 | } 134 | 135 | func QuerySellingListByBuyer(c *gin.Context) { 136 | appG := app.Gin{C: c} 137 | body := new(SellingListQueryByBuyRequestBody) 138 | //解析Body参数 139 | if err := c.ShouldBind(body); err != nil { 140 | appG.Response(http.StatusBadRequest, "失败", fmt.Sprintf("参数出错%s", err.Error())) 141 | return 142 | } 143 | if body.Buyer == "" { 144 | appG.Response(http.StatusBadRequest, "失败", "必须指定买家AccountId查询") 145 | return 146 | } 147 | var bodyBytes [][]byte 148 | bodyBytes = append(bodyBytes, []byte(body.Buyer)) 149 | //调用智能合约 150 | resp, err := bc.ChannelQuery("querySellingListByBuyer", bodyBytes) 151 | if err != nil { 152 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 153 | return 154 | } 155 | // 反序列化json 156 | var data []map[string]interface{} 157 | if err = json.Unmarshal(bytes.NewBuffer(resp.Payload).Bytes(), &data); err != nil { 158 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 159 | return 160 | } 161 | appG.Response(http.StatusOK, "成功", data) 162 | } 163 | 164 | func UpdateSelling(c *gin.Context) { 165 | appG := app.Gin{C: c} 166 | body := new(UpdateSellingRequestBody) 167 | //解析Body参数 168 | if err := c.ShouldBind(body); err != nil { 169 | appG.Response(http.StatusBadRequest, "失败", fmt.Sprintf("参数出错%s", err.Error())) 170 | return 171 | } 172 | if body.ObjectOfSale == "" || body.Seller == "" || body.Status == "" { 173 | appG.Response(http.StatusBadRequest, "失败", "参数不能为空") 174 | return 175 | } 176 | var bodyBytes [][]byte 177 | bodyBytes = append(bodyBytes, []byte(body.ObjectOfSale)) 178 | bodyBytes = append(bodyBytes, []byte(body.Seller)) 179 | bodyBytes = append(bodyBytes, []byte(body.Buyer)) 180 | bodyBytes = append(bodyBytes, []byte(body.Status)) 181 | //调用智能合约 182 | resp, err := bc.ChannelExecute("updateSelling", bodyBytes) 183 | if err != nil { 184 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 185 | return 186 | } 187 | var data map[string]interface{} 188 | if err = json.Unmarshal(bytes.NewBuffer(resp.Payload).Bytes(), &data); err != nil { 189 | appG.Response(http.StatusInternalServerError, "失败", err.Error()) 190 | return 191 | } 192 | appG.Response(http.StatusOK, "成功", data) 193 | } 194 | -------------------------------------------------------------------------------- /application/server/app: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HansomeZhang/Design-and-implementation-of-copyright-management-system-based-on-blockchain/6d23ad41bf023bacf01677a1fdce9eb3be787934/application/server/app -------------------------------------------------------------------------------- /application/server/blockchain/sdk.go: -------------------------------------------------------------------------------- 1 | package blockchain 2 | 3 | import ( 4 | "github.com/hyperledger/fabric-sdk-go/pkg/client/channel" 5 | "github.com/hyperledger/fabric-sdk-go/pkg/core/config" 6 | "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk" 7 | ) 8 | 9 | // 配置信息 10 | var ( 11 | sdk *fabsdk.FabricSDK // Fabric SDK 12 | configPath = "config.yaml" // 配置文件路径 13 | channelName = "appchannel" // 通道名称 14 | user = "Admin" // 用户 15 | chainCodeName = "fabric-realty" // 链码名称 16 | endpoints = []string{"peer0.tesla.com", "peer0.benz.com"} // 要发送交易的节点 17 | ) 18 | 19 | // Init 初始化 20 | func Init() { 21 | var err error 22 | // 通过配置文件初始化SDK 23 | sdk, err = fabsdk.New(config.FromFile(configPath)) 24 | if err != nil { 25 | panic(err) 26 | } 27 | } 28 | 29 | // ChannelExecute 区块链交互 30 | func ChannelExecute(fcn string, args [][]byte) (channel.Response, error) { 31 | // 创建客户端,表明在通道的身份 32 | ctx := sdk.ChannelContext(channelName, fabsdk.WithUser(user)) 33 | cli, err := channel.New(ctx) 34 | if err != nil { 35 | return channel.Response{}, err 36 | } 37 | // 对区块链账本的写操作(调用了链码的invoke) 38 | resp, err := cli.Execute(channel.Request{ 39 | ChaincodeID: chainCodeName, 40 | Fcn: fcn, 41 | Args: args, 42 | }, channel.WithTargetEndpoints(endpoints...)) 43 | if err != nil { 44 | return channel.Response{}, err 45 | } 46 | //返回链码执行后的结果 47 | return resp, nil 48 | } 49 | 50 | // ChannelQuery 区块链查询 51 | func ChannelQuery(fcn string, args [][]byte) (channel.Response, error) { 52 | // 创建客户端,表明在通道的身份 53 | ctx := sdk.ChannelContext(channelName, fabsdk.WithUser(user)) 54 | cli, err := channel.New(ctx) 55 | if err != nil { 56 | return channel.Response{}, err 57 | } 58 | // 对区块链账本查询的操作(调用了链码的invoke),只返回结果 59 | resp, err := cli.Query(channel.Request{ 60 | ChaincodeID: chainCodeName, 61 | Fcn: fcn, 62 | Args: args, 63 | }, channel.WithTargetEndpoints(endpoints...)) 64 | if err != nil { 65 | return channel.Response{}, err 66 | } 67 | //返回链码执行后的结果 68 | return resp, nil 69 | } 70 | -------------------------------------------------------------------------------- /application/server/config.yaml: -------------------------------------------------------------------------------- 1 | version: 1.0.0 2 | 3 | # GO SDK使用的客户端部分。 4 | client: 5 | # 客户端所属的组织,必须是organizations定义的组织 6 | organization: TESLA 7 | # 日志级别 8 | logging: 9 | level: info 10 | # MSP证书的根路径 11 | cryptoconfig: 12 | path: /network/crypto-config 13 | 14 | # 通道定义 15 | channels: 16 | appchannel: 17 | orderers: 18 | - orderer.carunion.com 19 | peers: 20 | peer0.tesla.com: 21 | endorsingPeer: true 22 | chaincodeQuery: true 23 | ledgerQuery: true 24 | eventSource: true 25 | peer1.tesla.com: 26 | endorsingPeer: true 27 | chaincodeQuery: true 28 | ledgerQuery: true 29 | eventSource: true 30 | 31 | # 组织配置 32 | organizations: 33 | TESLA: 34 | mspid: "TESLAMSP" 35 | cryptoPath: peerOrganizations/tesla.com/users/{username}@tesla.com/msp 36 | peers: 37 | - peer0.tesla.com 38 | - peer1.tesla.com 39 | 40 | # orderer节点列表 41 | orderers: 42 | orderer.carunion.com: 43 | url: orderer.carunion.com:7050 44 | # 传递给gRPC客户端构造函数 45 | grpcOptions: 46 | ssl-target-name-override: orderer.carunion.com 47 | keep-alive-time: 0s 48 | keep-alive-timeout: 20s 49 | keep-alive-permit: false 50 | fail-fast: false 51 | allow-insecure: true 52 | 53 | # peers节点列表 54 | peers: 55 | # peer节点定义,可以定义多个 56 | peer0.tesla.com: 57 | # URL用于发送背书和查询请求 58 | url: peer0.tesla.com:7051 59 | # 传递给gRPC客户端构造函数 60 | grpcOptions: 61 | ssl-target-name-override: peer0.tesla.com 62 | keep-alive-time: 0s 63 | keep-alive-timeout: 20s 64 | keep-alive-permit: false 65 | fail-fast: false 66 | allow-insecure: true 67 | peer1.tesla.com: 68 | url: peer1.tesla.com:7051 69 | grpcOptions: 70 | ssl-target-name-override: peer1.tesla.com 71 | keep-alive-time: 0s 72 | keep-alive-timeout: 20s 73 | keep-alive-permit: false 74 | fail-fast: false 75 | allow-insecure: true 76 | peer0.benz.com: 77 | url: peer0.benz.com:7051 78 | grpcOptions: 79 | ssl-target-name-override: peer0.benz.com 80 | keep-alive-time: 0s 81 | keep-alive-timeout: 20s 82 | keep-alive-permit: false 83 | fail-fast: false 84 | allow-insecure: true 85 | peer1.benz.com: 86 | url: peer1.benz.com:7051 87 | grpcOptions: 88 | ssl-target-name-override: peer1.benz.com 89 | keep-alive-time: 0s 90 | keep-alive-timeout: 20s 91 | keep-alive-permit: false 92 | fail-fast: false 93 | allow-insecure: true -------------------------------------------------------------------------------- /application/server/go.mod: -------------------------------------------------------------------------------- 1 | module application 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.7.1 7 | github.com/hyperledger/fabric-sdk-go v1.0.0 8 | github.com/robfig/cron/v3 v3.0.1 9 | ) 10 | -------------------------------------------------------------------------------- /application/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "time" 8 | 9 | "application/blockchain" 10 | "application/pkg/cron" 11 | "application/routers" 12 | ) 13 | 14 | func main() { 15 | timeLocal, err := time.LoadLocation("Asia/Shanghai") 16 | if err != nil { 17 | log.Printf("时区设置失败 %s", err) 18 | } 19 | time.Local = timeLocal 20 | 21 | blockchain.Init() 22 | go cron.Init() 23 | 24 | endPoint := fmt.Sprintf("0.0.0.0:%d", 8000) 25 | server := &http.Server{ 26 | Addr: endPoint, 27 | Handler: routers.InitRouter(), 28 | } 29 | log.Printf("[info] start http server listening %s", endPoint) 30 | if err := server.ListenAndServe(); err != nil { 31 | log.Printf("start http server failed %s", err) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /application/server/model/model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // Selling 销售要约 4 | // 需要确定ObjectOfSale是否属于Seller 5 | // 买家初始为空 6 | // Seller和ObjectOfSale一起作为复合键,保证可以通过seller查询到名下所有发起的销售 7 | type Selling struct { 8 | ObjectOfSale string `json:"objectOfSale"` //销售对象(正在出售的车辆RealEstateID) 9 | Seller string `json:"seller"` //发起销售人、卖家(卖家AccountId) 10 | Buyer string `json:"buyer"` //参与销售人、买家(买家AccountId) 11 | Price float64 `json:"price"` //价格 12 | CreateTime string `json:"createTime"` //创建时间 13 | SalePeriod int `json:"salePeriod"` //智能合约的有效期(单位为天) 14 | SellingStatus string `json:"sellingStatus"` //销售状态 15 | } 16 | 17 | // SellingStatusConstant 销售状态 18 | var SellingStatusConstant = func() map[string]string { 19 | return map[string]string{ 20 | "saleStart": "销售中", //正在销售状态,等待买家光顾 21 | "cancelled": "已取消", //被卖家取消销售或买家退款操作导致取消 22 | "expired": "已过期", //销售期限到期 23 | "delivery": "交付中", //买家买下并付款,处于等待卖家确认收款状态,如若卖家未能确认收款,买家可以取消并退款 24 | "done": "完成", //卖家确认接收资金,交易完成 25 | } 26 | } 27 | 28 | // Donating 捐赠要约 29 | // 需要确定ObjectOfDonating是否属于Donor 30 | // 需要指定受赠人Grantee,并等待受赠人同意接收 31 | type Donating struct { 32 | ObjectOfDonating string `json:"objectOfDonating"` //捐赠对象(正在捐赠的车辆RealEstateID) 33 | Donor string `json:"donor"` //捐赠人(捐赠人AccountId) 34 | Grantee string `json:"grantee"` //受赠人(受赠人AccountId) 35 | CreateTime string `json:"createTime"` //创建时间 36 | DonatingStatus string `json:"donatingStatus"` //捐赠状态 37 | } 38 | 39 | // DonatingStatusConstant 捐赠状态 40 | var DonatingStatusConstant = func() map[string]string { 41 | return map[string]string{ 42 | "donatingStart": "捐赠中", //捐赠人发起捐赠合约,等待受赠人确认受赠 43 | "cancelled": "已取消", //捐赠人在受赠人确认受赠之前取消捐赠或受赠人取消接收受赠 44 | "done": "完成", //受赠人确认接收,交易完成 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /application/server/pkg/app/response.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | type Gin struct { 8 | C *gin.Context 9 | } 10 | 11 | type Response struct { 12 | Code int `json:"code"` 13 | Msg string `json:"msg"` 14 | Data interface{} `json:"data"` 15 | } 16 | 17 | func (g *Gin) Response(httpCode int, errMsg string, data interface{}) { 18 | g.C.JSON(httpCode, Response{ 19 | Code: httpCode, 20 | Msg: errMsg, 21 | Data: data, 22 | }) 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /application/server/pkg/cron/cron.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "time" 9 | 10 | bc "application/blockchain" 11 | "application/model" 12 | 13 | "github.com/robfig/cron/v3" 14 | ) 15 | 16 | const spec = "0 0 0 * * ?" // 每天0点执行 17 | //const spec = "*/10 * * * * ?" //10秒执行一次,用于测试 18 | 19 | func Init() { 20 | c := cron.New(cron.WithSeconds()) //支持到秒级别 21 | _, err := c.AddFunc(spec, GoRun) 22 | if err != nil { 23 | log.Printf("定时任务开启失败 %s", err) 24 | } 25 | c.Start() 26 | log.Printf("定时任务已开启") 27 | select {} 28 | } 29 | 30 | func GoRun() { 31 | log.Printf("定时任务已启动") 32 | //先把所有销售查询出来 33 | resp, err := bc.ChannelQuery("querySellingList", [][]byte{}) //调用智能合约 34 | if err != nil { 35 | log.Printf("定时任务-querySellingList失败%s", err.Error()) 36 | return 37 | } 38 | // 反序列化json 39 | var data []model.Selling 40 | if err = json.Unmarshal(bytes.NewBuffer(resp.Payload).Bytes(), &data); err != nil { 41 | log.Printf("定时任务-反序列化json失败%s", err.Error()) 42 | return 43 | } 44 | for _, v := range data { 45 | //把状态为销售中和交付中的筛选出来 46 | if v.SellingStatus == model.SellingStatusConstant()["saleStart"] || 47 | v.SellingStatus == model.SellingStatusConstant()["delivery"] { 48 | //有效期天数 49 | day, _ := time.ParseDuration(fmt.Sprintf("%dh", v.SalePeriod*24)) 50 | local, _ := time.LoadLocation("Local") 51 | t, _ := time.ParseInLocation("2006-01-02 15:04:05", v.CreateTime, local) 52 | vTime := t.Add(day) 53 | //如果 time.Now()大于 vTime 说明过期 54 | if time.Now().Local().After(vTime) { 55 | //将状态更改为已过期 56 | var bodyBytes [][]byte 57 | bodyBytes = append(bodyBytes, []byte(v.ObjectOfSale)) 58 | bodyBytes = append(bodyBytes, []byte(v.Seller)) 59 | bodyBytes = append(bodyBytes, []byte(v.Buyer)) 60 | bodyBytes = append(bodyBytes, []byte("expired")) 61 | //调用智能合约 62 | resp, err := bc.ChannelExecute("updateSelling", bodyBytes) 63 | if err != nil { 64 | return 65 | } 66 | var data map[string]interface{} 67 | if err = json.Unmarshal(bytes.NewBuffer(resp.Payload).Bytes(), &data); err != nil { 68 | return 69 | } 70 | fmt.Println(data) 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /application/server/routers/router.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | v1 "application/api/v1" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | // InitRouter 初始化路由信息 11 | func InitRouter() *gin.Engine { 12 | r := gin.Default() 13 | 14 | apiV1 := r.Group("/api/v1") 15 | { 16 | apiV1.GET("/hello", v1.Hello) 17 | apiV1.POST("/queryAccountList", v1.QueryAccountList) 18 | apiV1.POST("/createRealEstate", v1.CreateRealEstate) 19 | apiV1.POST("/queryRealEstateList", v1.QueryRealEstateList) 20 | apiV1.POST("/createSelling", v1.CreateSelling) 21 | apiV1.POST("/createSellingByBuy", v1.CreateSellingByBuy) 22 | apiV1.POST("/querySellingList", v1.QuerySellingList) 23 | apiV1.POST("/querySellingListByBuyer", v1.QuerySellingListByBuyer) 24 | apiV1.POST("/updateSelling", v1.UpdateSelling) 25 | apiV1.POST("/createDonating", v1.CreateDonating) 26 | apiV1.POST("/queryDonatingList", v1.QueryDonatingList) 27 | apiV1.POST("/queryDonatingListByGrantee", v1.QueryDonatingListByGrantee) 28 | apiV1.POST("/updateDonating", v1.UpdateDonating) 29 | } 30 | // 静态文件路由 31 | r.StaticFS("/web", http.Dir("./dist/")) 32 | return r 33 | } 34 | -------------------------------------------------------------------------------- /application/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose up -d -------------------------------------------------------------------------------- /application/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose down -------------------------------------------------------------------------------- /application/web/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-present PanJiaChen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /application/web/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /application/web/build/index.js: -------------------------------------------------------------------------------- 1 | const { run } = require('runjs') 2 | const chalk = require('chalk') 3 | const config = require('../vue.config.js') 4 | const rawArgv = process.argv.slice(2) 5 | const args = rawArgv.join(' ') 6 | 7 | if (process.env.npm_config_preview || rawArgv.includes('--preview')) { 8 | const report = rawArgv.includes('--report') 9 | 10 | run(`vue-cli-service build ${args}`) 11 | 12 | const port = 9526 13 | const publicPath = config.publicPath 14 | 15 | var connect = require('connect') 16 | var serveStatic = require('serve-static') 17 | const app = connect() 18 | 19 | app.use( 20 | publicPath, 21 | serveStatic('./dist', { 22 | index: ['index.html', '/'] 23 | }) 24 | ) 25 | 26 | app.listen(port, function () { 27 | console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`)) 28 | if (report) { 29 | console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`)) 30 | } 31 | 32 | }) 33 | } else { 34 | run(`vue-cli-service build ${args}`) 35 | } 36 | -------------------------------------------------------------------------------- /application/web/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@/*": ["src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "dist"] 9 | } 10 | -------------------------------------------------------------------------------- /application/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-admin-template", 3 | "version": "4.2.1", 4 | "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint", 5 | "author": "Pan ", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "vue-cli-service serve", 9 | "build:prod": "vue-cli-service build", 10 | "build:stage": "vue-cli-service build --mode staging", 11 | "preview": "node build/index.js --preview", 12 | "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml" 13 | }, 14 | "dependencies": { 15 | "axios": "0.18.1", 16 | "element-ui": "2.13.0", 17 | "js-cookie": "2.2.0", 18 | "normalize.css": "7.0.0", 19 | "nprogress": "0.2.0", 20 | "path-to-regexp": "2.4.0", 21 | "vue": "2.6.10", 22 | "vue-router": "3.0.6", 23 | "vuex": "3.1.0" 24 | }, 25 | "devDependencies": { 26 | "@vue/cli-plugin-babel": "4.4.4", 27 | "@vue/cli-service": "4.4.4", 28 | "autoprefixer": "9.5.1", 29 | "babel-plugin-dynamic-import-node": "2.3.3", 30 | "chalk": "2.4.2", 31 | "connect": "3.6.6", 32 | "html-webpack-plugin": "3.2.0", 33 | "runjs": "4.3.2", 34 | "sass": "1.26.8", 35 | "sass-loader": "8.0.2", 36 | "script-ext-html-webpack-plugin": "2.1.3", 37 | "serve-static": "1.13.2", 38 | "svg-sprite-loader": "4.1.3", 39 | "svgo": "1.2.2", 40 | "vue-template-compiler": "2.6.10" 41 | }, 42 | "engines": { 43 | "node": ">=8.9", 44 | "npm": ">= 3.0.0" 45 | }, 46 | "browserslist": [ 47 | "> 1%", 48 | "last 2 versions" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /application/web/postcss.config.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | 'plugins': { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | 'autoprefixer': {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /application/web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HansomeZhang/Design-and-implementation-of-copyright-management-system-based-on-blockchain/6d23ad41bf023bacf01677a1fdce9eb3be787934/application/web/public/favicon.ico -------------------------------------------------------------------------------- /application/web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%= webpackConfig.name %> 10 | 11 | 12 | 13 | 17 |
18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /application/web/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /application/web/src/api/account.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 获取登录界面角色选择列表 4 | export function queryAccountList() { 5 | return request({ 6 | url: '/queryAccountList', 7 | method: 'post' 8 | }) 9 | } 10 | 11 | // 登录 12 | export function login(data) { 13 | return request({ 14 | url: '/queryAccountList', 15 | method: 'post', 16 | data 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /application/web/src/api/donating.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 查询捐赠列表(可查询所有,也可根据发起捐赠人查询) 4 | export function queryDonatingList(data) { 5 | return request({ 6 | url: '/queryDonatingList', 7 | method: 'post', 8 | data 9 | }) 10 | } 11 | 12 | // 根据受赠人(受赠人AccountId)查询捐赠(受赠的)(供受赠人查询) 13 | export function queryDonatingListByGrantee(data) { 14 | return request({ 15 | url: '/queryDonatingListByGrantee', 16 | method: 'post', 17 | data 18 | }) 19 | } 20 | 21 | // 更新捐赠状态(确认受赠、取消) Status取值为 完成"done"、取消"cancelled" 22 | export function updateDonating(data) { 23 | return request({ 24 | url: '/updateDonating', 25 | method: 'post', 26 | data 27 | }) 28 | } 29 | 30 | // 发起捐赠 31 | export function createDonating(data) { 32 | return request({ 33 | url: '/createDonating', 34 | method: 'post', 35 | data 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /application/web/src/api/realEstate.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 新建车辆(管理员) 4 | export function createRealEstate(data) { 5 | return request({ 6 | url: '/createRealEstate', 7 | method: 'post', 8 | data 9 | }) 10 | } 11 | 12 | // 获取商品信息(空json{}可以查询所有,指定proprietor可以查询指定客户名下车辆) 13 | export function queryRealEstateList(data) { 14 | return request({ 15 | url: '/queryRealEstateList', 16 | method: 'post', 17 | data 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /application/web/src/api/selling.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 查询销售(可查询所有,也可根据发起销售人查询)(发起的) 4 | export function querySellingList(data) { 5 | return request({ 6 | url: '/querySellingList', 7 | method: 'post', 8 | data 9 | }) 10 | } 11 | 12 | // 根据参与销售人、买家(买家AccountId)查询销售(参与的) 13 | export function querySellingListByBuyer(data) { 14 | return request({ 15 | url: '/querySellingListByBuyer', 16 | method: 'post', 17 | data 18 | }) 19 | } 20 | 21 | // 买家购买 22 | export function createSellingByBuy(data) { 23 | return request({ 24 | url: '/createSellingByBuy', 25 | method: 'post', 26 | data 27 | }) 28 | } 29 | 30 | // 更新销售状态(买家确认、买卖家取消)Status取值为 完成"done"、取消"cancelled" 当处于销售中状态,卖家要取消时,buyer为""空 31 | export function updateSelling(data) { 32 | return request({ 33 | url: '/updateSelling', 34 | method: 'post', 35 | data 36 | }) 37 | } 38 | 39 | // 发起销售 40 | export function createSelling(data) { 41 | return request({ 42 | url: '/createSelling', 43 | method: 'post', 44 | data 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /application/web/src/assets/404_images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HansomeZhang/Design-and-implementation-of-copyright-management-system-based-on-blockchain/6d23ad41bf023bacf01677a1fdce9eb3be787934/application/web/src/assets/404_images/404.png -------------------------------------------------------------------------------- /application/web/src/assets/404_images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HansomeZhang/Design-and-implementation-of-copyright-management-system-based-on-blockchain/6d23ad41bf023bacf01677a1fdce9eb3be787934/application/web/src/assets/404_images/404_cloud.png -------------------------------------------------------------------------------- /application/web/src/components/Breadcrumb/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 65 | 66 | 79 | -------------------------------------------------------------------------------- /application/web/src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 32 | 33 | 45 | -------------------------------------------------------------------------------- /application/web/src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 47 | 48 | 63 | -------------------------------------------------------------------------------- /application/web/src/icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import SvgIcon from '@/components/SvgIcon'// svg component 3 | 4 | // register globally 5 | Vue.component('svg-icon', SvgIcon) 6 | 7 | const req = require.context('./svg', false, /\.svg$/) 8 | const requireAll = requireContext => requireContext.keys().map(requireContext) 9 | requireAll(req) 10 | -------------------------------------------------------------------------------- /application/web/src/icons/svg/addRealestate.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /application/web/src/icons/svg/donating.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /application/web/src/icons/svg/donatingAll.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /application/web/src/icons/svg/donatingDonor.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /application/web/src/icons/svg/donatingGrantee.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /application/web/src/icons/svg/realestate.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /application/web/src/icons/svg/selling.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /application/web/src/icons/svg/sellingAll.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /application/web/src/icons/svg/sellingBuy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /application/web/src/icons/svg/sellingMe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /application/web/src/icons/svgo.yml: -------------------------------------------------------------------------------- 1 | # replace default config 2 | 3 | # multipass: true 4 | # full: true 5 | 6 | plugins: 7 | 8 | # - name 9 | # 10 | # or: 11 | # - name: false 12 | # - name: true 13 | # 14 | # or: 15 | # - name: 16 | # param1: 1 17 | # param2: 2 18 | 19 | - removeAttrs: 20 | attrs: 21 | - 'fill' 22 | - 'fill-rule' 23 | -------------------------------------------------------------------------------- /application/web/src/icons/tx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HansomeZhang/Design-and-implementation-of-copyright-management-system-based-on-blockchain/6d23ad41bf023bacf01677a1fdce9eb3be787934/application/web/src/icons/tx.png -------------------------------------------------------------------------------- /application/web/src/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | 20 | 32 | 33 | 41 | -------------------------------------------------------------------------------- /application/web/src/layout/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 64 | 65 | 143 | -------------------------------------------------------------------------------- /application/web/src/layout/components/Sidebar/FixiOSBug.js: -------------------------------------------------------------------------------- 1 | export default { 2 | computed: { 3 | device() { 4 | return this.$store.state.app.device 5 | } 6 | }, 7 | mounted() { 8 | // In order to fix the click on menu on the ios device will trigger the mouseleave bug 9 | // https://github.com/PanJiaChen/vue-element-admin/issues/1135 10 | this.fixBugIniOS() 11 | }, 12 | methods: { 13 | fixBugIniOS() { 14 | const $subMenu = this.$refs.subMenu 15 | if ($subMenu) { 16 | const handleMouseleave = $subMenu.handleMouseleave 17 | $subMenu.handleMouseleave = (e) => { 18 | if (this.device === 'mobile') { 19 | return 20 | } 21 | handleMouseleave(e) 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /application/web/src/layout/components/Sidebar/Item.vue: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /application/web/src/layout/components/Sidebar/Link.vue: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 37 | -------------------------------------------------------------------------------- /application/web/src/layout/components/Sidebar/Logo.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 33 | 34 | 83 | -------------------------------------------------------------------------------- /application/web/src/layout/components/Sidebar/SidebarItem.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 96 | -------------------------------------------------------------------------------- /application/web/src/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 55 | -------------------------------------------------------------------------------- /application/web/src/layout/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navbar } from './Navbar' 2 | export { default as Sidebar } from './Sidebar' 3 | export { default as AppMain } from './AppMain' 4 | -------------------------------------------------------------------------------- /application/web/src/layout/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 52 | 53 | 94 | -------------------------------------------------------------------------------- /application/web/src/layout/mixin/ResizeHandler.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | const { body } = document 4 | const WIDTH = 992 // refer to Bootstrap's responsive design 5 | 6 | export default { 7 | watch: { 8 | $route(route) { 9 | if (this.device === 'mobile' && this.sidebar.opened) { 10 | store.dispatch('app/closeSideBar', { withoutAnimation: false }) 11 | } 12 | } 13 | }, 14 | beforeMount() { 15 | window.addEventListener('resize', this.$_resizeHandler) 16 | }, 17 | beforeDestroy() { 18 | window.removeEventListener('resize', this.$_resizeHandler) 19 | }, 20 | mounted() { 21 | const isMobile = this.$_isMobile() 22 | if (isMobile) { 23 | store.dispatch('app/toggleDevice', 'mobile') 24 | store.dispatch('app/closeSideBar', { withoutAnimation: true }) 25 | } 26 | }, 27 | methods: { 28 | // use $_ for mixins properties 29 | // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential 30 | $_isMobile() { 31 | const rect = body.getBoundingClientRect() 32 | return rect.width - 1 < WIDTH 33 | }, 34 | $_resizeHandler() { 35 | if (!document.hidden) { 36 | const isMobile = this.$_isMobile() 37 | store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop') 38 | 39 | if (isMobile) { 40 | store.dispatch('app/closeSideBar', { withoutAnimation: true }) 41 | } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /application/web/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import 'normalize.css/normalize.css' // A modern alternative to CSS resets 4 | 5 | import ElementUI from 'element-ui' 6 | import 'element-ui/lib/theme-chalk/index.css' 7 | // import locale from 'element-ui/lib/locale/lang/en' // lang i18n 8 | 9 | import '@/styles/index.scss' // global css 10 | 11 | import App from './App' 12 | import store from './store' 13 | import router from './router' 14 | 15 | import '@/icons' // icon 16 | import '@/permission' // permission control 17 | 18 | /** 19 | * If you don't want to use mock-server 20 | * you want to use MockJs for mock api 21 | * you can execute: mockXHR() 22 | * 23 | * Currently MockJs will be used in the production environment, 24 | * please remove it before going online ! ! ! 25 | */ 26 | // if (process.env.NODE_ENV === 'production') { 27 | // const { mockXHR } = require('../mock') 28 | // mockXHR() 29 | // } 30 | 31 | // set ElementUI lang to EN 32 | // Vue.use(ElementUI, { locale }) 33 | // 如果想要中文版 element-ui,按如下方式声明 34 | Vue.use(ElementUI) 35 | 36 | Vue.config.productionTip = false 37 | 38 | new Vue({ 39 | el: '#app', 40 | router, 41 | store, 42 | render: h => h(App) 43 | }) 44 | -------------------------------------------------------------------------------- /application/web/src/permission.js: -------------------------------------------------------------------------------- 1 | import router from './router' 2 | import store from './store' 3 | import { Message } from 'element-ui' 4 | import NProgress from 'nprogress' // progress bar 5 | import 'nprogress/nprogress.css' // progress bar style 6 | import { getToken } from '@/utils/auth' // get token from cookie 7 | import getPageTitle from '@/utils/get-page-title' 8 | 9 | NProgress.configure({ showSpinner: false }) // NProgress Configuration 10 | 11 | const whiteList = ['/login'] // no redirect whitelist 12 | 13 | router.beforeEach(async(to, from, next) => { 14 | // start progress bar 15 | NProgress.start() 16 | 17 | // set page title 18 | document.title = getPageTitle(to.meta.title) 19 | 20 | // determine whether the user has logged in 21 | const hasToken = getToken() 22 | 23 | if (hasToken) { 24 | if (to.path === '/login') { 25 | // if is logged in, redirect to the home page 26 | next({ 27 | path: '/' 28 | }) 29 | NProgress.done() 30 | } else { 31 | // determine whether the user has obtained his permission roles through getInfo 32 | const hasRoles = store.getters.roles && store.getters.roles.length > 0 33 | if (hasRoles) { 34 | next() 35 | } else { 36 | try { 37 | // get user info 38 | // note: roles must be a object array! such as: ['admin'] or ,['developer','editor'] 39 | var roles = await store.dispatch('account/getInfo') 40 | 41 | // generate accessible routes map based on roles 42 | const accessRoutes = await store.dispatch('permission/generateRoutes', roles) 43 | 44 | // dynamically add accessible routes 45 | router.addRoutes(accessRoutes) 46 | 47 | // hack method to ensure that addRoutes is complete 48 | // set the replace: true, so the navigation will not leave a history record 49 | next({ 50 | ...to, 51 | replace: true 52 | }) 53 | } catch (error) { 54 | // remove token and go to login page to re-login 55 | await store.dispatch('account/resetToken') 56 | Message.error(error || 'Has Error') 57 | next(`/login?redirect=${to.path}`) 58 | NProgress.done() 59 | } 60 | } 61 | } 62 | } else { 63 | /* has no token*/ 64 | 65 | if (whiteList.indexOf(to.path) !== -1) { 66 | // in the free login whitelist, go directly 67 | next() 68 | } else { 69 | // other pages that do not have permission to access are redirected to the login page. 70 | next(`/login?redirect=${to.path}`) 71 | NProgress.done() 72 | } 73 | } 74 | }) 75 | 76 | router.afterEach(() => { 77 | // finish progress bar 78 | NProgress.done() 79 | }) 80 | -------------------------------------------------------------------------------- /application/web/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | Vue.use(Router) 5 | 6 | /* Layout */ 7 | import Layout from '@/layout' 8 | 9 | /** 10 | * Note: sub-menu only appear when route children.length >= 1 11 | * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html 12 | * 13 | * hidden: true if set true, item will not show in the sidebar(default is false) 14 | * alwaysShow: true if set true, will always show the root menu 15 | * if not set alwaysShow, when item has more than one children route, 16 | * it will becomes nested mode, otherwise not show the root menu 17 | * redirect: noRedirect if set noRedirect will no redirect in the breadcrumb 18 | * name:'router-name' the name is used by (must set!!!) 19 | * meta : { 20 | roles: ['admin','editor'] control the page roles (you can set multiple roles) 21 | title: 'title' the name show in sidebar and breadcrumb (recommend set) 22 | icon: 'svg-name' the icon show in the sidebar 23 | breadcrumb: false if set false, the item will hidden in breadcrumb(default is true) 24 | activeMenu: '/example/list' if set path, the sidebar will highlight the path you set 25 | } 26 | */ 27 | 28 | /** 29 | * constantRoutes 30 | * a base page that does not have permission requirements 31 | * all roles can be accessed 32 | */ 33 | export const constantRoutes = [{ 34 | path: '/login', 35 | component: () => import('@/views/login/index'), 36 | hidden: true 37 | }, 38 | 39 | { 40 | path: '/404', 41 | component: () => import('@/views/404'), 42 | hidden: true 43 | }, 44 | 45 | { 46 | path: '/', 47 | component: Layout, 48 | redirect: '/realestate', 49 | children: [{ 50 | path: 'realestate', 51 | name: 'Realestate', 52 | component: () => import('@/views/realestate/list/index'), 53 | meta: { 54 | title: '商品信息', 55 | icon: 'realestate' 56 | } 57 | }] 58 | } 59 | ] 60 | 61 | /** 62 | * asyncRoutes 63 | * the routes that need to be dynamically loaded based on user roles 64 | */ 65 | export const asyncRoutes = [ 66 | { 67 | path: '/selling', 68 | component: Layout, 69 | redirect: '/selling/all', 70 | name: 'Selling', 71 | alwaysShow: true, 72 | meta: { 73 | title: '销售', 74 | icon: 'selling' 75 | }, 76 | children: [{ 77 | path: 'all', 78 | name: 'SellingAll', 79 | component: () => import('@/views/selling/all/index'), 80 | meta: { 81 | title: '所有销售', 82 | icon: 'sellingAll' 83 | } 84 | }, 85 | { 86 | path: 'me', 87 | name: 'SellingMe', 88 | component: () => import('@/views/selling/me/index'), 89 | meta: { 90 | roles: ['editor'], 91 | title: '我发起的', 92 | icon: 'sellingMe' 93 | } 94 | }, { 95 | path: 'buy', 96 | name: 'SellingBuy', 97 | component: () => import('@/views/selling/buy/index'), 98 | meta: { 99 | roles: ['editor'], 100 | title: '我购买的', 101 | icon: 'sellingBuy' 102 | } 103 | } 104 | ] 105 | }, 106 | { 107 | path: '/donating', 108 | component: Layout, 109 | redirect: '/donating/all', 110 | name: 'Donating', 111 | alwaysShow: true, 112 | meta: { 113 | title: '捐赠', 114 | icon: 'donating' 115 | }, 116 | children: [{ 117 | path: 'all', 118 | name: 'DonatingAll', 119 | component: () => import('@/views/donating/all/index'), 120 | meta: { 121 | title: '所有捐赠', 122 | icon: 'donatingAll' 123 | } 124 | }, 125 | { 126 | path: 'donor', 127 | name: 'DonatingDonor', 128 | component: () => import('@/views/donating/donor/index'), 129 | meta: { 130 | roles: ['editor'], 131 | title: '我发起的捐赠', 132 | icon: 'donatingDonor' 133 | } 134 | }, { 135 | path: 'grantee', 136 | name: 'DonatingGrantee', 137 | component: () => import('@/views/donating/grantee/index'), 138 | meta: { 139 | roles: ['editor'], 140 | title: '我收到的受赠', 141 | icon: 'donatingGrantee' 142 | } 143 | } 144 | ] 145 | }, 146 | { 147 | path: '/addRealestate', 148 | component: Layout, 149 | meta: { 150 | roles: ['admin'] 151 | }, 152 | children: [{ 153 | path: '/addRealestate', 154 | name: 'AddRealestate', 155 | component: () => import('@/views/realestate/add/index'), 156 | meta: { 157 | title: '新增商品', 158 | icon: 'addRealestate' 159 | } 160 | }] 161 | }, 162 | 163 | // 404 page must be placed at the end !!! 164 | { 165 | path: '*', 166 | redirect: '/404', 167 | hidden: true 168 | } 169 | ] 170 | 171 | const createRouter = () => new Router({ 172 | base: '/web', 173 | // mode: 'history', // require service support 174 | scrollBehavior: () => ({ 175 | y: 0 176 | }), 177 | routes: constantRoutes 178 | }) 179 | 180 | const router = createRouter() 181 | 182 | // Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465 183 | export function resetRouter() { 184 | const newRouter = createRouter() 185 | router.matcher = newRouter.matcher // reset router 186 | } 187 | 188 | export default router 189 | -------------------------------------------------------------------------------- /application/web/src/settings.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | title: '基于区块链的商品溯源系统', 4 | 5 | /** 6 | * @type {boolean} true | false 7 | * @description Whether fix the header 8 | */ 9 | fixedHeader: false, 10 | 11 | /** 12 | * @type {boolean} true | false 13 | * @description Whether show the logo in sidebar 14 | */ 15 | sidebarLogo: true 16 | } 17 | -------------------------------------------------------------------------------- /application/web/src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | sidebar: state => state.app.sidebar, 3 | device: state => state.app.device, 4 | token: state => state.account.token, 5 | accountId: state => state.account.accountId, 6 | userName: state => state.account.userName, 7 | balance: state => state.account.balance, 8 | roles: state => state.account.roles, 9 | permission_routes: state => state.permission.routes 10 | } 11 | export default getters 12 | -------------------------------------------------------------------------------- /application/web/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import getters from './getters' 4 | import app from './modules/app' 5 | import permission from './modules/permission' 6 | import settings from './modules/settings' 7 | import account from './modules/account' 8 | 9 | Vue.use(Vuex) 10 | 11 | const store = new Vuex.Store({ 12 | modules: { 13 | app, 14 | permission, 15 | settings, 16 | account 17 | }, 18 | getters 19 | }) 20 | 21 | export default store 22 | -------------------------------------------------------------------------------- /application/web/src/store/modules/account.js: -------------------------------------------------------------------------------- 1 | import { 2 | login 3 | } from '@/api/account' 4 | import { 5 | getToken, 6 | setToken, 7 | removeToken 8 | } from '@/utils/auth' 9 | import { 10 | resetRouter 11 | } from '@/router' 12 | 13 | const getDefaultState = () => { 14 | return { 15 | token: getToken(), 16 | accountId: '', 17 | userName: '', 18 | balance: 0, 19 | roles: [] 20 | } 21 | } 22 | 23 | const state = getDefaultState() 24 | 25 | const mutations = { 26 | RESET_STATE: (state) => { 27 | Object.assign(state, getDefaultState()) 28 | }, 29 | SET_TOKEN: (state, token) => { 30 | state.token = token 31 | }, 32 | SET_ACCOUNTID: (state, accountId) => { 33 | state.accountId = accountId 34 | }, 35 | SET_USERNAME: (state, userName) => { 36 | state.userName = userName 37 | }, 38 | SET_BALANCE: (state, balance) => { 39 | state.balance = balance 40 | }, 41 | SET_ROLES: (state, roles) => { 42 | state.roles = roles 43 | } 44 | } 45 | 46 | const actions = { 47 | login({ 48 | commit 49 | }, accountId) { 50 | return new Promise((resolve, reject) => { 51 | login({ 52 | args: [{ 53 | accountId: accountId 54 | }] 55 | }).then(response => { 56 | commit('SET_TOKEN', response[0].accountId) 57 | setToken(response[0].accountId) 58 | resolve() 59 | }).catch(error => { 60 | reject(error) 61 | }) 62 | }) 63 | }, 64 | // get user info 65 | getInfo({ 66 | commit, 67 | state 68 | }) { 69 | return new Promise((resolve, reject) => { 70 | login({ 71 | args: [{ 72 | accountId: state.token 73 | }] 74 | }).then(response => { 75 | var roles 76 | if (response[0].userName === '管理员') { 77 | roles = ['admin'] 78 | } else { 79 | roles = ['editor'] 80 | } 81 | commit('SET_ROLES', roles) 82 | commit('SET_ACCOUNTID', response[0].accountId) 83 | commit('SET_USERNAME', response[0].userName) 84 | commit('SET_BALANCE', response[0].balance) 85 | resolve(roles) 86 | }).catch(error => { 87 | reject(error) 88 | }) 89 | }) 90 | }, 91 | logout({ 92 | commit 93 | }) { 94 | return new Promise(resolve => { 95 | removeToken() 96 | resetRouter() 97 | commit('RESET_STATE') 98 | resolve() 99 | }) 100 | }, 101 | 102 | resetToken({ 103 | commit 104 | }) { 105 | return new Promise(resolve => { 106 | removeToken() 107 | commit('RESET_STATE') 108 | resolve() 109 | }) 110 | } 111 | } 112 | 113 | export default { 114 | namespaced: true, 115 | state, 116 | mutations, 117 | actions 118 | } 119 | -------------------------------------------------------------------------------- /application/web/src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const state = { 4 | sidebar: { 5 | opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true, 6 | withoutAnimation: false 7 | }, 8 | device: 'desktop' 9 | } 10 | 11 | const mutations = { 12 | TOGGLE_SIDEBAR: state => { 13 | state.sidebar.opened = !state.sidebar.opened 14 | state.sidebar.withoutAnimation = false 15 | if (state.sidebar.opened) { 16 | Cookies.set('sidebarStatus', 1) 17 | } else { 18 | Cookies.set('sidebarStatus', 0) 19 | } 20 | }, 21 | CLOSE_SIDEBAR: (state, withoutAnimation) => { 22 | Cookies.set('sidebarStatus', 0) 23 | state.sidebar.opened = false 24 | state.sidebar.withoutAnimation = withoutAnimation 25 | }, 26 | TOGGLE_DEVICE: (state, device) => { 27 | state.device = device 28 | } 29 | } 30 | 31 | const actions = { 32 | toggleSideBar({ 33 | commit 34 | }) { 35 | commit('TOGGLE_SIDEBAR') 36 | }, 37 | closeSideBar({ 38 | commit 39 | }, { 40 | withoutAnimation 41 | }) { 42 | commit('CLOSE_SIDEBAR', withoutAnimation) 43 | }, 44 | toggleDevice({ 45 | commit 46 | }, device) { 47 | commit('TOGGLE_DEVICE', device) 48 | } 49 | } 50 | 51 | export default { 52 | namespaced: true, 53 | state, 54 | mutations, 55 | actions 56 | } 57 | -------------------------------------------------------------------------------- /application/web/src/store/modules/permission.js: -------------------------------------------------------------------------------- 1 | import { 2 | asyncRoutes, 3 | constantRoutes 4 | } from '@/router' 5 | 6 | /** 7 | * Use meta.role to determine if the current user has permission 8 | * @param roles 9 | * @param route 10 | */ 11 | function hasPermission(roles, route) { 12 | if (route.meta && route.meta.roles) { 13 | return roles.some(role => route.meta.roles.includes(role)) 14 | } else { 15 | return true 16 | } 17 | } 18 | 19 | /** 20 | * Filter asynchronous routing tables by recursion 21 | * @param routes asyncRoutes 22 | * @param roles 23 | */ 24 | export function filterAsyncRoutes(routes, roles) { 25 | const res = [] 26 | 27 | routes.forEach(route => { 28 | const tmp = { 29 | ...route 30 | } 31 | if (hasPermission(roles, tmp)) { 32 | if (tmp.children) { 33 | tmp.children = filterAsyncRoutes(tmp.children, roles) 34 | } 35 | res.push(tmp) 36 | } 37 | }) 38 | 39 | return res 40 | } 41 | 42 | const state = { 43 | routes: [], 44 | addRoutes: [] 45 | } 46 | 47 | const mutations = { 48 | SET_ROUTES: (state, routes) => { 49 | state.addRoutes = routes 50 | state.routes = constantRoutes.concat(routes) 51 | } 52 | } 53 | 54 | const actions = { 55 | generateRoutes({ 56 | commit 57 | }, roles) { 58 | return new Promise(resolve => { 59 | var accessedRoutes = filterAsyncRoutes(asyncRoutes, roles) 60 | commit('SET_ROUTES', accessedRoutes) 61 | resolve(accessedRoutes) 62 | }) 63 | } 64 | } 65 | 66 | export default { 67 | namespaced: true, 68 | state, 69 | mutations, 70 | actions 71 | } 72 | -------------------------------------------------------------------------------- /application/web/src/store/modules/settings.js: -------------------------------------------------------------------------------- 1 | import defaultSettings from '@/settings' 2 | 3 | const { showSettings, fixedHeader, sidebarLogo } = defaultSettings 4 | 5 | const state = { 6 | showSettings: showSettings, 7 | fixedHeader: fixedHeader, 8 | sidebarLogo: sidebarLogo 9 | } 10 | 11 | const mutations = { 12 | CHANGE_SETTING: (state, { key, value }) => { 13 | if (state.hasOwnProperty(key)) { 14 | state[key] = value 15 | } 16 | } 17 | } 18 | 19 | const actions = { 20 | changeSetting({ commit }, data) { 21 | commit('CHANGE_SETTING', data) 22 | } 23 | } 24 | 25 | export default { 26 | namespaced: true, 27 | state, 28 | mutations, 29 | actions 30 | } 31 | 32 | -------------------------------------------------------------------------------- /application/web/src/styles/element-ui.scss: -------------------------------------------------------------------------------- 1 | // cover some element-ui styles 2 | 3 | .el-breadcrumb__inner, 4 | .el-breadcrumb__inner a { 5 | font-weight: 400 !important; 6 | } 7 | 8 | .el-upload { 9 | input[type="file"] { 10 | display: none !important; 11 | } 12 | } 13 | 14 | .el-upload__input { 15 | display: none; 16 | } 17 | 18 | 19 | // to fixed https://github.com/ElemeFE/element/issues/2461 20 | .el-dialog { 21 | transform: none; 22 | left: 0; 23 | position: relative; 24 | margin: 0 auto; 25 | } 26 | 27 | // refine element ui upload 28 | .upload-container { 29 | .el-upload { 30 | width: 100%; 31 | 32 | .el-upload-dragger { 33 | width: 100%; 34 | height: 200px; 35 | } 36 | } 37 | } 38 | 39 | // dropdown 40 | .el-dropdown-menu { 41 | a { 42 | display: block 43 | } 44 | } 45 | 46 | // to fix el-date-picker css style 47 | .el-range-separator { 48 | box-sizing: content-box; 49 | } 50 | -------------------------------------------------------------------------------- /application/web/src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './variables.scss'; 2 | @import './mixin.scss'; 3 | @import './transition.scss'; 4 | @import './element-ui.scss'; 5 | @import './sidebar.scss'; 6 | 7 | body { 8 | height: 100%; 9 | -moz-osx-font-smoothing: grayscale; 10 | -webkit-font-smoothing: antialiased; 11 | text-rendering: optimizeLegibility; 12 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; 13 | } 14 | 15 | label { 16 | font-weight: 700; 17 | } 18 | 19 | html { 20 | height: 100%; 21 | box-sizing: border-box; 22 | } 23 | 24 | #app { 25 | height: 100%; 26 | } 27 | 28 | *, 29 | *:before, 30 | *:after { 31 | box-sizing: inherit; 32 | } 33 | 34 | a:focus, 35 | a:active { 36 | outline: none; 37 | } 38 | 39 | a, 40 | a:focus, 41 | a:hover { 42 | cursor: pointer; 43 | color: inherit; 44 | text-decoration: none; 45 | } 46 | 47 | div:focus { 48 | outline: none; 49 | } 50 | 51 | .clearfix { 52 | &:after { 53 | visibility: hidden; 54 | display: block; 55 | font-size: 0; 56 | content: " "; 57 | clear: both; 58 | height: 0; 59 | } 60 | } 61 | 62 | // main-container global css 63 | .app-container { 64 | padding: 20px; 65 | } 66 | -------------------------------------------------------------------------------- /application/web/src/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin clearfix { 2 | &:after { 3 | content: ""; 4 | display: table; 5 | clear: both; 6 | } 7 | } 8 | 9 | @mixin scrollBar { 10 | &::-webkit-scrollbar-track-piece { 11 | background: #d3dce6; 12 | } 13 | 14 | &::-webkit-scrollbar { 15 | width: 6px; 16 | } 17 | 18 | &::-webkit-scrollbar-thumb { 19 | background: #99a9bf; 20 | border-radius: 20px; 21 | } 22 | } 23 | 24 | @mixin relative { 25 | position: relative; 26 | width: 100%; 27 | height: 100%; 28 | } 29 | -------------------------------------------------------------------------------- /application/web/src/styles/sidebar.scss: -------------------------------------------------------------------------------- 1 | #app { 2 | 3 | .main-container { 4 | min-height: 100%; 5 | transition: margin-left .28s; 6 | margin-left: $sideBarWidth; 7 | position: relative; 8 | } 9 | 10 | .sidebar-container { 11 | transition: width 0.28s; 12 | width: $sideBarWidth !important; 13 | background-color: $menuBg; 14 | height: 100%; 15 | position: fixed; 16 | font-size: 0px; 17 | top: 0; 18 | bottom: 0; 19 | left: 0; 20 | z-index: 1001; 21 | overflow: hidden; 22 | 23 | // reset element-ui css 24 | .horizontal-collapse-transition { 25 | transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; 26 | } 27 | 28 | .scrollbar-wrapper { 29 | overflow-x: hidden !important; 30 | } 31 | 32 | .el-scrollbar__bar.is-vertical { 33 | right: 0px; 34 | } 35 | 36 | .el-scrollbar { 37 | height: 100%; 38 | } 39 | 40 | &.has-logo { 41 | .el-scrollbar { 42 | height: calc(100% - 50px); 43 | } 44 | } 45 | 46 | .is-horizontal { 47 | display: none; 48 | } 49 | 50 | a { 51 | display: inline-block; 52 | width: 100%; 53 | overflow: hidden; 54 | } 55 | 56 | .svg-icon { 57 | margin-right: 16px; 58 | } 59 | 60 | .el-menu { 61 | border: none; 62 | height: 100%; 63 | width: 100% !important; 64 | } 65 | 66 | // menu hover 67 | .submenu-title-noDropdown, 68 | .el-submenu__title { 69 | &:hover { 70 | background-color: $menuHover !important; 71 | } 72 | } 73 | 74 | .is-active>.el-submenu__title { 75 | color: $subMenuActiveText !important; 76 | } 77 | 78 | & .nest-menu .el-submenu>.el-submenu__title, 79 | & .el-submenu .el-menu-item { 80 | min-width: $sideBarWidth !important; 81 | background-color: $subMenuBg !important; 82 | 83 | &:hover { 84 | background-color: $subMenuHover !important; 85 | } 86 | } 87 | } 88 | 89 | .hideSidebar { 90 | .sidebar-container { 91 | width: 54px !important; 92 | } 93 | 94 | .main-container { 95 | margin-left: 54px; 96 | } 97 | 98 | .submenu-title-noDropdown { 99 | padding: 0 !important; 100 | position: relative; 101 | 102 | .el-tooltip { 103 | padding: 0 !important; 104 | 105 | .svg-icon { 106 | margin-left: 20px; 107 | } 108 | } 109 | } 110 | 111 | .el-submenu { 112 | overflow: hidden; 113 | 114 | &>.el-submenu__title { 115 | padding: 0 !important; 116 | 117 | .svg-icon { 118 | margin-left: 20px; 119 | } 120 | 121 | .el-submenu__icon-arrow { 122 | display: none; 123 | } 124 | } 125 | } 126 | 127 | .el-menu--collapse { 128 | .el-submenu { 129 | &>.el-submenu__title { 130 | &>span { 131 | height: 0; 132 | width: 0; 133 | overflow: hidden; 134 | visibility: hidden; 135 | display: inline-block; 136 | } 137 | } 138 | } 139 | } 140 | } 141 | 142 | .el-menu--collapse .el-menu .el-submenu { 143 | min-width: $sideBarWidth !important; 144 | } 145 | 146 | // mobile responsive 147 | .mobile { 148 | .main-container { 149 | margin-left: 0px; 150 | } 151 | 152 | .sidebar-container { 153 | transition: transform .28s; 154 | width: $sideBarWidth !important; 155 | } 156 | 157 | &.hideSidebar { 158 | .sidebar-container { 159 | pointer-events: none; 160 | transition-duration: 0.3s; 161 | transform: translate3d(-$sideBarWidth, 0, 0); 162 | } 163 | } 164 | } 165 | 166 | .withoutAnimation { 167 | 168 | .main-container, 169 | .sidebar-container { 170 | transition: none; 171 | } 172 | } 173 | } 174 | 175 | // when menu collapsed 176 | .el-menu--vertical { 177 | &>.el-menu { 178 | .svg-icon { 179 | margin-right: 16px; 180 | } 181 | } 182 | 183 | .nest-menu .el-submenu>.el-submenu__title, 184 | .el-menu-item { 185 | &:hover { 186 | // you can use $subMenuHover 187 | background-color: $menuHover !important; 188 | } 189 | } 190 | 191 | // the scroll bar appears when the subMenu is too long 192 | >.el-menu--popup { 193 | max-height: 100vh; 194 | overflow-y: auto; 195 | 196 | &::-webkit-scrollbar-track-piece { 197 | background: #d3dce6; 198 | } 199 | 200 | &::-webkit-scrollbar { 201 | width: 6px; 202 | } 203 | 204 | &::-webkit-scrollbar-thumb { 205 | background: #99a9bf; 206 | border-radius: 20px; 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /application/web/src/styles/transition.scss: -------------------------------------------------------------------------------- 1 | // global transition css 2 | 3 | /* fade */ 4 | .fade-enter-active, 5 | .fade-leave-active { 6 | transition: opacity 0.28s; 7 | } 8 | 9 | .fade-enter, 10 | .fade-leave-active { 11 | opacity: 0; 12 | } 13 | 14 | /* fade-transform */ 15 | .fade-transform-leave-active, 16 | .fade-transform-enter-active { 17 | transition: all .5s; 18 | } 19 | 20 | .fade-transform-enter { 21 | opacity: 0; 22 | transform: translateX(-30px); 23 | } 24 | 25 | .fade-transform-leave-to { 26 | opacity: 0; 27 | transform: translateX(30px); 28 | } 29 | 30 | /* breadcrumb transition */ 31 | .breadcrumb-enter-active, 32 | .breadcrumb-leave-active { 33 | transition: all .5s; 34 | } 35 | 36 | .breadcrumb-enter, 37 | .breadcrumb-leave-active { 38 | opacity: 0; 39 | transform: translateX(20px); 40 | } 41 | 42 | .breadcrumb-move { 43 | transition: all .5s; 44 | } 45 | 46 | .breadcrumb-leave-active { 47 | position: absolute; 48 | } 49 | -------------------------------------------------------------------------------- /application/web/src/styles/variables.scss: -------------------------------------------------------------------------------- 1 | // sidebar 2 | $menuText:#bfcbd9; 3 | $menuActiveText:#409EFF; 4 | $subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951 5 | 6 | $menuBg:#304156; 7 | $menuHover:#263445; 8 | 9 | $subMenuBg:#1f2d3d; 10 | $subMenuHover:#001528; 11 | 12 | $sideBarWidth: 210px; 13 | 14 | // the :export directive is the magic sauce for webpack 15 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass 16 | :export { 17 | menuText: $menuText; 18 | menuActiveText: $menuActiveText; 19 | subMenuActiveText: $subMenuActiveText; 20 | menuBg: $menuBg; 21 | menuHover: $menuHover; 22 | subMenuBg: $subMenuBg; 23 | subMenuHover: $subMenuHover; 24 | sideBarWidth: $sideBarWidth; 25 | } 26 | -------------------------------------------------------------------------------- /application/web/src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const TokenKey = 'account_id_token' 4 | 5 | export function getToken() { 6 | return Cookies.get(TokenKey) 7 | } 8 | 9 | export function setToken(token) { 10 | return Cookies.set(TokenKey, token) 11 | } 12 | 13 | export function removeToken() { 14 | return Cookies.remove(TokenKey) 15 | } 16 | -------------------------------------------------------------------------------- /application/web/src/utils/get-page-title.js: -------------------------------------------------------------------------------- 1 | import defaultSettings from '@/settings' 2 | 3 | const title = defaultSettings.title || 'Vue Admin Template' 4 | 5 | export default function getPageTitle(pageTitle) { 6 | if (pageTitle) { 7 | return `${pageTitle} - ${title}` 8 | } 9 | return `${title}` 10 | } 11 | -------------------------------------------------------------------------------- /application/web/src/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by PanJiaChen on 16/11/18. 3 | */ 4 | 5 | /** 6 | * Parse the time to string 7 | * @param {(Object|string|number)} time 8 | * @param {string} cFormat 9 | * @returns {string | null} 10 | */ 11 | export function parseTime(time, cFormat) { 12 | if (arguments.length === 0) { 13 | return null 14 | } 15 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' 16 | let date 17 | if (typeof time === 'object') { 18 | date = time 19 | } else { 20 | if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { 21 | time = parseInt(time) 22 | } 23 | if ((typeof time === 'number') && (time.toString().length === 10)) { 24 | time = time * 1000 25 | } 26 | date = new Date(time) 27 | } 28 | const formatObj = { 29 | y: date.getFullYear(), 30 | m: date.getMonth() + 1, 31 | d: date.getDate(), 32 | h: date.getHours(), 33 | i: date.getMinutes(), 34 | s: date.getSeconds(), 35 | a: date.getDay() 36 | } 37 | const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => { 38 | const value = formatObj[key] 39 | // Note: getDay() returns 0 on Sunday 40 | if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] } 41 | return value.toString().padStart(2, '0') 42 | }) 43 | return time_str 44 | } 45 | 46 | /** 47 | * @param {number} time 48 | * @param {string} option 49 | * @returns {string} 50 | */ 51 | export function formatTime(time, option) { 52 | if (('' + time).length === 10) { 53 | time = parseInt(time) * 1000 54 | } else { 55 | time = +time 56 | } 57 | const d = new Date(time) 58 | const now = Date.now() 59 | 60 | const diff = (now - d) / 1000 61 | 62 | if (diff < 30) { 63 | return '刚刚' 64 | } else if (diff < 3600) { 65 | // less 1 hour 66 | return Math.ceil(diff / 60) + '分钟前' 67 | } else if (diff < 3600 * 24) { 68 | return Math.ceil(diff / 3600) + '小时前' 69 | } else if (diff < 3600 * 24 * 2) { 70 | return '1天前' 71 | } 72 | if (option) { 73 | return parseTime(time, option) 74 | } else { 75 | return ( 76 | d.getMonth() + 77 | 1 + 78 | '月' + 79 | d.getDate() + 80 | '日' + 81 | d.getHours() + 82 | '时' + 83 | d.getMinutes() + 84 | '分' 85 | ) 86 | } 87 | } 88 | 89 | /** 90 | * @param {string} url 91 | * @returns {Object} 92 | */ 93 | export function param2Obj(url) { 94 | const search = url.split('?')[1] 95 | if (!search) { 96 | return {} 97 | } 98 | return JSON.parse( 99 | '{"' + 100 | decodeURIComponent(search) 101 | .replace(/"/g, '\\"') 102 | .replace(/&/g, '","') 103 | .replace(/=/g, '":"') 104 | .replace(/\+/g, ' ') + 105 | '"}' 106 | ) 107 | } 108 | -------------------------------------------------------------------------------- /application/web/src/utils/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { 3 | MessageBox, 4 | Message 5 | } from 'element-ui' 6 | 7 | const service = axios.create({ 8 | baseURL: process.env.VUE_APP_BASE_API, 9 | timeout: 5000 10 | }) 11 | 12 | service.interceptors.response.use( 13 | response => { 14 | const res = response.data 15 | if (res.code !== 200) { 16 | MessageBox.alert('服务器开小差了', 'error', { 17 | confirmButtonText: '确定', 18 | type: 'warning' 19 | }) 20 | return Promise.reject(new Error(res.msg || 'Error')) 21 | } else { 22 | return res.data 23 | } 24 | }, 25 | error => { 26 | if (error.response === undefined) { 27 | Message({ 28 | message: '请求失败 ' + error.message, 29 | type: 'error', 30 | duration: 5 * 1000 31 | }) 32 | return Promise.reject(error) 33 | } else { 34 | Message({ 35 | message: '失败 ' + error.response.data.data, 36 | type: 'error', 37 | duration: 5 * 1000 38 | }) 39 | return Promise.reject(error.response) 40 | } 41 | } 42 | ) 43 | 44 | export default service 45 | -------------------------------------------------------------------------------- /application/web/src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by PanJiaChen on 16/11/18. 3 | */ 4 | 5 | /** 6 | * @param {string} path 7 | * @returns {Boolean} 8 | */ 9 | export function isExternal(path) { 10 | return /^(https?:|mailto:|tel:)/.test(path) 11 | } 12 | 13 | -------------------------------------------------------------------------------- /application/web/src/views/404.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 34 | 35 | 229 | -------------------------------------------------------------------------------- /application/web/src/views/donating/all/index.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 126 | 127 | 158 | -------------------------------------------------------------------------------- /application/web/src/views/donating/donor/index.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 118 | 119 | 150 | -------------------------------------------------------------------------------- /application/web/src/views/donating/grantee/index.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 125 | 126 | 157 | -------------------------------------------------------------------------------- /application/web/src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 78 | 79 | 149 | -------------------------------------------------------------------------------- /application/web/src/views/realestate/add/index.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 177 | 178 | 180 | -------------------------------------------------------------------------------- /application/web/src/views/selling/all/index.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 174 | 175 | 206 | -------------------------------------------------------------------------------- /application/web/src/views/selling/buy/index.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 137 | 138 | 169 | -------------------------------------------------------------------------------- /application/web/src/views/selling/me/index.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 134 | 135 | 166 | -------------------------------------------------------------------------------- /application/web/vue.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const defaultSettings = require('./src/settings.js') 4 | 5 | function resolve(dir) { 6 | return path.join(__dirname, dir) 7 | } 8 | 9 | const name = defaultSettings.title || 'vue Admin Template' // page title 10 | 11 | // If your port is set to 80, 12 | // use administrator privileges to execute the command line. 13 | // For example, Mac: sudo npm run 14 | // You can change the port by the following methods: 15 | // port = 9528 npm run dev OR npm run dev --port = 9528 16 | const port = process.env.port || process.env.npm_config_port || 9528 // dev port 17 | 18 | // All configuration item explanations can be find in https://cli.vuejs.org/config/ 19 | module.exports = { 20 | /** 21 | * You will need to set publicPath if you plan to deploy your site under a sub path, 22 | * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/, 23 | * then publicPath should be set to "/bar/". 24 | * In most cases please use '/' !!! 25 | * Detail: https://cli.vuejs.org/config/#publicpath 26 | */ 27 | publicPath: '/web', 28 | outputDir: 'dist', 29 | assetsDir: 'static', 30 | lintOnSave: process.env.NODE_ENV === 'development', 31 | productionSourceMap: false, 32 | devServer: { 33 | port: port, 34 | open: true, 35 | overlay: { 36 | warnings: false, 37 | errors: true 38 | } 39 | }, 40 | configureWebpack: { 41 | // provide the app's title in webpack's name field, so that 42 | // it can be accessed in index.html to inject the correct title. 43 | name: name, 44 | resolve: { 45 | alias: { 46 | '@': resolve('src') 47 | } 48 | } 49 | }, 50 | chainWebpack(config) { 51 | config.plugins.delete('preload') // TODO: need test 52 | config.plugins.delete('prefetch') // TODO: need test 53 | 54 | // set svg-sprite-loader 55 | config.module 56 | .rule('svg') 57 | .exclude.add(resolve('src/icons')) 58 | .end() 59 | config.module 60 | .rule('icons') 61 | .test(/\.svg$/) 62 | .include.add(resolve('src/icons')) 63 | .end() 64 | .use('svg-sprite-loader') 65 | .loader('svg-sprite-loader') 66 | .options({ 67 | symbolId: 'icon-[name]' 68 | }) 69 | .end() 70 | 71 | // set preserveWhitespace 72 | config.module 73 | .rule('vue') 74 | .use('vue-loader') 75 | .loader('vue-loader') 76 | .tap(options => { 77 | options.compilerOptions.preserveWhitespace = true 78 | return options 79 | }) 80 | .end() 81 | 82 | config 83 | // https://webpack.js.org/configuration/devtool/#development 84 | .when(process.env.NODE_ENV === 'development', 85 | config => config.devtool('cheap-source-map') 86 | ) 87 | 88 | config 89 | .when(process.env.NODE_ENV !== 'development', 90 | config => { 91 | config 92 | .plugin('ScriptExtHtmlWebpackPlugin') 93 | .after('html') 94 | .use('script-ext-html-webpack-plugin', [{ 95 | // `runtime` must same as runtimeChunk name. default is `runtime` 96 | inline: /runtime\..*\.js$/ 97 | }]) 98 | .end() 99 | config 100 | .optimization.splitChunks({ 101 | chunks: 'all', 102 | cacheGroups: { 103 | libs: { 104 | name: 'chunk-libs', 105 | test: /[\\/]node_modules[\\/]/, 106 | priority: 10, 107 | chunks: 'initial' // only package third parties that are initially dependent 108 | }, 109 | elementUI: { 110 | name: 'chunk-elementUI', // split elementUI into a single package 111 | priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app 112 | test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm 113 | }, 114 | commons: { 115 | name: 'chunk-commons', 116 | test: resolve('src/components'), // can customize your rules 117 | minChunks: 3, // minimum common number 118 | priority: 5, 119 | reuseExistingChunk: true 120 | } 121 | } 122 | }) 123 | config.optimization.runtimeChunk('single') 124 | } 125 | ) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /chaincode/api/account.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "chaincode/model" 5 | "chaincode/pkg/utils" 6 | "encoding/json" 7 | "fmt" 8 | 9 | "github.com/hyperledger/fabric/core/chaincode/shim" 10 | pb "github.com/hyperledger/fabric/protos/peer" 11 | ) 12 | 13 | // QueryAccountList 查询账户列表 14 | func QueryAccountList(stub shim.ChaincodeStubInterface, args []string) pb.Response { 15 | var accountList []model.Account 16 | results, err := utils.GetStateByPartialCompositeKeys(stub, model.AccountKey, args) 17 | if err != nil { 18 | return shim.Error(fmt.Sprintf("%s", err)) 19 | } 20 | for _, v := range results { 21 | if v != nil { 22 | var account model.Account 23 | err := json.Unmarshal(v, &account) 24 | if err != nil { 25 | return shim.Error(fmt.Sprintf("QueryAccountList-反序列化出错: %s", err)) 26 | } 27 | accountList = append(accountList, account) 28 | } 29 | } 30 | accountListByte, err := json.Marshal(accountList) 31 | if err != nil { 32 | return shim.Error(fmt.Sprintf("QueryAccountList-序列化出错: %s", err)) 33 | } 34 | return shim.Success(accountListByte) 35 | } 36 | -------------------------------------------------------------------------------- /chaincode/api/donating.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "chaincode/model" 5 | "chaincode/pkg/utils" 6 | "encoding/json" 7 | "fmt" 8 | "time" 9 | 10 | "github.com/hyperledger/fabric/core/chaincode/shim" 11 | pb "github.com/hyperledger/fabric/protos/peer" 12 | ) 13 | 14 | // CreateDonating 发起捐赠 15 | func CreateDonating(stub shim.ChaincodeStubInterface, args []string) pb.Response { 16 | // 验证参数 17 | if len(args) != 3 { 18 | return shim.Error("参数个数不满足") 19 | } 20 | objectOfDonating := args[0] 21 | donor := args[1] 22 | grantee := args[2] 23 | if objectOfDonating == "" || donor == "" || grantee == "" { 24 | return shim.Error("参数存在空值") 25 | } 26 | if donor == grantee { 27 | return shim.Error("捐赠人和受赠人不能同一人") 28 | } 29 | //判断objectOfDonating是否属于donor 30 | resultsRealEstate, err := utils.GetStateByPartialCompositeKeys2(stub, model.RealEstateKey, []string{donor, objectOfDonating}) 31 | if err != nil || len(resultsRealEstate) != 1 { 32 | return shim.Error(fmt.Sprintf("验证%s属于%s失败: %s", objectOfDonating, donor, err)) 33 | } 34 | var realEstate model.RealEstate 35 | if err = json.Unmarshal(resultsRealEstate[0], &realEstate); err != nil { 36 | return shim.Error(fmt.Sprintf("CreateDonating-反序列化出错: %s", err)) 37 | } 38 | //根据grantee获取受赠人信息 39 | resultsAccount, err := utils.GetStateByPartialCompositeKeys(stub, model.AccountKey, []string{grantee}) 40 | if err != nil || len(resultsAccount) != 1 { 41 | return shim.Error(fmt.Sprintf("grantee受赠人信息验证失败%s", err)) 42 | } 43 | var accountGrantee model.Account 44 | if err = json.Unmarshal(resultsAccount[0], &accountGrantee); err != nil { 45 | return shim.Error(fmt.Sprintf("查询操作人信息-反序列化出错: %s", err)) 46 | } 47 | if accountGrantee.UserName == "管理员" { 48 | return shim.Error(fmt.Sprintf("不能捐赠给管理员%s", err)) 49 | } 50 | //判断记录是否已存在,不能重复发起捐赠 51 | //若Encumbrance为true即说明此车辆已经正在担保状态 52 | if realEstate.Encumbrance { 53 | return shim.Error("此车辆已经作为担保状态,不能再发起捐赠") 54 | } 55 | createTime, _ := stub.GetTxTimestamp() 56 | donating := &model.Donating{ 57 | ObjectOfDonating: objectOfDonating, 58 | Donor: donor, 59 | Grantee: grantee, 60 | CreateTime: time.Unix(int64(createTime.GetSeconds()), int64(createTime.GetNanos())).Local().Format("2006-01-02 15:04:05"), 61 | DonatingStatus: model.DonatingStatusConstant()["donatingStart"], 62 | } 63 | // 写入账本 64 | if err := utils.WriteLedger(donating, stub, model.DonatingKey, []string{donating.Donor, donating.ObjectOfDonating, donating.Grantee}); err != nil { 65 | return shim.Error(fmt.Sprintf("%s", err)) 66 | } 67 | //将房子状态设置为正在担保状态 68 | realEstate.Encumbrance = true 69 | if err := utils.WriteLedger(realEstate, stub, model.RealEstateKey, []string{realEstate.Proprietor, realEstate.RealEstateID}); err != nil { 70 | return shim.Error(fmt.Sprintf("%s", err)) 71 | } 72 | //将本次购买交易写入账本,可供受赠人查询 73 | donatingGrantee := &model.DonatingGrantee{ 74 | Grantee: grantee, 75 | CreateTime: time.Unix(int64(createTime.GetSeconds()), int64(createTime.GetNanos())).Local().Format("2006-01-02 15:04:05"), 76 | Donating: *donating, 77 | } 78 | if err := utils.WriteLedger(donatingGrantee, stub, model.DonatingGranteeKey, []string{donatingGrantee.Grantee, donatingGrantee.CreateTime}); err != nil { 79 | return shim.Error(fmt.Sprintf("将本次捐赠交易写入账本失败%s", err)) 80 | } 81 | donatingGranteeByte, err := json.Marshal(donatingGrantee) 82 | if err != nil { 83 | return shim.Error(fmt.Sprintf("序列化成功创建的信息出错: %s", err)) 84 | } 85 | // 成功返回 86 | return shim.Success(donatingGranteeByte) 87 | } 88 | 89 | // QueryDonatingList 查询捐赠列表(可查询所有,也可根据发起捐赠人查询)(发起的)(供捐赠人查询) 90 | func QueryDonatingList(stub shim.ChaincodeStubInterface, args []string) pb.Response { 91 | var donatingList []model.Donating 92 | results, err := utils.GetStateByPartialCompositeKeys2(stub, model.DonatingKey, args) 93 | if err != nil { 94 | return shim.Error(fmt.Sprintf("%s", err)) 95 | } 96 | for _, v := range results { 97 | if v != nil { 98 | var donating model.Donating 99 | err := json.Unmarshal(v, &donating) 100 | if err != nil { 101 | return shim.Error(fmt.Sprintf("QueryDonatingList-反序列化出错: %s", err)) 102 | } 103 | donatingList = append(donatingList, donating) 104 | } 105 | } 106 | donatingListByte, err := json.Marshal(donatingList) 107 | if err != nil { 108 | return shim.Error(fmt.Sprintf("QueryDonatingList-序列化出错: %s", err)) 109 | } 110 | return shim.Success(donatingListByte) 111 | } 112 | 113 | // QueryDonatingListByGrantee 根据受赠人(受赠人AccountId)查询捐赠(受赠的)(供受赠人查询) 114 | func QueryDonatingListByGrantee(stub shim.ChaincodeStubInterface, args []string) pb.Response { 115 | if len(args) != 1 { 116 | return shim.Error(fmt.Sprintf("必须指定受赠人AccountId查询")) 117 | } 118 | var donatingGranteeList []model.DonatingGrantee 119 | results, err := utils.GetStateByPartialCompositeKeys2(stub, model.DonatingGranteeKey, args) 120 | if err != nil { 121 | return shim.Error(fmt.Sprintf("%s", err)) 122 | } 123 | for _, v := range results { 124 | if v != nil { 125 | var donatingGrantee model.DonatingGrantee 126 | err := json.Unmarshal(v, &donatingGrantee) 127 | if err != nil { 128 | return shim.Error(fmt.Sprintf("QueryDonatingListByGrantee-反序列化出错: %s", err)) 129 | } 130 | donatingGranteeList = append(donatingGranteeList, donatingGrantee) 131 | } 132 | } 133 | donatingGranteeListByte, err := json.Marshal(donatingGranteeList) 134 | if err != nil { 135 | return shim.Error(fmt.Sprintf("QueryDonatingListByGrantee-序列化出错: %s", err)) 136 | } 137 | return shim.Success(donatingGranteeListByte) 138 | } 139 | 140 | // UpdateDonating 更新捐赠状态(确认受赠、取消) 141 | func UpdateDonating(stub shim.ChaincodeStubInterface, args []string) pb.Response { 142 | // 验证参数 143 | if len(args) != 4 { 144 | return shim.Error("参数个数不满足") 145 | } 146 | objectOfDonating := args[0] 147 | donor := args[1] 148 | grantee := args[2] 149 | status := args[3] 150 | if objectOfDonating == "" || donor == "" || grantee == "" || status == "" { 151 | return shim.Error("参数存在空值") 152 | } 153 | if donor == grantee { 154 | return shim.Error("捐赠人和受赠人不能同一人") 155 | } 156 | //根据objectOfDonating和donor获取想要购买的商品信息,确认存在该车辆 157 | resultsRealEstate, err := utils.GetStateByPartialCompositeKeys2(stub, model.RealEstateKey, []string{donor, objectOfDonating}) 158 | if err != nil || len(resultsRealEstate) != 1 { 159 | return shim.Error(fmt.Sprintf("根据%s和%s获取想要购买的商品信息失败: %s", objectOfDonating, donor, err)) 160 | } 161 | var realEstate model.RealEstate 162 | if err = json.Unmarshal(resultsRealEstate[0], &realEstate); err != nil { 163 | return shim.Error(fmt.Sprintf("UpdateDonating-反序列化出错: %s", err)) 164 | } 165 | //根据grantee获取受赠人 166 | resultsGranteeAccount, err := utils.GetStateByPartialCompositeKeys(stub, model.AccountKey, []string{grantee}) 167 | if err != nil || len(resultsGranteeAccount) != 1 { 168 | return shim.Error(fmt.Sprintf("grantee受赠人信息验证失败%s", err)) 169 | } 170 | var accountGrantee model.Account 171 | if err = json.Unmarshal(resultsGranteeAccount[0], &accountGrantee); err != nil { 172 | return shim.Error(fmt.Sprintf("查询grantee受赠人信息-反序列化出错: %s", err)) 173 | } 174 | //根据objectOfDonating和donor和grantee获取捐赠信息 175 | resultsDonating, err := utils.GetStateByPartialCompositeKeys2(stub, model.DonatingKey, []string{donor, objectOfDonating, grantee}) 176 | if err != nil || len(resultsDonating) != 1 { 177 | return shim.Error(fmt.Sprintf("根据%s和%s和%s获取销售信息失败: %s", objectOfDonating, donor, grantee, err)) 178 | } 179 | var donating model.Donating 180 | if err = json.Unmarshal(resultsDonating[0], &donating); err != nil { 181 | return shim.Error(fmt.Sprintf("UpdateDonating-反序列化出错: %s", err)) 182 | } 183 | //不管完成还是取消操作,必须确保捐赠处于捐赠中状态 184 | if donating.DonatingStatus != model.DonatingStatusConstant()["donatingStart"] { 185 | return shim.Error("此交易并不处于捐赠中,确认/取消捐赠失败") 186 | } 187 | //根据grantee获取买家购买信息donatingGrantee 188 | var donatingGrantee model.DonatingGrantee 189 | resultsDonatingGrantee, err := utils.GetStateByPartialCompositeKeys2(stub, model.DonatingGranteeKey, []string{grantee}) 190 | if err != nil || len(resultsDonatingGrantee) == 0 { 191 | return shim.Error(fmt.Sprintf("根据%s获取受赠人信息失败: %s", grantee, err)) 192 | } 193 | for _, v := range resultsDonatingGrantee { 194 | if v != nil { 195 | var s model.DonatingGrantee 196 | err := json.Unmarshal(v, &s) 197 | if err != nil { 198 | return shim.Error(fmt.Sprintf("UpdateDonating-反序列化出错: %s", err)) 199 | } 200 | if s.Donating.ObjectOfDonating == objectOfDonating && s.Donating.Donor == donor && s.Grantee == grantee { 201 | //还必须判断状态必须为交付中,防止房子已经交易过,只是被取消了 202 | if s.Donating.DonatingStatus == model.DonatingStatusConstant()["donatingStart"] { 203 | donatingGrantee = s 204 | break 205 | } 206 | } 207 | } 208 | } 209 | var data []byte 210 | //判断捐赠状态 211 | switch status { 212 | case "done": 213 | //将商品信息转入受赠人,并重置担保状态 214 | realEstate.Proprietor = grantee 215 | realEstate.Encumbrance = false 216 | //realEstate.RealEstateID = stub.GetTxID() //重新更新车辆ID 217 | if err := utils.WriteLedger(realEstate, stub, model.RealEstateKey, []string{realEstate.Proprietor, realEstate.RealEstateID}); err != nil { 218 | return shim.Error(fmt.Sprintf("%s", err)) 219 | } 220 | //清除原来的商品信息 221 | if err := utils.DelLedger(stub, model.RealEstateKey, []string{donor, objectOfDonating}); err != nil { 222 | return shim.Error(fmt.Sprintf("%s", err)) 223 | } 224 | //捐赠状态设置为完成,写入账本 225 | donating.DonatingStatus = model.DonatingStatusConstant()["done"] 226 | donating.ObjectOfDonating = realEstate.RealEstateID //重新更新车辆ID 227 | if err := utils.WriteLedger(donating, stub, model.DonatingKey, []string{donating.Donor, objectOfDonating, grantee}); err != nil { 228 | return shim.Error(fmt.Sprintf("%s", err)) 229 | } 230 | donatingGrantee.Donating = donating 231 | if err := utils.WriteLedger(donatingGrantee, stub, model.DonatingGranteeKey, []string{donatingGrantee.Grantee, donatingGrantee.CreateTime}); err != nil { 232 | return shim.Error(fmt.Sprintf("将本次捐赠交易写入账本失败%s", err)) 233 | } 234 | data, err = json.Marshal(donatingGrantee) 235 | if err != nil { 236 | return shim.Error(fmt.Sprintf("序列化捐赠交易的信息出错: %s", err)) 237 | } 238 | break 239 | case "cancelled": 240 | //重置商品信息担保状态 241 | realEstate.Encumbrance = false 242 | if err := utils.WriteLedger(realEstate, stub, model.RealEstateKey, []string{realEstate.Proprietor, realEstate.RealEstateID}); err != nil { 243 | return shim.Error(fmt.Sprintf("%s", err)) 244 | } 245 | //更新捐赠状态 246 | donating.DonatingStatus = model.DonatingStatusConstant()["cancelled"] 247 | if err := utils.WriteLedger(donating, stub, model.DonatingKey, []string{donating.Donor, donating.ObjectOfDonating, donating.Grantee}); err != nil { 248 | return shim.Error(fmt.Sprintf("%s", err)) 249 | } 250 | donatingGrantee.Donating = donating 251 | if err := utils.WriteLedger(donatingGrantee, stub, model.DonatingGranteeKey, []string{donatingGrantee.Grantee, donatingGrantee.CreateTime}); err != nil { 252 | return shim.Error(fmt.Sprintf("%s", err)) 253 | } 254 | data, err = json.Marshal(donatingGrantee) 255 | if err != nil { 256 | return shim.Error(fmt.Sprintf("%s", err)) 257 | } 258 | break 259 | default: 260 | return shim.Error(fmt.Sprintf("%s状态不支持", status)) 261 | } 262 | return shim.Success(data) 263 | } 264 | -------------------------------------------------------------------------------- /chaincode/api/hello.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "chaincode/pkg/utils" 5 | 6 | "github.com/hyperledger/fabric/core/chaincode/shim" 7 | pb "github.com/hyperledger/fabric/protos/peer" 8 | ) 9 | 10 | // Hello 测试 11 | func Hello(stub shim.ChaincodeStubInterface, args []string) pb.Response { 12 | err := utils.WriteLedger(map[string]interface{}{"msg": "hello"}, stub, "hello", []string{}) 13 | if err != nil { 14 | return shim.Error(err.Error()) 15 | } 16 | return shim.Success([]byte("hello world")) 17 | } 18 | -------------------------------------------------------------------------------- /chaincode/api/realEstate.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "chaincode/model" 5 | "chaincode/pkg/utils" 6 | "encoding/json" 7 | "fmt" 8 | 9 | "github.com/hyperledger/fabric/core/chaincode/shim" 10 | pb "github.com/hyperledger/fabric/protos/peer" 11 | ) 12 | 13 | // CreateRealEstate 新建车辆(管理员) 14 | func CreateRealEstate(stub shim.ChaincodeStubInterface, args []string) pb.Response { 15 | // 验证参数 16 | if len(args) != 10 { 17 | return shim.Error("参数个数不满足") 18 | } 19 | accountId := args[0] //accountId用于验证是否为管理员 20 | proprietor := args[1] 21 | //车辆型号 22 | carmodel := args[2] 23 | //汽车生产着 24 | car_scz := args[3] 25 | //汽车生产地 26 | car_scd := args[4] 27 | //汽车生产时间 28 | car_scsj := args[5] 29 | //汽车零部件批号 30 | car_lbjph := args[6] 31 | //汽车零部件生产者 32 | car_lbjscz := args[7] 33 | //汽车零部件生产地 34 | car_lbjscd := args[8] 35 | //汽车零部件生产时间 36 | car_lbjscsj := args[9] 37 | 38 | resultsAccount, err := utils.GetStateByPartialCompositeKeys(stub, model.AccountKey, []string{accountId}) 39 | if err != nil || len(resultsAccount) != 1 { 40 | return shim.Error(fmt.Sprintf("操作人权限验证失败%s", err)) 41 | } 42 | var account model.Account 43 | if err = json.Unmarshal(resultsAccount[0], &account); err != nil { 44 | return shim.Error(fmt.Sprintf("查询操作人信息-反序列化出错: %s", err)) 45 | } 46 | if account.UserName != "管理员" { 47 | return shim.Error(fmt.Sprintf("操作人权限不足%s", err)) 48 | } 49 | //判断客户是否存在 50 | resultsProprietor, err := utils.GetStateByPartialCompositeKeys(stub, model.AccountKey, []string{proprietor}) 51 | if err != nil || len(resultsProprietor) != 1 { 52 | return shim.Error(fmt.Sprintf("客户proprietor信息验证失败%s", err)) 53 | } 54 | realEstate := &model.RealEstate{ 55 | RealEstateID: stub.GetTxID()[:16], 56 | Proprietor: proprietor, 57 | Encumbrance: false, 58 | Carmodel: carmodel, 59 | Car_scz: car_scz, 60 | Car_scd: car_scd, 61 | Car_scsj: car_scsj, 62 | Car_lbjph: car_lbjph, 63 | Car_lbjscz: car_lbjscz, 64 | Car_lbjscd: car_lbjscd, 65 | Car_lbjscsj: car_lbjscsj, 66 | } 67 | // 写入账本 68 | if err := utils.WriteLedger(realEstate, stub, model.RealEstateKey, []string{realEstate.Proprietor, realEstate.RealEstateID}); err != nil { 69 | return shim.Error(fmt.Sprintf("%s", err)) 70 | } 71 | //将成功创建的信息返回 72 | realEstateByte, err := json.Marshal(realEstate) 73 | if err != nil { 74 | return shim.Error(fmt.Sprintf("序列化成功创建的信息出错: %s", err)) 75 | } 76 | // 成功返回 77 | return shim.Success(realEstateByte) 78 | } 79 | 80 | // QueryRealEstateList 查询车辆(可查询所有,也可根据所有人查询名下车辆) 81 | func QueryRealEstateList(stub shim.ChaincodeStubInterface, args []string) pb.Response { 82 | var realEstateList []model.RealEstate 83 | results, err := utils.GetStateByPartialCompositeKeys2(stub, model.RealEstateKey, args) 84 | if err != nil { 85 | return shim.Error(fmt.Sprintf("%s", err)) 86 | } 87 | for _, v := range results { 88 | if v != nil { 89 | var realEstate model.RealEstate 90 | err := json.Unmarshal(v, &realEstate) 91 | if err != nil { 92 | return shim.Error(fmt.Sprintf("QueryRealEstateList-反序列化出错: %s", err)) 93 | } 94 | realEstateList = append(realEstateList, realEstate) 95 | } 96 | } 97 | realEstateListByte, err := json.Marshal(realEstateList) 98 | if err != nil { 99 | return shim.Error(fmt.Sprintf("QueryRealEstateList-序列化出错: %s", err)) 100 | } 101 | return shim.Success(realEstateListByte) 102 | } 103 | -------------------------------------------------------------------------------- /chaincode/chaincode.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "chaincode/api" 5 | "chaincode/model" 6 | "chaincode/pkg/utils" 7 | "fmt" 8 | "time" 9 | 10 | "github.com/hyperledger/fabric/core/chaincode/shim" 11 | pb "github.com/hyperledger/fabric/protos/peer" 12 | ) 13 | 14 | type BlockChainRealEstate struct { 15 | } 16 | 17 | // Init 链码初始化 18 | func (t *BlockChainRealEstate) Init(stub shim.ChaincodeStubInterface) pb.Response { 19 | fmt.Println("链码初始化") 20 | //初始化默认数据 21 | var accountIds = [6]string{ 22 | "5feceb66ffc8", 23 | "6b86b273ff34", 24 | "d4735e3a265e", 25 | "4e07408562be", 26 | "4b227777d4dd", 27 | "ef2d127de37b", 28 | } 29 | var userNames = [6]string{"管理员", "①号客户", "②号客户", "③号客户", "④号客户", "⑤号客户"} 30 | var balances = [6]float64{0, 5000000, 5000000, 5000000, 5000000, 5000000} 31 | //初始化账号数据 32 | for i, val := range accountIds { 33 | account := &model.Account{ 34 | AccountId: val, 35 | UserName: userNames[i], 36 | Balance: balances[i], 37 | } 38 | // 写入账本 39 | if err := utils.WriteLedger(account, stub, model.AccountKey, []string{val}); err != nil { 40 | return shim.Error(fmt.Sprintf("%s", err)) 41 | } 42 | } 43 | return shim.Success(nil) 44 | } 45 | 46 | // Invoke 实现Invoke接口调用智能合约 47 | func (t *BlockChainRealEstate) Invoke(stub shim.ChaincodeStubInterface) pb.Response { 48 | funcName, args := stub.GetFunctionAndParameters() 49 | switch funcName { 50 | case "hello": 51 | return api.Hello(stub, args) 52 | // return api.CreateRealEstate(stub, args) 53 | case "queryAccountList": 54 | return api.QueryAccountList(stub, args) 55 | case "createRealEstate": 56 | return api.CreateRealEstate(stub, args) 57 | case "queryRealEstateList": 58 | return api.QueryRealEstateList(stub, args) 59 | case "createSelling": 60 | return api.CreateSelling(stub, args) 61 | case "createSellingByBuy": 62 | return api.CreateSellingByBuy(stub, args) 63 | case "querySellingList": 64 | return api.QuerySellingList(stub, args) 65 | case "querySellingListByBuyer": 66 | return api.QuerySellingListByBuyer(stub, args) 67 | case "updateSelling": 68 | return api.UpdateSelling(stub, args) 69 | case "createDonating": 70 | return api.CreateDonating(stub, args) 71 | case "queryDonatingList": 72 | return api.QueryDonatingList(stub, args) 73 | case "queryDonatingListByGrantee": 74 | return api.QueryDonatingListByGrantee(stub, args) 75 | case "updateDonating": 76 | return api.UpdateDonating(stub, args) 77 | default: 78 | return shim.Error(fmt.Sprintf("没有该功能: %s", funcName)) 79 | } 80 | } 81 | 82 | func main() { 83 | timeLocal, err := time.LoadLocation("Asia/Shanghai") 84 | if err != nil { 85 | panic(err) 86 | } 87 | time.Local = timeLocal 88 | err = shim.Start(new(BlockChainRealEstate)) 89 | if err != nil { 90 | fmt.Printf("Error starting Simple chaincode: %s", err) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /chaincode/go.mod: -------------------------------------------------------------------------------- 1 | module chaincode 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/Knetic/govaluate v3.0.0+incompatible // indirect 7 | github.com/Shopify/sarama v1.32.0 // indirect 8 | github.com/fsouza/go-dockerclient v1.7.10 // indirect 9 | github.com/hashicorp/go-version v1.4.0 // indirect 10 | github.com/hyperledger/fabric v1.4.12 11 | github.com/hyperledger/fabric-amcl v0.0.0-20210603140002-2670f91851c8 // indirect 12 | github.com/miekg/pkcs11 v1.1.1 // indirect 13 | github.com/onsi/ginkgo v1.16.5 // indirect 14 | github.com/onsi/gomega v1.18.1 // indirect 15 | github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect 16 | github.com/spf13/viper v1.10.1 // indirect 17 | github.com/stretchr/testify v1.7.1 // indirect 18 | github.com/sykesm/zap-logfmt v0.0.4 // indirect 19 | go.uber.org/zap v1.21.0 // indirect 20 | golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect 21 | golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect 22 | google.golang.org/grpc v1.45.0 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /chaincode/model/model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // Account 账户,虚拟管理员和若干客户账号 4 | type Account struct { 5 | AccountId string `json:"accountId"` //账号ID 6 | UserName string `json:"userName"` //账号名 7 | Balance float64 `json:"balance"` //余额 8 | } 9 | 10 | // type RealEstate struct { 11 | // RealEstateID string `json:"realEstateId"` //车辆ID 12 | // Proprietor string `json:"proprietor"` //所有者(客户)(客户AccountId) 13 | // Encumbrance bool `json:"encumbrance"` //是否作为担保 14 | // Carmodel string `json:"carmodel"` 15 | // Car_scz string `json:"car_scz"` 16 | // Car_scd string `json:"car_scd"` 17 | // Car_scsj string `json:"car_scsj"` 18 | // Car_lbjph string `json:"car_lbjph"` 19 | // Car_lbjscz string `json:"Car_lbjscz"` 20 | // Car_lbjscd string `json:"car_lbjscd"` 21 | // Car_lbjscsj string `json:"car_lbjscsj"` 22 | // } 23 | 24 | // RealEstate 车辆作为担保出售、捐赠或质押时Encumbrance为true,默认状态false。 25 | // 仅当Encumbrance为false时,才可发起出售、捐赠或质押 26 | // Proprietor和RealEstateID一起作为复合键,保证可以通过Proprietor查询到名下所有的商品信息 27 | type RealEstate struct { 28 | RealEstateID string `json:"realEstateId"` //车辆ID 29 | Proprietor string `json:"proprietor"` //所有者(客户)(客户AccountId) 30 | Encumbrance bool `json:"encumbrance"` //是否作为担保 31 | Carmodel string `json:"carmodel"` 32 | Car_scz string `json:"car_scz"` 33 | Car_scd string `json:"car_scd"` 34 | Car_scsj string `json:"car_scsj"` 35 | Car_lbjph string `json:"car_lbjph"` 36 | Car_lbjscz string `json:"car_lbjscz"` 37 | Car_lbjscd string `json:"car_lbjscd"` 38 | Car_lbjscsj string `json:"car_lbjscsj"` 39 | } 40 | 41 | // Selling 销售要约 42 | // 需要确定ObjectOfSale是否属于Seller 43 | // 买家初始为空 44 | // Seller和ObjectOfSale一起作为复合键,保证可以通过seller查询到名下所有发起的销售 45 | type Selling struct { 46 | ObjectOfSale string `json:"objectOfSale"` //销售对象(正在出售的车辆RealEstateID) 47 | Seller string `json:"seller"` //发起销售人、卖家(卖家AccountId) 48 | Buyer string `json:"buyer"` //参与销售人、买家(买家AccountId) 49 | Price float64 `json:"price"` //价格 50 | CreateTime string `json:"createTime"` //创建时间 51 | SalePeriod int `json:"salePeriod"` //智能合约的有效期(单位为天) 52 | SellingStatus string `json:"sellingStatus"` //销售状态 53 | } 54 | 55 | // SellingStatusConstant 销售状态 56 | var SellingStatusConstant = func() map[string]string { 57 | return map[string]string{ 58 | "saleStart": "销售中", //正在销售状态,等待买家光顾 59 | "cancelled": "已取消", //被卖家取消销售或买家退款操作导致取消 60 | "expired": "已过期", //销售期限到期 61 | "delivery": "交付中", //买家买下并付款,处于等待卖家确认收款状态,如若卖家未能确认收款,买家可以取消并退款 62 | "done": "完成", //卖家确认接收资金,交易完成 63 | } 64 | } 65 | 66 | // SellingBuy 买家参与销售 67 | // 销售对象不能是买家发起的 68 | // Buyer和CreateTime作为复合键,保证可以通过buyer查询到名下所有参与的销售 69 | type SellingBuy struct { 70 | Buyer string `json:"buyer"` //参与销售人、买家(买家AccountId) 71 | CreateTime string `json:"createTime"` //创建时间 72 | Selling Selling `json:"selling"` //销售对象 73 | } 74 | 75 | // Donating 捐赠要约 76 | // 需要确定ObjectOfDonating是否属于Donor 77 | // 需要指定受赠人Grantee,并等待受赠人同意接收 78 | type Donating struct { 79 | ObjectOfDonating string `json:"objectOfDonating"` //捐赠对象(正在捐赠的车辆RealEstateID) 80 | Donor string `json:"donor"` //捐赠人(捐赠人AccountId) 81 | Grantee string `json:"grantee"` //受赠人(受赠人AccountId) 82 | CreateTime string `json:"createTime"` //创建时间 83 | DonatingStatus string `json:"donatingStatus"` //捐赠状态 84 | } 85 | 86 | // DonatingStatusConstant 捐赠状态 87 | var DonatingStatusConstant = func() map[string]string { 88 | return map[string]string{ 89 | "donatingStart": "捐赠中", //捐赠人发起捐赠合约,等待受赠人确认受赠 90 | "cancelled": "已取消", //捐赠人在受赠人确认受赠之前取消捐赠或受赠人取消接收受赠 91 | "done": "完成", //受赠人确认接收,交易完成 92 | } 93 | } 94 | 95 | // DonatingGrantee 供受赠人查询的 96 | type DonatingGrantee struct { 97 | Grantee string `json:"grantee"` //受赠人(受赠人AccountId) 98 | CreateTime string `json:"createTime"` //创建时间 99 | Donating Donating `json:"donating"` //捐赠对象 100 | } 101 | 102 | const ( 103 | AccountKey = "account-key" 104 | RealEstateKey = "real-estate-key" 105 | SellingKey = "selling-key" 106 | SellingBuyKey = "selling-buy-key" 107 | DonatingKey = "donating-key" 108 | DonatingGranteeKey = "donating-grantee-key" 109 | ) 110 | -------------------------------------------------------------------------------- /chaincode/pkg/utils/fabric.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/hyperledger/fabric/core/chaincode/shim" 9 | ) 10 | 11 | // WriteLedger 写入账本 12 | func WriteLedger(obj interface{}, stub shim.ChaincodeStubInterface, objectType string, keys []string) error { 13 | //创建复合主键 14 | var key string 15 | if val, err := stub.CreateCompositeKey(objectType, keys); err != nil { 16 | return errors.New(fmt.Sprintf("%s-创建复合主键出错 %s", objectType, err)) 17 | } else { 18 | key = val 19 | } 20 | bytes, err := json.Marshal(obj) 21 | if err != nil { 22 | return errors.New(fmt.Sprintf("%s-序列化json数据失败出错: %s", objectType, err)) 23 | } 24 | //写入区块链账本 25 | if err := stub.PutState(key, bytes); err != nil { 26 | return errors.New(fmt.Sprintf("%s-写入区块链账本出错: %s", objectType, err)) 27 | } 28 | return nil 29 | } 30 | 31 | // DelLedger 删除账本 32 | func DelLedger(stub shim.ChaincodeStubInterface, objectType string, keys []string) error { 33 | //创建复合主键 34 | var key string 35 | if val, err := stub.CreateCompositeKey(objectType, keys); err != nil { 36 | return errors.New(fmt.Sprintf("%s-创建复合主键出错 %s", objectType, err)) 37 | } else { 38 | key = val 39 | } 40 | //写入区块链账本 41 | if err := stub.DelState(key); err != nil { 42 | return errors.New(fmt.Sprintf("%s-删除区块链账本出错: %s", objectType, err)) 43 | } 44 | return nil 45 | } 46 | 47 | // GetStateByPartialCompositeKeys 根据复合主键查询数据(适合获取全部,多个,单个数据) 48 | // 将keys拆分查询 49 | func GetStateByPartialCompositeKeys(stub shim.ChaincodeStubInterface, objectType string, keys []string) (results [][]byte, err error) { 50 | if len(keys) == 0 { 51 | // 传入的keys长度为0,则查找并返回所有数据 52 | // 通过主键从区块链查找相关的数据,相当于对主键的模糊查询 53 | resultIterator, err := stub.GetStateByPartialCompositeKey(objectType, keys) 54 | if err != nil { 55 | return nil, errors.New(fmt.Sprintf("%s-获取全部数据出错: %s", objectType, err)) 56 | } 57 | defer resultIterator.Close() 58 | 59 | //检查返回的数据是否为空,不为空则遍历数据,否则返回空数组 60 | for resultIterator.HasNext() { 61 | val, err := resultIterator.Next() 62 | if err != nil { 63 | return nil, errors.New(fmt.Sprintf("%s-返回的数据出错: %s", objectType, err)) 64 | } 65 | 66 | results = append(results, val.GetValue()) 67 | } 68 | } else { 69 | // 传入的keys长度不为0,查找相应的数据并返回 70 | for _, v := range keys { 71 | // 创建组合键 72 | key, err := stub.CreateCompositeKey(objectType, []string{v}) 73 | if err != nil { 74 | return nil, errors.New(fmt.Sprintf("%s-创建组合键出错: %s", objectType, err)) 75 | } 76 | // 从账本中获取数据 77 | bytes, err := stub.GetState(key) 78 | if err != nil { 79 | return nil, errors.New(fmt.Sprintf("%s-获取数据出错: %s", objectType, err)) 80 | } 81 | 82 | if bytes != nil { 83 | results = append(results, bytes) 84 | } 85 | } 86 | } 87 | 88 | return results, nil 89 | } 90 | 91 | // GetStateByPartialCompositeKeys2 根据复合主键查询数据(适合获取全部或指定的数据) 92 | func GetStateByPartialCompositeKeys2(stub shim.ChaincodeStubInterface, objectType string, keys []string) (results [][]byte, err error) { 93 | // 通过主键从区块链查找相关的数据,相当于对主键的模糊查询 94 | resultIterator, err := stub.GetStateByPartialCompositeKey(objectType, keys) 95 | if err != nil { 96 | return nil, errors.New(fmt.Sprintf("%s-获取全部数据出错: %s", objectType, err)) 97 | } 98 | defer resultIterator.Close() 99 | 100 | //检查返回的数据是否为空,不为空则遍历数据,否则返回空数组 101 | for resultIterator.HasNext() { 102 | val, err := resultIterator.Next() 103 | if err != nil { 104 | return nil, errors.New(fmt.Sprintf("%s-返回的数据出错: %s", objectType, err)) 105 | } 106 | 107 | results = append(results, val.GetValue()) 108 | } 109 | return results, nil 110 | } 111 | -------------------------------------------------------------------------------- /network/config/BENZAnchor.tx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HansomeZhang/Design-and-implementation-of-copyright-management-system-based-on-blockchain/6d23ad41bf023bacf01677a1fdce9eb3be787934/network/config/BENZAnchor.tx -------------------------------------------------------------------------------- /network/config/appchannel.tx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HansomeZhang/Design-and-implementation-of-copyright-management-system-based-on-blockchain/6d23ad41bf023bacf01677a1fdce9eb3be787934/network/config/appchannel.tx -------------------------------------------------------------------------------- /network/config/genesis.block: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HansomeZhang/Design-and-implementation-of-copyright-management-system-based-on-blockchain/6d23ad41bf023bacf01677a1fdce9eb3be787934/network/config/genesis.block -------------------------------------------------------------------------------- /network/configtx.yaml: -------------------------------------------------------------------------------- 1 | # 定义组织机构实体 2 | Organizations: 3 | - &CARUNION 4 | Name: CARUNION # 组织的名称 5 | ID: CARUNIONMSP # 组织的 MSPID 6 | MSPDir: crypto-config/ordererOrganizations/carunion.com/msp #组织的证书相对位置(生成的crypto-config目录) 7 | 8 | - &BENZ 9 | Name: BENZ 10 | ID: BENZMSP 11 | MSPDir: crypto-config/peerOrganizations/benz.com/msp 12 | AnchorPeers: # 组织锚节点的配置 13 | - Host: peer0.benz.com 14 | Port: 7051 15 | 16 | - &TESLA 17 | Name: TESLA 18 | ID: TESLAMSP 19 | MSPDir: crypto-config/peerOrganizations/tesla.com/msp 20 | AnchorPeers: # 组织锚节点的配置 21 | - Host: peer0.tesla.com 22 | Port: 7051 23 | 24 | # 定义了排序服务的相关参数,这些参数将用于创建创世区块 25 | Orderer: &OrdererDefaults 26 | # 排序节点类型用来指定要启用的排序节点实现,不同的实现对应不同的共识算法 27 | OrdererType: solo # 共识机制 28 | Addresses: # Orderer 的域名(用于连接) 29 | - orderer.carunion.com:7050 30 | BatchTimeout: 2s # 出块时间间隔 31 | BatchSize: # 用于控制每个block的信息量 32 | MaxMessageCount: 10 #每个区块的消息个数 33 | AbsoluteMaxBytes: 99 MB #每个区块最大的信息大小 34 | PreferredMaxBytes: 512 KB #每个区块包含的一条信息最大长度 35 | Organizations: 36 | 37 | # 定义Peer组织如何与应用程序通道交互的策略 38 | # 默认策略:所有Peer组织都将能够读取数据并将数据写入账本 39 | Application: &ApplicationDefaults 40 | Organizations: 41 | 42 | # 用来定义用于 configtxgen 工具的配置入口 43 | # 将 Profile 参数( TwoOrgsOrdererGenesis 或 TwoOrgsChannel )指定为 configtxgen 工具的参数 44 | Profiles: 45 | # TwoOrgsOrdererGenesis配置文件用于创建系统通道创世块 46 | # 该配置文件创建一个名为SampleConsortium的联盟 47 | # 该联盟在configtx.yaml文件中包含两个Peer组织BENZ和TESLA 48 | TwoOrgsOrdererGenesis: 49 | Orderer: 50 | <<: *OrdererDefaults 51 | Organizations: 52 | - *CARUNION 53 | Consortiums: 54 | SampleConsortium: 55 | Organizations: 56 | - *BENZ 57 | - *TESLA 58 | # 使用TwoOrgsChannel配置文件创建应用程序通道 59 | TwoOrgsChannel: 60 | Consortium: SampleConsortium 61 | Application: 62 | <<: *ApplicationDefaults 63 | Organizations: 64 | - *BENZ 65 | - *TESLA -------------------------------------------------------------------------------- /network/crypto-config.yaml: -------------------------------------------------------------------------------- 1 | # 排序节点的组织定义 2 | OrdererOrgs: 3 | - Name: CARUNION # 名称 4 | Domain: carunion.com # 域名 5 | Specs: # 节点域名:orderer.carunion.com 6 | - Hostname: orderer # 主机名 7 | 8 | # peer节点的组织定义 9 | PeerOrgs: 10 | # BENZ-组织 11 | - Name: BENZ # 名称 12 | Domain: benz.com # 域名 13 | Template: # 使用模板定义。Count 指的是该组织下组织节点的个数 14 | Count: 2 # 节点域名:peer0.benz.com 和 peer1.benz.com 15 | Users: # 组织的用户信息。Count 指该组织中除了 Admin 之外的用户的个数 16 | Count: 1 # 用户:Admin 和 User1 17 | 18 | # TESLA-组织 19 | - Name: TESLA 20 | Domain: tesla.com 21 | Template: 22 | Count: 2 # 节点域名:peer0.tesla.com 和 peer1.tesla.com 23 | Users: 24 | Count: 1 # 用户:Admin 和 User1 -------------------------------------------------------------------------------- /network/docker-compose-base.yaml: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | 3 | services: 4 | peer-base: # peer的公共服务 5 | image: hyperledger/fabric-peer:1.4.12 6 | environment: 7 | - GODEBUG=netdns=go 8 | - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock 9 | - CORE_LOGGING_PEER=info 10 | - CORE_CHAINCODE_LOGGING_LEVEL=INFO 11 | - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/peer/msp # msp证书(节点证书) 12 | - CORE_LEDGER_STATE_STATEDATABASE=goleveldb # 状态数据库的存储引擎(or CouchDB) 13 | - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fabric_network # docker 网络 14 | volumes: 15 | - /var/run/docker.sock:/host/var/run/docker.sock 16 | working_dir: /opt/gopath/src/github.com/hyperledger/fabric 17 | command: peer node start 18 | networks: 19 | - fabric_network -------------------------------------------------------------------------------- /network/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | 3 | volumes: 4 | orderer.carunion.com: 5 | peer0.benz.com: 6 | peer1.benz.com: 7 | peer0.tesla.com: 8 | peer1.tesla.com: 9 | 10 | networks: 11 | fabric_network: 12 | name: fabric_network 13 | 14 | services: 15 | # 排序服务节点 16 | orderer.carunion.com: 17 | container_name: orderer.carunion.com 18 | image: hyperledger/fabric-orderer:1.4.12 19 | environment: 20 | - GODEBUG=netdns=go 21 | - ORDERER_GENERAL_LISTENADDRESS=0.0.0.0 22 | - ORDERER_GENERAL_GENESISMETHOD=file 23 | - ORDERER_GENERAL_GENESISFILE=/etc/hyperledger/config/genesis.block # 注入创世区块 24 | - ORDERER_GENERAL_LOCALMSPID=CARUNIONMSP 25 | - ORDERER_GENERAL_LOCALMSPDIR=/etc/hyperledger/orderer/msp # 证书相关 26 | command: orderer 27 | ports: 28 | - "7050:7050" 29 | volumes: # 挂载由cryptogen和configtxgen生成的证书文件以及创世区块 30 | - ./config/genesis.block:/etc/hyperledger/config/genesis.block 31 | - ./crypto-config/ordererOrganizations/carunion.com/orderers/orderer.carunion.com/:/etc/hyperledger/orderer 32 | - orderer.carunion.com:/var/hyperledger/production/orderer 33 | networks: 34 | - fabric_network 35 | 36 | # BENZ 组织 peer0 节点 37 | peer0.benz.com: 38 | extends: 39 | file: docker-compose-base.yaml 40 | service: peer-base 41 | container_name: peer0.benz.com 42 | environment: 43 | - CORE_PEER_ID=peer0.benz.com 44 | - CORE_PEER_LOCALMSPID=BENZMSP 45 | - CORE_PEER_ADDRESS=peer0.benz.com:7051 46 | ports: 47 | - "7051:7051" # grpc服务端口 48 | - "7053:7053" # eventhub端口 49 | volumes: 50 | - ./crypto-config/peerOrganizations/benz.com/peers/peer0.benz.com:/etc/hyperledger/peer 51 | - peer0.benz.com:/var/hyperledger/production 52 | depends_on: 53 | - orderer.carunion.com 54 | 55 | # BENZ 组织 peer1 节点 56 | peer1.benz.com: 57 | extends: 58 | file: docker-compose-base.yaml 59 | service: peer-base 60 | container_name: peer1.benz.com 61 | environment: 62 | - CORE_PEER_ID=peer1.benz.com 63 | - CORE_PEER_LOCALMSPID=BENZMSP 64 | - CORE_PEER_ADDRESS=peer1.benz.com:7051 65 | ports: 66 | - "17051:7051" 67 | - "17053:7053" 68 | volumes: 69 | - ./crypto-config/peerOrganizations/benz.com/peers/peer1.benz.com:/etc/hyperledger/peer 70 | - peer1.benz.com:/var/hyperledger/production 71 | depends_on: 72 | - orderer.carunion.com 73 | 74 | # TESLA 组织 peer0 节点 75 | peer0.tesla.com: 76 | extends: 77 | file: docker-compose-base.yaml 78 | service: peer-base 79 | container_name: peer0.tesla.com 80 | environment: 81 | - CORE_PEER_ID=peer0.tesla.com 82 | - CORE_PEER_LOCALMSPID=TESLAMSP 83 | - CORE_PEER_ADDRESS=peer0.tesla.com:7051 84 | ports: 85 | - "27051:7051" 86 | - "27053:7053" 87 | volumes: 88 | - ./crypto-config/peerOrganizations/tesla.com/peers/peer0.tesla.com:/etc/hyperledger/peer 89 | - peer0.tesla.com:/var/hyperledger/production 90 | depends_on: 91 | - orderer.carunion.com 92 | 93 | # TESLA 组织 peer1 节点 94 | peer1.tesla.com: 95 | extends: 96 | file: docker-compose-base.yaml 97 | service: peer-base 98 | container_name: peer1.tesla.com 99 | environment: 100 | - CORE_PEER_ID=peer1.tesla.com 101 | - CORE_PEER_LOCALMSPID=TESLAMSP 102 | - CORE_PEER_ADDRESS=peer1.tesla.com:7051 103 | ports: 104 | - "37051:7051" 105 | - "37053:7053" 106 | volumes: 107 | - ./crypto-config/peerOrganizations/tesla.com/peers/peer1.tesla.com:/etc/hyperledger/peer 108 | - peer1.tesla.com:/var/hyperledger/production 109 | depends_on: 110 | - orderer.carunion.com 111 | 112 | # 客户端节点 113 | cli: 114 | container_name: cli 115 | image: hyperledger/fabric-tools:1.4.12 116 | tty: true 117 | environment: 118 | # go 环境设置 119 | - GO111MODULE=auto 120 | - GOPROXY=https://goproxy.cn 121 | - CORE_PEER_ID=cli 122 | command: /bin/bash 123 | volumes: 124 | - ./config:/etc/hyperledger/config 125 | - ./crypto-config/peerOrganizations/benz.com/:/etc/hyperledger/peer/benz.com 126 | - ./crypto-config/peerOrganizations/tesla.com/:/etc/hyperledger/peer/tesla.com 127 | - ./../chaincode:/opt/gopath/src/chaincode # 链码路径注入 128 | networks: 129 | - fabric_network 130 | depends_on: 131 | - orderer.carunion.com 132 | - peer0.benz.com 133 | - peer1.benz.com 134 | - peer0.tesla.com 135 | - peer1.tesla.com -------------------------------------------------------------------------------- /network/explorer/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "network-configs": { 3 | "test-network": { 4 | "name": "Fabric Network", 5 | "profile": "./connection-profile/network.json" 6 | } 7 | }, 8 | "license": "Apache-2.0" 9 | } -------------------------------------------------------------------------------- /network/explorer/connection-profile/network.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fabric-network", 3 | "version": "1.0.0", 4 | "client": { 5 | "tlsEnable": true, 6 | "adminCredential": { 7 | "id": "admin", 8 | "password": "123456" 9 | }, 10 | "enableAuthentication": true, 11 | "organization": "BENZMSP", 12 | "connection": { 13 | "timeout": { 14 | "peer": { 15 | "endorser": "300" 16 | }, 17 | "orderer": "300" 18 | } 19 | } 20 | }, 21 | "channels": { 22 | "appchannel": { 23 | "peers": { 24 | "peer0.benz.com": {} 25 | } 26 | } 27 | }, 28 | "organizations": { 29 | "BENZMSP": { 30 | "mspid": "BENZMSP", 31 | "adminPrivateKey": { 32 | "path": "/tmp/crypto/peerOrganizations/benz.com/users/Admin@benz.com/msp/keystore/873639716107b3746970537e1558c5f17436032abe2c83dd60ca7b1e9f421488_sk" 33 | }, 34 | "peers": [ 35 | "peer0.benz.com" 36 | ], 37 | "signedCert": { 38 | "path": "/tmp/crypto/peerOrganizations/benz.com/users/Admin@benz.com/msp/signcerts/Admin@benz.com-cert.pem" 39 | } 40 | } 41 | }, 42 | "peers": { 43 | "peer0.benz.com": { 44 | "tlsCACerts": { 45 | "path": "/tmp/crypto/peerOrganizations/benz.com/peers/peer0.benz.com/tls/ca.crt" 46 | }, 47 | "url": "grpc://peer0.benz.com:7051" 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /network/explorer/connection-profile/network_temp.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fabric-network", 3 | "version": "1.0.0", 4 | "client": { 5 | "tlsEnable": true, 6 | "adminCredential": { 7 | "id": "admin", 8 | "password": "123456" 9 | }, 10 | "enableAuthentication": true, 11 | "organization": "BENZMSP", 12 | "connection": { 13 | "timeout": { 14 | "peer": { 15 | "endorser": "300" 16 | }, 17 | "orderer": "300" 18 | } 19 | } 20 | }, 21 | "channels": { 22 | "appchannel": { 23 | "peers": { 24 | "peer0.benz.com": {} 25 | } 26 | } 27 | }, 28 | "organizations": { 29 | "BENZMSP": { 30 | "mspid": "BENZMSP", 31 | "adminPrivateKey": { 32 | "path": "/tmp/crypto/peerOrganizations/benz.com/users/Admin@benz.com/msp/keystore/priv_sk" 33 | }, 34 | "peers": [ 35 | "peer0.benz.com" 36 | ], 37 | "signedCert": { 38 | "path": "/tmp/crypto/peerOrganizations/benz.com/users/Admin@benz.com/msp/signcerts/Admin@benz.com-cert.pem" 39 | } 40 | } 41 | }, 42 | "peers": { 43 | "peer0.benz.com": { 44 | "tlsCACerts": { 45 | "path": "/tmp/crypto/peerOrganizations/benz.com/peers/peer0.benz.com/tls/ca.crt" 46 | }, 47 | "url": "grpc://peer0.benz.com:7051" 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /network/explorer/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | version: '2.1' 3 | 4 | volumes: 5 | pgdata: 6 | walletstore: 7 | 8 | networks: 9 | fabric_network: 10 | external: 11 | name: fabric_network 12 | 13 | services: 14 | 15 | explorerdb.com: 16 | image: hyperledger/explorer-db:1.1.6 17 | container_name: explorerdb.com 18 | hostname: explorerdb.com 19 | environment: 20 | - DATABASE_DATABASE=fabricexplorer 21 | - DATABASE_USERNAME=hppoc 22 | - DATABASE_PASSWORD=password 23 | healthcheck: 24 | test: "pg_isready -h localhost -p 5432 -q -U postgres" 25 | interval: 30s 26 | timeout: 10s 27 | retries: 5 28 | volumes: 29 | - pgdata:/var/lib/postgresql/data 30 | networks: 31 | - fabric_network 32 | 33 | explorer.com: 34 | image: hyperledger/explorer:1.1.6 35 | container_name: explorer.com 36 | hostname: explorer.com 37 | environment: 38 | - DATABASE_HOST=explorerdb.com 39 | - DATABASE_DATABASE=fabricexplorer 40 | - DATABASE_USERNAME=hppoc 41 | - DATABASE_PASSWD=password 42 | - LOG_LEVEL_APP=debug 43 | - LOG_LEVEL_DB=debug 44 | - LOG_LEVEL_CONSOLE=info 45 | - LOG_CONSOLE_STDOUT=true 46 | - DISCOVERY_AS_LOCALHOST=false 47 | volumes: 48 | - ./config.json:/opt/explorer/app/platform/fabric/config.json 49 | - ./connection-profile:/opt/explorer/app/platform/fabric/connection-profile 50 | - ../crypto-config:/tmp/crypto 51 | - walletstore:/opt/explorer/wallet 52 | ports: 53 | - "8080:8080" 54 | depends_on: 55 | explorerdb.com: 56 | condition: service_healthy 57 | networks: 58 | - fabric_network -------------------------------------------------------------------------------- /network/explorer/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | priv_sk_path=$(ls ../crypto-config/peerOrganizations/benz.com/users/Admin\@benz.com/msp/keystore/) 4 | 5 | cp -rf ./connection-profile/network_temp.json ./connection-profile/network.json 6 | 7 | sed -i "s/priv_sk/$priv_sk_path/" ./connection-profile/network.json 8 | 9 | docker-compose down -v 10 | docker-compose up -d -------------------------------------------------------------------------------- /network/explorer/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose down -v -------------------------------------------------------------------------------- /network/hyperledger-fabric-darwin-amd64-1.4.12/bin/configtxgen: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HansomeZhang/Design-and-implementation-of-copyright-management-system-based-on-blockchain/6d23ad41bf023bacf01677a1fdce9eb3be787934/network/hyperledger-fabric-darwin-amd64-1.4.12/bin/configtxgen -------------------------------------------------------------------------------- /network/hyperledger-fabric-darwin-amd64-1.4.12/bin/cryptogen: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HansomeZhang/Design-and-implementation-of-copyright-management-system-based-on-blockchain/6d23ad41bf023bacf01677a1fdce9eb3be787934/network/hyperledger-fabric-darwin-amd64-1.4.12/bin/cryptogen -------------------------------------------------------------------------------- /network/hyperledger-fabric-linux-amd64-1.4.12/bin/configtxgen: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HansomeZhang/Design-and-implementation-of-copyright-management-system-based-on-blockchain/6d23ad41bf023bacf01677a1fdce9eb3be787934/network/hyperledger-fabric-linux-amd64-1.4.12/bin/configtxgen -------------------------------------------------------------------------------- /network/hyperledger-fabric-linux-amd64-1.4.12/bin/cryptogen: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HansomeZhang/Design-and-implementation-of-copyright-management-system-based-on-blockchain/6d23ad41bf023bacf01677a1fdce9eb3be787934/network/hyperledger-fabric-linux-amd64-1.4.12/bin/cryptogen -------------------------------------------------------------------------------- /network/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ `uname` == 'Darwin' ]]; then 4 | echo "Mac OS" 5 | export PATH=${PWD}/hyperledger-fabric-darwin-amd64-1.4.12/bin:$PATH 6 | fi 7 | if [[ `uname` == 'Linux' ]]; then 8 | echo "Linux" 9 | export PATH=${PWD}/hyperledger-fabric-linux-amd64-1.4.12/bin:$PATH 10 | fi 11 | 12 | echo "一、清理环境" 13 | ./stop.sh 14 | 15 | echo "二、生成证书和秘钥( MSP 材料),生成结果将保存在 crypto-config 文件夹中" 16 | cryptogen generate --config=./crypto-config.yaml 17 | 18 | echo "三、创建排序通道创世区块" 19 | configtxgen -profile TwoOrgsOrdererGenesis -outputBlock ./config/genesis.block -channelID firstchannel 20 | 21 | echo "四、生成通道配置事务'appchannel.tx'" 22 | configtxgen -profile TwoOrgsChannel -outputCreateChannelTx ./config/appchannel.tx -channelID appchannel 23 | 24 | echo "五、为 BENZ 定义锚节点" 25 | configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./config/BENZAnchor.tx -channelID appchannel -asOrg BENZ 26 | 27 | echo "六、为 TESLA 定义锚节点" 28 | configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./config/TESLAAnchor.tx -channelID appchannel -asOrg TESLA 29 | 30 | echo "区块链 : 启动" 31 | docker-compose up -d 32 | echo "正在等待节点的启动完成,等待10秒" 33 | sleep 10 34 | 35 | BENZPeer0Cli="CORE_PEER_ADDRESS=peer0.benz.com:7051 CORE_PEER_LOCALMSPID=BENZMSP CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/peer/benz.com/users/Admin@benz.com/msp" 36 | BENZPeer1Cli="CORE_PEER_ADDRESS=peer1.benz.com:7051 CORE_PEER_LOCALMSPID=BENZMSP CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/peer/benz.com/users/Admin@benz.com/msp" 37 | TESLAPeer0Cli="CORE_PEER_ADDRESS=peer0.tesla.com:7051 CORE_PEER_LOCALMSPID=TESLAMSP CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/peer/tesla.com/users/Admin@tesla.com/msp" 38 | TESLAPeer1Cli="CORE_PEER_ADDRESS=peer1.tesla.com:7051 CORE_PEER_LOCALMSPID=TESLAMSP CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/peer/tesla.com/users/Admin@tesla.com/msp" 39 | 40 | echo "七、创建通道" 41 | docker exec cli bash -c "$BENZPeer0Cli peer channel create -o orderer.carunion.com:7050 -c appchannel -f /etc/hyperledger/config/appchannel.tx" 42 | 43 | echo "八、将所有节点加入通道" 44 | docker exec cli bash -c "$BENZPeer0Cli peer channel join -b appchannel.block" 45 | docker exec cli bash -c "$BENZPeer1Cli peer channel join -b appchannel.block" 46 | docker exec cli bash -c "$TESLAPeer0Cli peer channel join -b appchannel.block" 47 | docker exec cli bash -c "$TESLAPeer1Cli peer channel join -b appchannel.block" 48 | 49 | echo "九、更新锚节点" 50 | docker exec cli bash -c "$BENZPeer0Cli peer channel update -o orderer.carunion.com:7050 -c appchannel -f /etc/hyperledger/config/BENZAnchor.tx" 51 | docker exec cli bash -c "$TESLAPeer0Cli peer channel update -o orderer.carunion.com:7050 -c appchannel -f /etc/hyperledger/config/TESLAAnchor.tx" 52 | 53 | # -n 链码名,可以自己随便设置 54 | # -v 版本号 55 | # -p 链码目录,在 /opt/gopath/src/ 目录下 56 | echo "十、安装链码" 57 | docker exec cli bash -c "$BENZPeer0Cli peer chaincode install -n fabric-realty -v 1.0.0 -l golang -p chaincode" 58 | docker exec cli bash -c "$TESLAPeer0Cli peer chaincode install -n fabric-realty -v 1.0.0 -l golang -p chaincode" 59 | 60 | # 只需要其中一个节点实例化 61 | # -n 对应上一步安装链码的名字 62 | # -v 版本号 63 | # -C 是通道,在fabric的世界,一个通道就是一条不同的链 64 | # -c 为传参,传入init参数 65 | echo "十一、实例化链码" 66 | docker exec cli bash -c "$BENZPeer0Cli peer chaincode instantiate -o orderer.carunion.com:7050 -C appchannel -n fabric-realty -l golang -v 1.0.0 -c '{\"Args\":[\"init\"]}' -P \"AND ('BENZMSP.member','TESLAMSP.member')\"" 67 | 68 | echo "正在等待链码实例化完成,等待5秒" 69 | sleep 5 70 | 71 | # 进行链码交互,验证链码是否正确安装及区块链网络能否正常工作 72 | echo "十二、验证链码" 73 | docker exec cli bash -c "$BENZPeer0Cli peer chaincode invoke -C appchannel -n fabric-realty -c '{\"Args\":[\"hello\"]}'" 74 | docker exec cli bash -c "$TESLAPeer0Cli peer chaincode invoke -C appchannel -n fabric-realty -c '{\"Args\":[\"hello\"]}'" -------------------------------------------------------------------------------- /network/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 清除链码容器 4 | function clearContainers() { 5 | CONTAINER_IDS=$(docker ps -a | awk '($2 ~ /dev-peer.*fabric-realty.*/) {print $1}') 6 | if [ -z "$CONTAINER_IDS" -o "$CONTAINER_IDS" == " " ]; then 7 | echo "---- No containers available for deletion ----" 8 | else 9 | docker rm -f $CONTAINER_IDS 10 | fi 11 | } 12 | 13 | # 清除链码镜像 14 | function removeUnwantedImages() { 15 | DOCKER_IMAGE_IDS=$(docker images | awk '($1 ~ /dev-peer.*fabric-realty.*/) {print $3}') 16 | if [ -z "$DOCKER_IMAGE_IDS" -o "$DOCKER_IMAGE_IDS" == " " ]; then 17 | echo "---- No images available for deletion ----" 18 | else 19 | docker rmi -f $DOCKER_IMAGE_IDS 20 | fi 21 | } 22 | 23 | echo "清理环境" 24 | mkdir -p config 25 | mkdir -p crypto-config 26 | rm -rf config/* 27 | rm -rf crypto-config/* 28 | docker-compose down -v 29 | clearContainers 30 | removeUnwantedImages 31 | echo "清理完毕" --------------------------------------------------------------------------------