Post on: 2025-9-14Last edited: 2025-9-14Words 10068Read Time 26 min

type
status
date
slug
summary
tags
category
icon
password
Go后端项目地址:

准备工作

后端我们使用 Golang 进行开发,使用 Gin 框架支持 Api 开发,使用 Geth 进行链上交互,使用 Gorm 作为ORM框架,MySQL 作为数据库。
GolangMySQL请自行准备,在这里就不介绍了。
首先,初始化我们的后端项目文件夹。

数据库设计

我们只设计核心逻辑相关的表,因此只有两张表,分别是 projectparticipant 表。

依赖引入

引入 Golang 后端核心依赖。
后续开发过程中,可能还会引入一些依赖。如果你看到代码中依赖报红,显示没引入,那么 go mod tidy 或者rm -f go.sum && go mod tidy可以解决 90% 以上的问题,会自动检测代码中的依赖,没找到的会去拉取。

合约交互

这里算是本项目的核心内容。我们将封装一些调用 FairTicket 合约的方法,更方便我们后续使用。

联通测试

首先,我们测试一下是否准备完成。运行下面的代码,看看是否能连接到我们的 anvil 。
如果有依赖报错,则执行 go mod tidy 更新一下依赖。
接下来,请保证 anvil 正在后台运行。然后运行
看到输出成功,说明我们的 Golang 程序已经成功连接上了我们的本地 anvil 链。
进一步地,我们还可以拿一个地址,获取它的账户余额。
更多的 Golang 与链上交互相关的例子,请参考Go Ethereum Book

合约编译

现在,回到我们的 FairTicket 合约项目,编译我们的合约。
编译的结果会保存在 out 文件夹中,里面包含了一些 .json 文件。

生成交互所需Go文件

现在,复制 out 文件夹到我们的 Golang 项目下。
新建一个 abi.sh 文件,输入以下内容。作用是根据编译后的信息,利用 abigen 工具,为Solidity智能合约生成 Go 语言绑定,让 Go 程序能够更方便地与智能合约进行交互。
运行 bash abi.sh ,可以看到项目中生成了 bindings 文件夹,里面存放各个 .go 文件。

合约交互

创建一个 contract 文件夹,存放合约调用相关代码。
这里就封装了我们所有所需的合约调用相关函数了。
除了主动地发起合约调用调用之外,还提供了初始化监听事件通道的方法。

数据库交互

连接数据库

当然,首先我们要创建数据库的连接对象。
创建一个 db 文件夹和db.go 文件

创建Model

在项目根目录创建cmd 文件夹,用于存放执行命令行的一些 main 函数。
在这里,我们使用 gormgen 来根据数据库生成我们的 model 结构体。在 cmd 文件夹下创建 gen.go 文件
然后执行如下命令,运行这个脚本。
运行成功后,你会发现多了一个 model 文件夹,如下所示。

核心功能代码

都是简单的增删改查。这里就不按照常规后端项目结构中的什么分层架构(Controller,Service,Repository),这样分开去写了,也不弄什么接口了。我们将Service层和Repository层进行合并,操作数据库的代码也写在Service层中。一切为了简洁好懂。
事不宜迟,我们现在来编写代码吧!我将在代码中详细解释每个方法的用处。

project.svc.go

participant.svc.go

event.svc.go

这段代码主要是用来监听事件。这是因为当发起交易和交易被确认完成,是不定时的异步操作,这取决于实际上的网络拥堵情况等。监听到对应的事件,相当于是通知我们有一个对应的交易已经上链完成,我们进行对应的后续操作。
比如在合约中创建项目成功后,会发出一个 ProjectCreated(id,fingerprint) 事件,说明已经上链完成,并且带上一些信息。又比如在合约的 Lottery 函数完成后,会发出一个MagicNumberPublished(id, magicNumber) 事件,告知我们某个项目产生了一个随机数,我们就可以进行后续的计算中奖者环节了。

merkle.go

MerkleTree的相关介绍可以移步 Merkle proofs explained
merkle.go 文件提供一个BuildMerkleTree函数,根据输入的叶子节点,计算Merkle treeroot 以及各个叶子节点的 merkle proof。在本项目中,MerkleTree 由构建中奖者的名单进行构建,同时为每个中奖者提供 Merkle proof ,只要中奖者持有这个MerkleProof 即可验证是否中奖。
提问:为什么不选择直接在合约中保存每个项目的中奖者地址名单?这样对一下地址不就知道了吗?
回答:链上每个写操作都是要消耗gas 的,如果保存每个项目的中奖者地址,会增加无谓的消耗。并且区块链网络的打包取决于区块链网络的拥堵情况,当网络拥堵的时候,消耗也会增加。而使用Merkle Tree,在链上就只需要存储对应项目的Merkle Root ,将中奖者的名单和Merkle Proof在链下存储。虽然对比参与人数来说,中奖者可能远远小于我们在合约中存的参与者数量,但是该省省该花花。
当然在实现的时候你也可以选择直接存中奖者信息,这样更清晰,容易验证。

核心功能测试

测试代码编写

上诉代码编写完之后,我们来验证一下逻辑流程是否走通。
我们测试的功能和流程如下:
  1. 创建项目
  1. 开始项目
  1. 参与者参与项目。
  1. 结束项目
  1. 开启抽票
  1. 中奖验证
先创建一个 test 文件夹,用于存放测试用例。
在这里我们就不弄单元测试了,直接上集成测试,测试整个流程。如果你感兴趣的话,可以尝试验证每个成功和错误的测试用例。相信你跟着代码和注释就会明明白白的了。
下面的测试用例主要是使用 AI 生成的,微调了一点内容,你会很明显感觉到 AI 味很浓。
如果你重新部署了合约,要进行测试,记得检查更新一下 contract/contract.go 文件里面定义的合约地址。也记得清理一下数据库的旧数据,以免读到旧的数据。
运行。执行耗时大概是11+s,因为我们代码里一共sleep 了 11s。实际上也不用sleep这么久,因为我们代码执行很快,本地链将交易上链的速度也很快,你完全可以大大缩短每个 sleep 耗时来加快测试速度。

测试数据验证

我们查看一下数据库中以及合约中的数据是否符合期望。
  1. 检查数据库,查看projectparticipant 表的内容是否和我们预期的一致。
  1. 查看合约中相关信息。
    1. 查看 project 信息。
      1. 查看 participant 信息

      Api代码编写

      核心功能测试完之后,我们可以编写API相关的代码,将功能暴露出去,可以通过http请求进行调用。
      在Api代码编写时,作为一个文字教程并不需要额外的封装和各种生产中推荐的设置,并且将能放在一起的都放在同个文件,否则会容易混淆和丢失重点,分散注意。我们力求简洁,只关注核心的,只封装高频使用的功能,并且会忽略一些安全检查,虽然不会很优美和安全,但是够简洁直观。
      api相关文件树

      api/request.go

      定义项目中需要的 api 参数。json tag 表示该字段绑定 json 的字段名称,bindinggo validator 的绑定标记,required 表示必传项。

      api/response.go

      在这个文件里面,我们首先定义了Api 的统一返回结构体,以及分别封装了成功和失败的两个返回函数。这可以使我们的 api 代码更加简洁优雅。

      api/project.go

      api/participant.go

      项目启动

      在项目启动前,我们还需要准备几个事情
      1. 设置 api 路由。
      1. 设置中间件,支持跨域。
      1. 启动合约事件监听。
      涉及到的文件树如下

      listener/event.go

      启动几个监听合约事件的 go routine

      api/cors.middleware.go

      设置跨域中间件,以便后续我们前端可以调用。(因为前端是用3000端口,后端用8080端口,会出现跨域问题)

      router/router.go

      main.go

      启动!

      启动就非常简单了。
      但是前提保证你的 anvil 在运行,并且合约已经部署到链上。如果之前关闭过 anvil并且没有保存链上状态,那么请重新运行 anvil 并且部署合约。部署合约后,请保证合约地址与代码中设置的合约地址一致。
      启动!

      Loading...
      从0到1实现一个Dapp全栈项目FairTicket✨(2)Solidity智能合约开发

      从0到1实现一个Dapp全栈项目FairTicket✨(2)Solidity智能合约开发


      从0到1实现一个Dapp全栈项目FairTicket✨(4)Next前端开发

      从0到1实现一个Dapp全栈项目FairTicket✨(4)Next前端开发