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