GoFrame 是一款模块化、高性能的 Go 语言开发框架,具有工程完备、开箱即用、高扩展性等特点,包含了常用的基础组件和开发工具,既可以作为完整的业务项目框架使用也可以作为独立的组件库使用。
快速开始
接下来我们将使用 GoFrame 来开发一个简单的 Web 服务,从而快速上手 GoFrame。如下代码使用 GoFrame 快速启动一个 Web Server
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" ) func main () { s := g.Server() s.BindHandler("/" , func (r *ghttp.Request) { r.Response.Write("Hello World\n" ) }) s.SetPort(8000 ) s.Run() }
接下来配置 go mod
并安装依赖:
1 2 # go mod init main # go mod tidy
运行该程序:
1 2 3 4 5 6 7 8 2025-06-28T15:07:00.962+08:00 [INFO] pid[2719616]: http server started listening on [:8000] 2025-06-28T15:07:00.962+08:00 [INFO] openapi specification is disabled ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE ----------|--------|-------|-----------------|------------- :8000 | ALL | / | main.main.func1 | ----------|--------|-------|-----------------|-------------
接下来使用 curl 访问该服务:
快速解释一下上面代码:
g 组件是框架提供的一个耦合组件,封装和初始化一些常用的组件对象,例如 g.Server()
就可以获取一个默认的 Server 对象
通过 Server 的 BindHandler
方法绑定路由及其处理函数,输出参数为当前请求对象 r *ghttp.Request
,包含了当前请求的上下文信息
通过调用 r.Response
对象的 Write
方法输出响应内容
调用 Server 的 Run()
方法启动服务
获取请求参数
可以对上面的 Web Server
进行扩展,获取客户端提交的请求参数:
1 2 3 4 5 6 7 ...... s.BindHandler("/" , func (r *ghttp.Request) { r.Response.Writef("Hello, %s, you are %d years old\n" , r.Get("name" , "unknown" ).String(), r.Get("age" , 0 ).Int()) }) ......
可以通过 r.Get()
方法来获取请求参数,它的第一个参数为参数名称,第二个参数为默认值,返回值是一个 gvar.Var
对象,它是一个运行时泛型对象,需要根据具体场景转换为特定类型值。
1 func (r *Request) Get(key string , def ...interface {}) *gvar.Var
r.Get()
可以获取所有 HTTP 请求方法提交的参数,例如它支持从 URL 查询参数、HTTP body 等中获取参数,而且可以自动识别不同的 Content-Type
,例如 JSON
、x-www-form-urlencoded
等。
1 2 3 4 5 6 7 8 9 10 11 Hello, unknown, you are 0 years old Hello, jack, you are 20 years old Hello, jane, you are 25 years old Hello, mark, you are 22 years old
这种获取参数的方式有一些缺点,例如参数名称硬编码到代码中,而且与业务数据结构无法关联起来,可以通过结构化的参数对象来解决这个问题。
请求数据结构
通过 结构化数据
来提取请求参数,可以很好地维护参数的名称和类型。通过 r.Parse()
方法将请求参数映射到请求对象上,同样 r.Parse()
支持解析不同方法提交的请求参数:
1 2 3 4 5 6 7 8 9 10 11 12 ...... s := g.Server() s.BindHandler("/" , func (r *ghttp.Request) { var req HelloReq if err := r.Parse(&req); err != nil { r.Response.Write(err.Error()) return } r.Response.Writef("Hello, %s, you are %d years old\n" , req.Name, req.Age) }) .....
1 2 3 4 5 6 7 8 Hello, jack, you are 20 years old Hello, mark, you are 22 years old Hello, jane, you are 25 years old
使用规范路由
为了简化路由的注册方式,而且避免在每个路由处理函数中进行繁琐的参数解析,GoFrame 提供了 规范化的路由注册方式
。为了使用 规范路由
,首先需要定义请求数据结构和响应数据结构:
1 2 3 4 5 6 7 type HelloReq struct { g.Meta `path:"/" method:"get"` Name string `v:"required" dc:"name"` Age int `v:"required" dc:"age"` } type HelloRes struct {}
当接口比较多时,手动配置路由与路由函数的关系有些繁琐,可以通过对象化的形式来封装路由函数。如下定义一个 路由对象
:
1 2 3 4 5 6 7 type Hello {}func (h Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error ) { r := g.RequestFromCtx(ctx) r.Response.Writef("Hello, %s, you are %d years old\n" , req.Name, req.Age) return }
路由对象用于封装路由函数,其所有定义的公开方法都将作为路由函数进行注册
这里路由对象的 Say
方法其实就是一个路由函数,但是的它的定义更加符合业务逻辑函数的定义风格
通过 g.RequestFromCtx
从 ctx 中获取原始的 ghttp.Request
对象,之后就可以返回自定义内容
最后通过如下方式完成路由的注册:
1 2 3 4 5 6 7 func main () { ...... s.Group("/" , func (group *ghttp.RouterGroup) { group.Bind(new (Hello)) }) ...... }
这里使用了 s.Group
来进行路由分组,在其回调方法中注册的所有路由,都会带有其定义的 分组路由前缀
,这里即 /
通过 group.Bind
方法注册路由对象,该方法将会遍历路由对象的所有公开方法,读取方法的输入输出结构体定义,并对其执行路由注册 。
代码运行如下:
1 2 3 4 5 Hello, jack, you are 20 years old Not Found
可以看到,此时已经不支持通过 POST
方法提交的请求了,因为我们的请求对象的 g.Meta
对象中指定了请求方法为 GET
。
中间件
中间件是一种拦截器设计,在 Web Server 中可以拦截请求/响应,并添加自定义处理逻辑。中间件的定义和普通的路由函数一样,但可以在 Request 参数中使用 Middleware 属性对象来控制请求流程。
中间件的类型分为两种:
前置中间件:在路由服务函数调用之前
后置中间件:在路由服务函数调用之后
1 2 3 4 5 func Middleware (r *ghttp.Request) { r.Middleware.Next() }
在中间件中执行完成处理逻辑后,使用 r.Middleware.Next()
方法进一步执行下一个流程。如果这个时候直接退出不调用 r.Middleware.Next()
方法的话,将会退出后续的执行流程(例如请求鉴权失败)。
如下使用中间件来实现错误处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func ErrorHandler (r *ghttp.Request) { r.Middleware.Next() if err := r.GetError(); err != nil { r.Response.Write("error occurs: " , err.Error()) return } } func main () { ...... s.Group("/" , func (group *ghttp.RouterGroup) { group.Middleware(ErrorHandler) group.Bind(new (Hello)) }) ...... }
这里定义了 ErrorHandler
这个错误处理中间件,它首先调用 r.Middleware.Next()
执行路由函数流程,然后通过 r.GetError()
获取路由函数执行过程中产生的错误,如果有则进行处理。因此它是一个后置中间件
在路由注册中,通过 group.Middleware(ErrorHandler)
给该分组下的所有路由,都绑定这个错误处理的中间件
再次运行,查看错误处理中间件的效果:
1 2 error occurs: The Name field is required
统一返回结构
如果希望执行成功或者失败,都是以 json 格式返回应答,可以使用如下方法实现:
1 2 3 4 5 6 7 8 type HelloRes struct { Content string `json:"content" dc:"result"` } type Response struct { Message string `json:"message"` Data interface {} `json:"data"` }
修改路由处理函数的实现,让它返回 HelloRes
数据结构,而不是直接写入响应内容
1 2 3 4 5 6 func (h Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error ) { res = &HelloRes{ Content: fmt.Sprintf("Hello, %s, you are %d years old" , req.Name, req.Age), } return }
修改中间件的实现,统一返回 json 数据。这里的关键是通过 r.GetHandlerResponse()
获取路由函数返回的执行结果,并通过 r.Response.WriteJson
方法写入真正的响应内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func Middleware (r *ghttp.Request) { r.Middleware.Next() var ( msg string res = r.GetHandlerResponse() err = r.GetError() ) if err != nil { msg = err.Error() } else { msg = "OK" } r.Response.WriteJson(Response{ Message: msg, Data: res, }) }
1 2 3 4 5 6 ...... s.Group("/" , func (group *ghttp.RouterGroup) { group.Middleware(Middleware) group.Bind(new (Hello)) }) ......
实际测试结果如下:
1 2 3 4 5 # curl '127.0.0.1:8000?name=jack&age=20' {"message" :"OK" ,"data" :{"content" :"Hello, jack, you are 20 years old" }} # curl 127.0 .0 .1 :8000 {"message" :"The Name field is required" ,"data" :null}
通过中间件对返回的数据统一进行封装,这对有着大量 API 接口的业务来说是很有必要的 。
自动化生成接口文档
使用 GoFrame 框架自动化生成接口文档非常简单:
1 2 3 4 5 6 7 func main () { ...... s.SetOpenApiPath("/api.json" ) s.SetSwaggerPath("/swagger" ) s.SetPort(8000 ) s.Run() }
s.SetOpenApiPath("/api.json")
启用 OpenAPIv3
接口文档生成,访问路径设置为 /api.json
。OpenAPIv3
是目前业界接口文档的标准协议,通常使用 json 格式生成,这个 json 文件可以使用很多接口管理攻击打开,例如 Swagger UI
、Postman
等
s.SetSwaggerPath("/swagger")
启用内置的接口文档 UI 工具,访问路径设置为 /swagger
。swagger
是常用的接口文档 UI 工具,支持多种接口文档格式,例如 OpenAPIv3
。其实准确地说,GoFrame 框架内置的 UI 工具是 redoc
,而不是 swagger ui
另外,我们可以按照 OpenAPIv3
接口协议规范,在 g.Meta
中继续完善接口定义,例如添加如下标签:
tags:接口分类/模块
summary:接口描述
现在我们访问对应的 URI 地址,就能看到接口文档了,也能使用 Swagger UI
查看接口:
项目脚手架
GoFrame 框架同时还提供了 项目脚手架
工具,以快速生成标准化、工程化的项目框架代码。
安装框架工具
如下在 Linux 系统上下载并安装预编译的框架工具:
1 wget -O gf "https://github.com/gogf/gf/releases/latest/download/gf_$(go env GOOS) _$(go env GOARCH) " && chmod +x gf && ./gf install -y && rm ./gf
确认安装成功:
1 2 3 4 5 6 7 v2.9.0 Welcome to GoFrame! Env Detail: Go Version: go1.23.1 linux/amd64 GF Version(go.mod): cannot find go.mod ......
创建项目模版
使用如下命令快速创建一个工程项目,项目名称为 demo,-u
参数用于指定是否更新项目中使用的 GoFrame 框架:
生成的项目脚手架是按照通用性设计的,可以满足 Web、微服务等开发场景:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 . ├── api ├── hack ├── internal ├── manifest ├── resource ├── utility ├── go.mod ├── go.sum ├── main.go ├── Makefile └── README.MD
默认会生成一个 HTTP Web Server
的模版项目,因此直接可以直接运行该项目:
1 2 3 4 5 2025-06-30T13:21:36.393+08:00 [INFO] pid[3830415]: http server started listening on [:8000] 2025-06-30T13:21:36.393+08:00 [INFO] swagger ui is serving at address: http://127.0.0.1:8000/swagger/ 2025-06-30T13:21:36.393+08:00 [INFO] openapi specification is serving at address: http://127.0.0.1:8000/api.json .....
如果想要更新项目使用的 GoFrame 框架版本,可以在项目根目录下(目录下有 go.mod 文件)使用如下命令:
项目启动分析
接下来将对该工具生成的 脚手架
项目的启动流程进行分析,以熟悉工具生成的 脚手架
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( _ "demo/internal/packed" "github.com/gogf/gf/v2/os/gctx" "demo/internal/cmd" ) func main () { cmd.Main.Run(gctx.GetInitCtx()) }
程序入口由 main.go 进入,该文件调用 internal/cmd
包的 Main.Run
启动程序
项目的所有核心业务逻辑都放到了 internal
目录下,以封装内部实现
框架的核心组件均需要传递 context 上下文参数,这里使用 gctx.GetInitCtx()
获取一个 Context
Main.Run
函数主要完成启动逻辑,默认它会启动一个 HTTP Server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var ( Main = gcmd.Command{ Name: "main" , Usage: "main" , Brief: "start http server" , Func: func (ctx context.Context, parser *gcmd.Parser) (err error ) { s := g.Server() s.Group("/" , func (group *ghttp.RouterGroup) { group.Middleware(ghttp.MiddlewareHandlerResponse) group.Bind( hello.NewV1(), ) }) s.Run() return nil }, } )
这里仍然是通过 s.Group()
的方式创建分组路由,在路由处理函数内,通过 Middleware
方法添加中间件 ghttp.MiddlewareHandlerResponse
,用于规范 HTTP 响应
通过 Bind
方法绑定 hello.NewV1()
所返回的路由对象,这个路由对象下的所有公开方法均会自动注册到路由中。项目脚手架支持接口的版本管理,默认返回的路由对象都是 v1 版本
NewV1()
的返回值其实是一个接口,而不是具体的对象。这样实现的理由是:当我们定义了很多 API 接口,但是具体实现的 controller 对象可能只实现了其中的一部分,为了尽早发现这个问题,我们就可以使用 Go 的接口特性了。假设具体实现(例如这里的 ControllerV1)只实现了接口的部分方法,那么在编译时就会报错,就不用等到运行时才能发现这个问题了 。
1 2 3 4 5 6 7 8 9 10 11 12 type IHelloV1 interface { Hello(ctx context.Context, req *v1.HelloReq) (res *v1.HelloRes, err error ) } func NewV1 () hello.IHelloV1 { return &ControllerV1{} } func (c *ControllerV1) Hello(ctx context.Context, req *v1.HelloReq) (res *v1.HelloRes, err error ) { g.RequestFromCtx(ctx).Response.Writeln("Hello World!" ) return }
从具体的 路由函数实现
中可以看到,请求参数的定义是 v1.HelloReq
,响应参数的定义是 v1.HelloRes
,从这两个参数的定义中可以看到路由信息:
1 2 3 4 5 6 7 type HelloReq struct { g.Meta `path:"/hello" tags:"Hello" method:"get" summary:"You first hello api"` } type HelloRes struct { g.Meta `mime:"text/html" example:"string"` }
最后通过调用 ghttp.Server.Run()
方法启动 HTTP Server,开始接收 HTTP 请求。
接口开发
接下来继续在脚手架框架代码中编写简单的 CRUD 接口,实现对数据库表的增删改查操作。
设计数据表
在接口开发之前先设计数据库表是比较好的开发习惯,如下流程启动 mysql 数据库并创建数据库表:
1 2 3 4 5 docker run -d --name mysql \ -p 3306:3306 \ -e MYSQL_DATABASE=test \ -e MYSQL_ROOT_PASSWORD=12345678 \ mysql:8.0
1 docker exec -it mysql mysql -h 127.0.0.1 -P 3306 -u root -p12345678 test
1 2 3 4 5 6 7 CREATE TABLE `user` ( `id ` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'user id' , `name` varchar(45) DEFAULT NULL COMMENT 'user name' , `status` tinyint DEFAULT NULL COMMENT 'user status' , `age` tinyint unsigned DEFAULT NULL COMMENT 'user age' , PRIMARY KEY (`id `) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
1 2 3 4 5 6 mysql> show tables; +----------------+ | Tables_in_test | +----------------+ | user | +----------------+
生成 dao/do/entity
开发工具的配置在 hack/config.yaml
文件中维护,我们首先需要检查生成的默认配置是否符合预期:
1 2 3 4 5 6 7 8 9 10 gfcli: gen: dao: - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" descriptionTag: true docker: build: "-a amd64 -s linux -p temp -ew" tagPrefixes: - my.image.pub/my-app
当我们执行 make dao
命令时,就会用到该配置文件中的 dao
部分配置:
1 2 3 4 5 6 generated: /root/code/private/go/go_open/gofg_learn/gf_tool/demo/internal/dao/user.go generated: /root/code/private/go/go_open/gofg_learn/gf_tool/demo/internal/dao/internal/user.go generated: /root/code/private/go/go_open/gofg_learn/gf_tool/demo/internal/model/do/user.go generated: /root/code/private/go/go_open/gofg_learn/gf_tool/demo/internal/model/entity/user.go done !
执行 make dao
命令后,会自动生成 dao/do/entity
文件。每张表将会生成 3 类 Go 文件:
dao:通过对象方式访问底层数据源,底层基于 ORM 组件实现
do:数据转换模型,用于业务模型到数据模型的转换,由工具维护,用户不能修改
entity:数据模型,由工具维护,用户不能修改
对于工具生成的代码,如果有 Code generated and maintained by GoFrame CLI tool. DO NOT EDIT
的注释,提示我们不要手动修改这些文件。
生成的 dao 文件有 2 个:
internal/dao/internal/user.go
用于封装对数据表 user 的访问,提供一些数据结构和方法以简化对数据表的 CRUD 操作
internal/dao/user.go
对 internal/dao/internal/user.go
的进一步封装,用于供其他模块直接调用访问。开发者可以按需修改该文件
internal/dao/user.go
提供的方法与对象才是可供其他模块调用的接口,它的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package daoimport ( "demo/internal/dao/internal" ) type userDao struct { *internal.UserDao } var ( User = userDao{internal.NewUserDao()} )
生成的 do
代码文件 internal/model/do/user.go
内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package doimport ( "github.com/gogf/gf/v2/frame/g" ) type User struct { g.Meta `orm:"table:user, do:true"` Id interface {} Name interface {} Status interface {} Age interface {} }
生成的 entity
代码文件 internal/model/entity/user.go
内容如下:
1 2 3 4 5 6 7 8 9 package entitytype User struct { Id uint `json:"id" orm:"id" description:"user id"` Name string `json:"name" orm:"name" description:"user name"` Status int `json:"status" orm:"status" description:"user status"` Age uint `json:"age" orm:"age" description:"user age"` }
可以看到,entity 数据结构定义与数据表字段一一对应。
编写 API 接口定义
在 api
子目录下,定义 CRUD 接口,接口采用 RESTful 风格,充分使用 GET/POST/PUT/DELETE
的 HTTP Method,同时使用 v1 作为版本号。API 接口代码文件 api/user/v1/user.go
内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package v1import ( "demo/internal/model/entity" "github.com/gogf/gf/v2/frame/g" ) type Status int const ( StatusOK Status = 0 StatusDisabled Status = 1 ) type CreateReq struct { g.Meta `path:"/user" method:"post" tags:"User" summary:"Create user"` Name string `v:"required|length:3,10" dc:"user name"` Age uint `v:"required|between:18,200" dc:"user age"` } type CreateRes struct { Id int64 `json:"id" dc:"user id"` } type UpdateReq struct { g.Meta `path:"/user/{id}" method:"put" tags:"User" summary:"Update user"` Id int64 `v:"required" dc:"user id"` Name *string `v:"length:3,10" dc:"user name"` Age *uint `v:"between:18,200" dc:"user age"` Status *Status `v:"in:0,1" dc:"user status"` } type UpdateRes struct {}type DeleteReq struct { g.Meta `path:"/user/{id}" method:"delete" tags:"User" summary:"Delete user"` Id int64 `v:"required" dc:"user id"` } type DeleteRes struct {}type GetOneReq struct { g.Meta `path:"/user/{id}" method:"get" tags:"User" summary:"Get one user"` Id int64 `v:"required" dc:"user id"` } type GetOneRes struct { *entity.User `dc:"user"` } type GetListReq struct { g.Meta `path:"/user" method:"get" tags:"User" summary:"Get users"` Age *uint `v:"between:18,200" dc:"user age"` Status *Status `v:"in:0,1" dc:"user age"` } type GetListRes struct { List []*entity.User `json:"list" dc:"user list"` }
这里实现了 User 的创建、更新、删除和查询接口(单个查询和列表查询)
在请求对象的定义中,同样使用了 g.Meta
来管理接口的元数据信息
在请求对象的其他属性中,使用了 v
标签来进行参数校验,required
、length
、between
等都是内置的校验规则
在响应对象中,通过 json 标签来定义 json 序列化时的字段名
对于 path 标签 /user/{id}
,其中 {id}
表示一个路由参数。该参数通过 URL Path
的方式传递,参数名称为 id。从路由中匹配到的 id 参数会自动赋值给请求对象的同名属性(不区分大小写)
在 UpdateReq
请求对象中,我们看到一些参数使用了指针类型。这是避免类型默认值对接口的影响 ,例如如果 Status
字段不使用指针,则它的默认值就是 0,那么就无法区分调用端到底有没有传递该参数。而使用指针(其默认值是 nil)则可以很好地进行区分
在查询接口中,返回的数据直接使用了 *entity.User
结构体
这种接口定义方式可以自动化地生成接口文档,保证文档与代码的一致性(代码即文档)。
根据 api 生成代码
当 api 定义完成后,直接通过 make ctrl
命令(或者 gf gen ctrl)生成 controller 代码:
1 2 3 4 5 6 7 8 9 10 generated: /root/code/private/go/go_open/gofg_learn/gf_tool/demo/api/hello/hello.go generated: /root/code/private/go/go_open/gofg_learn/gf_tool/demo/api/user/user.go generated: /root/code/private/go/go_open/gofg_learn/gf_tool/demo/internal/controller/user/user.go generated: /root/code/private/go/go_open/gofg_learn/gf_tool/demo/internal/controller/user/user_new.go generated: /root/code/private/go/go_open/gofg_learn/gf_tool/demo/internal/controller/user/user_v1_create.go generated: /root/code/private/go/go_open/gofg_learn/gf_tool/demo/internal/controller/user/user_v1_update.go generated: /root/code/private/go/go_open/gofg_learn/gf_tool/demo/internal/controller/user/user_v1_delete.go generated: /root/code/private/go/go_open/gofg_learn/gf_tool/demo/internal/controller/user/user_v1_get_one.go generated: /root/code/private/go/go_open/gofg_learn/gf_tool/demo/internal/controller/user/user_v1_get_list.go
生成的代码主要包含 3 类文件:
api 接口抽象文件:例如 api/user/user.go
文件就是对 API 接口的 interface 定义,这样能在编译期就发现某些接口未实现的问题
1 2 3 4 5 6 7 type IUserV1 interface { Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error ) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error ) Delete(ctx context.Context, req *v1.DeleteReq) (res *v1.DeleteRes, err error ) GetOne(ctx context.Context, req *v1.GetOneReq) (res *v1.GetOneRes, err error ) GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error ) }
controller 路由对象管理:用于管理 controller 的初始化
internal/controller/user/user.go
:其实是一个空文件,可用于定义一些 controller 内部使用的数据结构、常量等
internal/controller/user/user_new.go
:包含路由对象创建函数
1 2 3 4 5 6 7 8 9 10 11 package userimport ( "demo/api/user" ) type ControllerV1 struct {}func NewV1 () user.IUserV1 { return &ControllerV1{} }
controller 路由实现代码:用于实现 API 接口,默认会按照一个 api 接口一个源文件的形式生成代码(也可以控制聚合到一个源码文件中)
internal/controller/user/user_v1_create.go
internal/controller/user/user_v1_delete.go
internal/controller/user/user_v1_get_list.go
internal/controller/user/user_v1_get_one.go
internal/controller/user/user_v1_update.go
默认生成的代码内容如下所示,需要我们自己完善对应的业务逻辑:
1 2 3 func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error ) { return nil , gerror.NewCode(gcode.CodeNotImplemented) }
完成接口逻辑实现
通过脚手架工具,很多与业务逻辑无关的代码都已经生成好了,我们只需要在对应的代码文件中实现业务逻辑即可。使用 GoFrame 框架数据库 ORM 组件,可以非常方便、高效完成接口开发工作。
例如修改 internal/controller/user/user_v1_create.go
文件,实现用户创建逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error ) { id, err := dao.User.Ctx(ctx).Data(do.User{ Name: req.Name, Status: v1.StatusOK, Age: req.Age, }).InsertAndGetId() if err != nil { return nil , err } res = &v1.CreateRes{ Id: id, } return }
通过 dao.User
通过 dao 组件操作 user 表
每个 dao 操作都需要传递 ctx 参数,通过 Ctx(ctx) 方法创建一个 gdb.Model
对象,该对象是框架的模型对象,用于操作特定的数据表
通过 Data 方法传递需要写入数据表的数据,使用 do 转换模型对象
输入我们的数据,由 do 模型在底层自动转换为对应的数据表字段类型
最后通过 InsertAndGetId
方法执行插入操作,并返回新创建数据的 ID
另外参数的校验逻辑不需要在 Controller 中编写,因为我们在定义请求对象时已经通过 v
标签定义好了,框架会自动进行校验,如果校验失败,不会执行对应的路由函数。因此只需要在路由函数中实现业务逻辑即可。
如下分别实现剩余几个接口:
1 2 3 4 func (c *ControllerV1) Delete(ctx context.Context, req *v1.DeleteReq) (res *v1.DeleteRes, err error ) { _, err = dao.User.Ctx(ctx).WherePri(req.Id).Delete() return }
1 2 3 4 5 6 7 8 func (c *ControllerV1) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error ) { _, err = dao.User.Ctx(ctx).Data(do.User{ Name: req.Name, Status: req.Status, Age: req.Age, }).WherePri(req.Id).Update() return }
1 2 3 4 5 6 7 8 func (c *ControllerV1) GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error ) { res = &v1.GetListRes{} err = dao.User.Ctx(ctx).Where(do.User{ Age: req.Age, Status: req.Status, }).Scan(&res.List) return }
1 2 3 4 5 func (c *ControllerV1) GetOne(ctx context.Context, req *v1.GetOneReq) (res *v1.GetOneRes, err error ) { res = &v1.GetOneRes{} err = dao.User.Ctx(ctx).WherePri(req.Id).Scan(&res.User) return }
配置与路由
GoFrame 的数据库组件使用了接口化设计,接口与实现是分离的,以提供更好的抽象型和扩展性。由于当前我们使用 mysql 数据库,因此需要引入 mysql 驱动,在 `main.go 中新增如下代码即可:
1 _ "github.com/gogf/gf/contrib/drivers/mysql/v2"
之后需要完成项目配置,脚手架生成的文件中主要有两个配置文件:
hack/config.yaml
:用于配置脚手架工具本身,主要在项目开发过程中使用
manifest/config/config.yaml
:业务项目的配置文件,由开发者自行维护。默认生成的配置内容如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # https: server: address: ":8000" openapiPath: "/api.json" swaggerPath: "/swagger" # https: logger: level : "all" stdout: true # https: database: default : link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
最后,我们还需要为新添加的接口注册路由,修改 internal/cmd/cmd.go
文件,在 group.Bind
方法中,通过 user.NewV1()
添加我们的路由对象即可。
运行项目
1 2 3 4 5 # go run main.go 2025 -07 -02 T14:07 :54.560 +08 :00 [INFO] pid[802037 ]: http server started listening on [:8000 ]2025 -07 -02 T14:07 :54.560 +08 :00 [INFO] swagger ui is serving at address: http:2025 -07 -02 T14:07 :54.560 +08 :00 [INFO] openapi specification is serving at address: http:......
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # curl -X POST 'http://127.0.0.1:8000/user' -d '{"name":"john","age":20}' {"code" :0 ,"message" :"OK" ,"data" :{"id" :1 }} # curl 'http://127.0.0.1:8000/user' {"code" :0 ,"message" :"OK" ,"data" :{"list" :[{"id" :1 ,"name" :"john" ,"status" :0 ,"age" :20 }]}} # curl 'http://127.0.0.1:8000/user/1' {"code" :0 ,"message" :"OK" ,"data" :{"id" :1 ,"name" :"john" ,"status" :0 ,"age" :20 }} # curl -X PUT 'http://127.0.0.1:8000/user/1' -d '{"name":"john","age":30}' {"code" :0 ,"message" :"OK" ,"data" :null} # curl -X DELETE 'http://127.0.0.1:8000/user/1' {"code" :0 ,"message" :"OK" ,"data" :null} # curl 'http://127.0.0.1:8000/user' {"code" :0 ,"message" :"OK" ,"data" :{"list" :null}}
小结
可以看到,使用 GoFrame 脚手架工具来进行 CRUD 接口的开发,主要的几件事情是:
数据库表涉及
api 接口定义
接口的业务逻辑实现
简单的配置、路由注册
总结
这篇文章学习了 GoFrame 框架的基本使用方法,可以看到,使用 GoFrame 框架来开发项目的确比较高效,而且 GoFrame 框架的文档非常丰富,解释也很详尽,的确是一个非常值得学习的开源项目。
Reference