区块链食品溯源项目

创建项目

bee new trace

安装go-sdk

go get github.com/FISCO-BCOS/go-sdk
  • 将webase的sdk证书文件复制到自己的项目

  • 修改config.toml使sdk是本项目下

修改配置文件app.conf

appname = tracehttpport = 8080runmode = devautorender = falsecopyrequestbody = trueEnableDocs = truecontractAddress=0xc5a3d4384897acb9ce3e9096644c35aa903d1687distributorAddress=0x1cb30f505b9e338bb48f9cec3ceadd1d2d4da565distributorPK=5eff4114710e69a7677a39145b71ab4b763c126aa118defd2f0316a604e9495cad29c3854604e3b7721588b4778d047e2deb9911a48ae04125e22fdce8eda292producerAddress=0x1cb30f505b9e338bb48f9cec3ceadd1d2d4da565producerPK=e885dc09e398461121608831780410fb2e6c782d2afc95e324057dec60e07f34d8bde9f6cdc63dd79591988165b63b9ebb96e0ddbc823fe80c6570bcee5c97fdretailerAddress=0x746afad125d42011b289ef421e8703e0dd867b60retailerPK=47f39c99583acea98ab31b5b38f13bd0c05726e9304cf5e803bc4cf82910c1ddb80b7941bb9bad4db450e2d761045161ea67b8cb74a66f738004d5927c3a1f70

地址私钥根据自己的webase修改

新建conf.go获取配置文件信息

package conf​import ("fmt"​"github.com/astaxie/beego")​var (ContractAddressstringDistributorAddress stringDistributorPK stringProducerAddressstringProducerPK stringRetailerAddressstringRetailerPK string)​func init() {err := beego.LoadAppConfig("ini","conf/app.conf")if err != nil{fmt.Println(err)return}ContractAddress, _ =beego.AppConfig.String("contractAddress")DistributorAddress,_ = beego.AppConfig.String("distributorAddress")DistributorPK,_ = beego.AppConfig.String("distributorPK")ProducerAddress,_ = beego.AppConfig.String("producerAddress")ProducerPK,_ = beego.AppConfig.String("producerPK")RetailerAddress,_ = beego.AppConfig.String("retailerAddress")RetailerPK,_ = beego.AppConfig.String("retailerPK")}

将go-sdk下config.go和config_pem.go复制到项目目录conf目录下

编写Service层

将合约编译成go文件

abigen -abi abi/FoodInfo.abi -bin bin/FoodInfo.bin -pkg main -type FoodInfo -out FoodInfo.go​abigen -abi abi/Role.abi -bin bin/Role.bin -pkg main -type Role -out Role.go​abigen -abi abi/RoleController.abi -bin bin/RoleController.bin -pkg main -type RoleController -out RoleController.go​abigen -abi abi/Trace.abi -bin bin/Trace.bin -pkg main -type Trace -out Trace.go

编写TraceService

与合约进行交互

package service​import ("log"​"github.com/FISCO-BCOS/go-sdk/abi/bind""github.com/FISCO-BCOS/go-sdk/client"conf2 "github.com/FISCO-BCOS/go-sdk/conf""github.com/ethereum/go-ethereum/common"​//"github.com/FISCO-BCOS/go-sdk/client""trace/conf")​var Tracecli TraceSession​func init() {configs, err := conf.ParseConfigFile("config.toml")if err != nil {log.Fatal(err)}client, err := client.Dial((*conf2.Config)(&configs[0]))if(err != nil) {log.Fatal(err)}address := conf.ContractAddressContractAddress := common.HexToAddress(address)instance, err := NewTrace(ContractAddress,client)if err != nil{log.Fatal(err)}Tracecli = TraceSession{Contract: instance, CallOpts: *client.GetCallOpts(),TransactOpts: *client.GetTransactOpts()}}​func LoadUser(user string) {fromAddress := ""privateKey := ""switch user {case "producer":fromAddress = conf.ProducerAddressprivateKey = conf.ProducerPKcase "distributor":fromAddress = conf.DistributorAddressprivateKey = conf.DistributorPKcase "retailer":fromAddress = conf.RetailerAddressprivateKey = conf.RetailerPKdefault:return}ket,_ := crpto.HexToECDSA(privateKey)auth := bind.NewKeyedTransactor(key)auth.From = common.HexToAddress(fromAddress)files, _ := conf.ParseConfigFile("config.toml")client,_ := client.Dial((*conf2.Config)(&files[0]))instance,_ := TraceSession{Contract: instance,CallOpts: *client.GetCallOpts(), TransactOpts: *auth}}

编写foodInfoService

package service​import ("fmt""strconv")​type FoodInfoService struct{}​func (fs *FoodInfoService) Trace(traceNumber string) interface{} {trace := get_trace(traceNumber)return trace}func (fs *FoodInfoService) Get_food(traceNumber string) map[string]interface{} {response := make(map[string]interface{})name, current, quality, _, err := Tracecli.GetTraceinfoByTraceNumber(traceNumber)byTraceNumber, i, i2, _, err := Tracecli.GetTraceDetailByTraceNumber(traceNumber)if err != nil {fmt.Println(err)}response["timestamp"] = byTraceNumber[len(byTraceNumber)-1]response["produce"] = i[0]response["name"] = nameresponse["current"] = currentresponse["address"] = i2[0]response["quality"] = qualityreturn response}func (fs *FoodInfoService) GetFoodList() []int {food, _ := Tracecli.GetAllFood()intSlice := make([]int, len(food))for i, v := range food {intSlice[i] = int(v.Int64())}return intSlice}​func get_trace(traceNumber string) []interface{} {_, s, _, _, err := Tracecli.GetTraceinfoByTraceNumber(traceNumber)var responses []interface{}byTraceNumber, i, i2, i3, err := Tracecli.GetTraceDetailByTraceNumber(traceNumber)if err != nil {fmt.Println(err)}for index := 0; index < len(byTraceNumber); index++ {if index == 0 {response := make(map[string]interface{})response["traceNumber"] = traceNumberresponse["name"] = sresponse["produce_time"] = byTraceNumber[0]response["timestamp"] = byTraceNumber[index]response["from"] = i[index]response["quality"] = i3[index]response["from_address"] = i2[index]i4 := append(responses, response)responses = i4} else {response := make(map[string]interface{})response["traceNumber"] = traceNumberresponse["name"] = sresponse["produce_time"] = byTraceNumber[0]response["timestamp"] = byTraceNumber[index]response["from"] = i[index-1]response["to"] = i[index]response["quality"] = i3[index]response["from_address"] = i2[index-1]i4 := append(responses, response)responses = i4}}return responses}​func (fs *FoodInfoService) GetTrace(traceNumber string) []interface{} {_, s, _, _, err := Tracecli.GetTraceinfoByTraceNumber(traceNumber)var responses []interface{}byTraceNumber, i, i2, i3, err := Tracecli.GetTraceDetailByTraceNumber(traceNumber)if err != nil {fmt.Println(err)}for index := 0; index < len(byTraceNumber); index++ {if index == 0 {response := make(map[string]interface{})response["traceNumber"] = traceNumberresponse["name"] = sresponse["produce_time"] = byTraceNumber[0]response["timestamp"] = byTraceNumber[index]response["from"] = i[index]response["quality"] = i3[index]response["from_address"] = i2[index]i4 := append(responses, response)responses = i4} else {response := make(map[string]interface{})response["traceNumber"] = traceNumberresponse["name"] = sresponse["produce_time"] = byTraceNumber[0]response["timestamp"] = byTraceNumber[index]response["from"] = i[index-1]response["quality"] = i3[index]response["from_address"] = i2[index-1]i4 := append(responses, response)responses = i4}}return responses}func (f FoodInfoService) Producing() []interface{} {var resList []interface{}numList := f.GetFoodList()for index := 0; index < len(numList); index++ {trace := f.GetTrace(strconv.Itoa(numList[index]))if len(trace) == 1 {i := append(resList, trace[0])resList = i}}fmt.Println("Producing: ", resList)return resList}func (f FoodInfoService) Distributing() []interface{} {numList := f.GetFoodList()var resList []interface{}for index := 0; index < len(numList); index++ {trace := f.GetTrace(strconv.Itoa(numList[index]))if len(trace) == 2 {i := append(resList, trace[1])resList = i}}return resList}func (f FoodInfoService) Retailing() []interface{} {numList := f.GetFoodList()var resList []interface{}for index := 0; index < len(numList); index++ {trace := f.GetTrace(strconv.Itoa(numList[index]))if len(trace) == 3 {i := append(resList, trace[2])resList = i}}return resList}​

编写食物数据模型Food

food.go

package models​import "math/big"​// Food 表示食物的数据模型type Food struct {Namestring`json:"name"`// 食物名称TraceNumber *big.Int `json:"TraceNumber"` // 追溯编号,使用 *big.Int 类型以支持大整数TraceNamestring`json:"TraceName"`// 追溯名称Quality uint8`json:"Quality"` // 食物质量,使用 uint8 表示无符号 8 位整数}​

在utils下统一请求数据格式

response.go
package utils​import ("encoding/json""fmt")​// returnDataStruct 定义了用于返回 JSON 数据的结构体type returnDataStruct struct {Ret int `json:"ret"` // 返回状态码Msg string `json:"msg"` // 返回消息Data interface{} `json:"data"` // 返回数据,使用空接口表示可以包含任何类型的数据}​// Send_json 用于构建并返回标准的 JSON 响应func Send_json(ret int, msg string, data interface{}) []byte {// 构建 returnDataStruct 结构体实例retu, err := json.Marshal(returnDataStruct{Ret: ret,Msg: msg,Data: data,})if err != nil {fmt.Println("error:", err)}return retu}​// Send_custom_json 用于构建并返回标准的 json响应func Send_custom_json(ret int, data_key string, data interface{}) []byte {response := make(map[string]interface{})response["ret"] = retresponse[data_key] = dataretu, err := json.Marshal(response)if err != nil {fmt.Println(err)}return retu}​

编写给用户返回的数据格式

User.go
package controllers​import ("trace/conf""trace/utils"​"github.com/beego/beego/v2/server/web")​type User struct {web.Controller}​func (u User) Userinfof() {userName := u.GetString("userName")var jsons []byteswitch userName {case "produce":jsons = utils.Send_custom_json(200, "address", conf.ProducerAddress)case "distributor":jsons = utils.Send_custom_json(200, "address", conf.DistributorAddress)case "retailer":jsons = utils.Send_custom_json(200, "address", conf.RetailerAddress)default:jsons = utils.Send_custom_json(200, "error", "user not found")}u.Ctx.ResponseWriter.Write(jsons)}​
注册路由
web.Router("/userinfo", &controllers.User{}, "*:Userinfof")

编写处理与食物追溯相关请求

Trace.go

package controllers​import "github.com/beego/beego/v2/server/web"​type Trace struct {web.Controller}​​
newfood处理创建新食物的请求
func (t Trace) newFood() {t.newFood()}​
处理创建新食物的具体逻辑
func (t *Trace) newfood() {// 初始化 Food 结构体var food models.Food// 获取请求体数据data := t.Ctx.Input.RequestBody// 调用 paramjson 函数解析 JSON 数据success, parsedFood := paramjson(data, &food)if !success {// 如果解析失败,返回错误响应t.Ctx.ResponseWriter.Write(utils.Send_json(0, "传入参数不正确", parsedFood))return}// 调用 LoadUser 函数加载用户service.LoadUser("producer")// 调用 Tracecli.NewFood 函数创建新食物_, receipt, err := service.Tracecli.NewFood(parsedFood.Name, parsedFood.TraceNumber, parsedFood.TraceName, strconv.Itoa(int(parsedFood.Quality)))if err != nil {// 如果创建失败,返回错误响应t.Ctx.ResponseWriter.Write(utils.Send_json(0, "trace already exist", err))return}// 返回成功响应t.Ctx.ResponseWriter.Write(utils.Send_json(1, "ok", receipt))}
编写解析json数据具体逻辑
func paramjson(data []byte, food *models.Food) (bool, models.Food) {// 如果输入数据为 nil,则返回解析失败的结果if data == nil {return false, models.Food{}}// 尝试解析 JSON 数据并填充到 models.Food 结构体中if err := json.Unmarshal(data, &food); err != nil {// 解析失败,则返回解析失败的结果return false, models.Food{}} else {// 解析成功,则返回解析后的 models.Food 结构体return true, *food}}​
配置路由
web.Router("/produce", &controllers.Trace{}, "POST:NewFood")

Adddistribution 处理食物分销的请求

func (t Trace) Adddistribution() {t.adddistribution()}
adddistribution 处理食物分销的具体逻辑
func (t Trace) adddistribution() {var food models.Fooddata := t.Ctx.Input.RequestBodyparamjson(data, &food)b, i := paramjson(data, &food)if !b {t.Ctx.ResponseWriter.Write(utils.Send_json(0, "传入参数不正确", i))}service.LoadUser("distributor")_, receipt, err := service.Tracecli.AddTraceInfoByDistributor(food.TraceNumber, food.TraceName, new(big.Int).SetUint64(uint64(food.Quality)))if err != nil {t.Ctx.ResponseWriter.Write(utils.Send_json(0, "error", err))}t.Ctx.ResponseWriter.Write(utils.Send_json(1, "ok", receipt))}
配置路由
web.Router("/distributor", &controllers.Trace{}, "POST:Adddistribution")

Addretail 处理食物零售的请求

func (t Trace) Addretail() {t.Addretail()}
addretail 处理食物零售的具体逻辑
func (t Trace) addretail() {var food models.Fooddata := t.Ctx.Input.RequestBodyparamjson(data, &food)b, i := paramjson(data, &food)if !b {t.Ctx.ResponseWriter.Write(utils.Send_json(0, "传入参数不正确", i))}service.LoadUser("retailer")_, receipt, err := service.Tracecli.AddTraceInfoByRetailer(food.TraceNumber, food.TraceName, new(big.Int).SetUint64(uint64(food.Quality)))if err != nil {t.Ctx.ResponseWriter.Write(utils.Send_json(0, "error", err))​}t.Ctx.ResponseWriter.Write(utils.Send_json(1, "ok", receipt))}
配置路由
web.Router("/addretail", &controllers.Trace{}, "POST:Addretail")

编写GetfoodInfo 控制器

处理食品信息的请求

type GetfoodInfo struct {web.Controller}​

Trace 获取某个食品的溯源信息

func (g GetfoodInfo) Trace() {// 检索追溯号和 FoodInfoService 实例tn, fs := g.getTraceNumberAndService()​// 在 FoodInfoService 上调用 Get_food 方法,并以 JSON 格式响应结果g.Ctx.Output.JSON(fs.Get_food(tn), false, false)}

getTraceNumberAndService 是一个辅助方法,用于检索追溯号和 FoodInfoService

编写getTraceNumberAndService方法
func (g GetfoodInfo) getTraceNumberAndService() (string, *service.FoodInfoService) {// 从请求参数中检索追溯号tn := g.GetString("traceNumber")​// 将追溯号转换为整数atoi, _ := strconv.Atoi(tn)​// 检查追溯号是否小于 0 或为空;如果是,则以错误响应if atoi < 0 || tn == "" {g.Ctx.ResponseWriter.Write(utils.Send_custom_json(0, "error", "invalid parameter"))}​// 创建 FoodInfoService 的新实例fs := &service.FoodInfoService{}​// 返回追溯号和 FoodInfoService 实例return tn, fs}
配置路由
web.Router("/trace", &controllers.GetfoodInfo{}, "*:Trace")

获取某个食品当前信息

func (g GetfoodInfo) GetFood() {tn, fs := g.getTraceNumberAndService()g.Ctx.Output.JSON(fs.Get_food(tn), false, false)}​
配置路由
web.Router("/food", &controllers.GetfoodInfo{}, "*:GetFood")

获取某个生产商的食物信息

func (g GetfoodInfo) Producing() {fs := g.getFoodInfoService()g.Ctx.Output.JSON(fs.Producing(), false, false)}

getFoodInfoService 是用于获取 FoodInfoService 实例的方法

func (g GetfoodInfo) getFoodInfoService() *service.FoodInfoService {// 创建并返回 FoodInfoService 的新实例fs := &service.FoodInfoService{}return fs}

配置路由
web.Router("/producing", &controllers.GetfoodInfo{}, "GET:Producing")

获取位于中间商的食物信息

func (g GetfoodInfo) Distributing() {fs := g.getFoodInfoService()g.Ctx.Output.JSON(fs.Distributing(), false, false)}
配置路由
web.Router("/distributing", &controllers.GetfoodInfo{}, "GET:Distributing")

获取超市的食物信息

func (g GetfoodInfo) Retailing() {fs := g.getFoodInfoService()g.Ctx.Output.JSON(fs.Retailing(), false, false)}​
配置路由
web.Router("/retailing", &controllers.GetfoodInfo{}, "GET:Retailing")

INITROLE 函数用于初始化角色设置

INITROLE

// INITROLE 函数用于初始化角色设置func INITROLE() {// 从配置中获取分发者的地址和私钥fromAddress := conf.DistributorAddressprivateKey := conf.DistributorPK​// 将私钥解析为 ECDSA 密钥key, err := crypto.HexToECDSA(privateKey)if err != nil {fmt.Println("error private:", err)return}​// 创建一个新的 KeyedTransactor,将密钥和地址设置为授权信息auth := bind.NewKeyedTransactor(key)auth.From = common.HexToAddress(fromAddress)​// 从配置文件中解析 FISCO-BCOS 客户端的配置files, _ := conf.ParseConfigFile("config.toml")client, _ := client.Dial((*conf2.Config)(&files[0]))​// 部署 Service 合约并获取合约实例_, _, instance, _ := service.DeployService(client.GetTransactOpts(), client)session := service.ServiceSession{Contract: instance, CallOpts: *client.GetCallOpts(), TransactOpts: *client.GetTransactOpts()}​// 将分发者地址转换为 common.Address 类型address := common.HexToAddress(conf.DistributorAddress)​// 设置 Distributor 角色_, _, err = session.SetDRRole(address.String())if err != nil {fmt.Println(err)}​// 将生产者地址转换为 common.Address 类型address1 := common.HexToAddress(conf.ProducerAddress)​// 设置 Producer 角色_, _, err = session.ResetPRRole(address1.String())if err != nil {fmt.Println(err)}​// 将零售商地址转换为 common.Address 类型address2 := common.HexToAddress(conf.RetailerAddress)​// 设置 Retailer 角色_, _, err = session.ResetRRRole(address2.String())if err != nil {fmt.Println(err)}​// 部署 Role 合约并获取合约实例_, _, instance1, err := Role.DeployService(client.GetTransactOpts(), client)if err != nil {fmt.Println(err)}​session1 := Role.ServiceSession{Contract: instance1, CallOpts: *client.GetCallOpts(), TransactOpts: *client.GetTransactOpts()}​// 判断所有角色是否设置成功err1 := session1.OnlyDRRole(address)err2 := session1.OnlyPRRole(address1)err3 := session1.OnlyPRRole(address2)​if err1 != nil || err2 != nil || err3 != nil {fmt.Println(err1)fmt.Println(err2)fmt.Println(err3)}}​

main函数初始化

func main() {// 初始化角色INITROLE()​// 设置 Beego 路由不区分大小写beego.BConfig.RouterCaseSensitive = false​// 运行 Beego 应用beego.Run()}