该功能特性从v2.5版本开始提供。该命令目前仅支持HTTP接口开发,GRPC部分请参考gen pb命令。未来会考虑HTTPGRPC统一使用该命令生成控制器及SDK源代码。

基本介绍

解决痛点

在开发项目的时候,往往需要先根据业务需求和场景设计API接口,使用proto或者golang struct来设计API的输入和输出,随后再创建与API相对应的控制器实现,最后也有可能会提供SDK(同为Golang语言条件下)供内/外部服务调用。在开发过程中会遇到以下痛点:

  • 重复性的代码工作较繁琐。在API中创建输入输出定义文件后还需要在控制器目录下创建对应的文件、创建对应的控制器初始化代码、从API代码中反复拷贝各个输入输出结构名称,在这过程重复性的操作比较繁琐。
  • API与控制器之间的关联没有可靠规范约束。除了API有一定的命名约束外,控制器的创建和方法命名并没有约束,灵活度较高,API的结构名称与控制器方法名称难以约束对应,当接口越来越多时会有一定维护成本。
  • 团队开发多人协作时代码文件冲突概率大。多人开发协作都往一个文件执行变更时,出现文件冲突的概率就会变大,团队协作开发中处理这种文件冲突的精力开销毫无意义。
  • 缺少API的HTTP SDK自动生成工具。当开发完API后,往往需要立即给内部或者外部调用,缺少便捷的SDK生成,需要手动来维护这部分SDK代码,那么对于调用端来说成本非常高。

命令特性

  • 规范了API定义与控制器文件命名、控制器实现方法命名。
  • 规范了API定义与控制器代码之间的关联关系,便于快速定位API实现。
  • 根据API定义自动生成控制器接口、控制器初始化文件及代码、接口初始化代码。
  • 根据API定义自动生成易于使用的HTTP SDK代码。该功能可配置,默认关闭。
  • 支持File Watch自动化生成模式:当某个API结构定义文件发生变化时,自动增量化更新对应的控制器、SDK代码。

前置约定

重要的规范🔥

该命令的目的之一是规范化api代码的编写,那么我们应该有一些重要的规范需要了解(否则生成不了代码哦):

  • api层的接口定义文件路径需要满足/api/模块/版本/定义文件.go,例如:/api/user/v1/user.go/api/user/v1/user_delete.go、etc.
    • 这里的模块指的是API的模块划分,我们可以将API按照不同的业务属性进行拆分方便聚合维护。你也可以将模块认为是具体的业务资源。
    • 这里的版本通常使用v1/v2..这样的形式来定义,用以API兼容性的版本控制。当相同的API出现兼容性更新时,需要通过不同版本号来区分。默认使用v1来管理第一个版本。
    • 这里的定义文件指的是API的输入输出定义文件,通常每个API需要单独定义一个go文件来独立维护。当然也支持将多个API放到一个go文件中统一维护。
  • api定义的结构体名称需要满足操作+Req操作+Res的命名方式。例如:GetOneReq/GetOneResGetListReq/GetListResDeleteReq/DeleteRes、etc.
    • 这里的操作是当前API模块的操作名称,通常对应CURD是:CreateUpdateGetList/GetOneDelete

以下是项目工程模板中的Hello接口示例:

建议性的命名

我们对一些常用的接口定义做了一些建议性的命名,供大家参考:

操作名称建议命名备注
查询列表GetListReq/Res通常是从数据库中分页查询数据记录
查询详情GetOneReq/Res通常接口需要传递主键条件,从数据库中查询记录详情
创建资源CreateReq/Res通常是往数据表中插入一条或多条数据记录
修改资源UpdateReq/Res通常是按照一定条件修改数据表中的一条或多条数据记录
删除资源DeleteReq/Res通常是按照一定条件删除数据表中的一条或多条数据记录

命令使用

该命令通过分析给定的api接口定义目录下的代码,自动生成对应的控制器/SDK Go代码文件。

手动模式

如果是手动执行命令行,直接在项目根目录下执行 gf gen ctrl 即可,她将完整扫描api接口定义目录,并生成对应代码。

$ gf gen ctrl -h
USAGE
    gf gen ctrl [OPTION]

OPTION
    -s, --srcFolder       source folder path to be parsed. default: api
    -d, --dstFolder       destination folder path storing automatically generated go files. default: internal/controller
    -w, --watchFile       used in file watcher, it re-generates go files only if given file is under srcFolder
    -k, --sdkPath         also generate SDK go files for api definitions to specified directory
    -v, --sdkStdVersion   use standard version prefix for generated sdk request path
    -n, --sdkNoV1         do not add version suffix for interface module name if version is v1
    -c, --clear           auto delete generated and unimplemented controller go files if api definitions are missing
    -m, --merge           generate all controller files into one go file by name of api definition source go file
    -h, --help            more information about this command

EXAMPLE
    gf gen ctrl

如果使用框架推荐的项目工程脚手架,并且系统安装了make工具,也可以使用make ctrl快捷指令。

参数说明:

名称必须默认值含义
srcFolderapi指向api接口定义文件目录地址
dstFolderinternal/controller指向生成的控制器文件存放目录
watchFile

用在IDE的文件监控中,用于根据当文件发生变化时自动执行生成操作

sdkPath
如果需要生成HTTP SDK,该参数用于指定生成的SDK代码目录存放路径
sdkStdVersionfalse

生成的HTTP SDK是否使用标准的版本管理。标准的版本管理将自动根据API版本增加请求的路由前缀。例如v1版本的API将会自动增加/api/v1的请求路由前缀。

sdkNoV1false生成的HTTP SDK中,当接口为v1版本时,接口模块名称是否不带V1后缀。

clear

false是否删除controller中与api层定义不存在的控制器接口文件。

merge

false

用以控制生成的ctrl控制器代码文件按照api层的文件生成,而不是默认按照api接口拆分为不同的接口实现文件。

自动模式(推荐)

如果您是使用的GolandIDE,那么可以使用我们提供的配置文件:watchers.xml  自动监听代码文件修改时自动生成接口文件。使用方式,如下图:

使用示例

自动生成的接口定义文件

自动生成的控制器代码文件

自动生成的HTTP SDK代码文件

常见问题

为什么每一个api接口生成一个controller文件而不是合并到一个controller文件中

当然,针对小型项目或者个人简单项目、一个api模块只有几个接口的项目而言,管理的方式并不会成为什么问题,可以根据个人喜好维护代码文件即可。我们这里以较复杂的业务项目,或者企业级项目,在一个api模块的接口比较多的场景来展开描述一下。

  • 首先,开发api接口时,查找api接口实现更加清晰,而不是在一个动则上千行的代码文件中查找。
  • 其次,在多人协作的项目中,如果多人同时修改同一个controller文件在版本管理中容易出现文件冲突。一个api接口对应一个controller实现文件的维护方式能最大减少代码协作时的文件冲突概率,大部分开发者也不希望花费自己宝贵的时间一次又一次地解决文件冲突上。
  • 最后,controller层的代码有它自身的职责:
    • 校验输入参数:客户端提交的参数都是不可信任的,大部分场景下都需要做数据校验。
    • 实现接口逻辑:直接在controller中实现接口逻辑,或者调用一个或多个service接口、第三方服务接口来实现接口逻辑。注意事项,不能在service层的接口中去实现api接口逻辑,因为api接口是与具体的业务场景绑定的,无法复用。💀大部分常见的错误是controller直接把请求透传给service接口来实现api接口逻辑,造成了controller看起来可有可无、service层的实现越来越重且无法复用。💀
    • 生成返回数据:组织内部产生的结果数据,生成接口定义的返回数据接口。
  • 这些职责也就意味着controller的代码也是比较复杂,分开维护能减少开发者心智负担、易于清晰维护api接口实现逻辑。

一些建议

如果一个api模块下的接口文件太多,建议将复杂的api模块进一步划分为子模块。这样可以对复杂的api模块进行解耦,也能通过多目录的方式来维护api接口定义和controller接口实现文件。目录结构会更清晰,更利于多人协作和版本管理。

看完以上关于对此的设计后,如果您仍然想使用单源码文件来管理所有接口,可以参考merge参数。

根据api模块生成对应的controller模块中为何存在一个空的go文件

例如

说明

每个api模块会生成一个空的该模块controller下的go文件,该文件只会生成一次,用户可以在里面填充必要的预定义代码内容,例如,该模块controller内部使用的变量、常量、数据结构定义,或者包初始化init方法定义等等。我们提倡好的代码管理习惯,模块下的预定义内容尽量统一维护到该模块下以模块名称命名的go文件中(模块.go),而不是分散到各个go文件中,以便于更好地维护代码。

如果该controller目前没有需要自定义填充的代码内容,那么保留该文件为空即可,为未来预留扩展能力。





Content Menu

  • No labels

36 Comments

  1. 手动怎么生成呢?用gf gen ctrl后,在api中没有生成新文件,
    现在的方式太复杂了,维护的文件也太多,比JAVA还绕,除非有配套的强大开发工具,如JAVA的eclipse.exe,不然工作量太大了。还是1版本更经典得多

    1. 注意你的gf版本,该特性需要 v2.5+

  2. zhc

    这个是根据API文件夹里的结构体来自动生成的吗


  3. api/instance/v1 只支持三级? 而且是每一个req, 单独生成instance_v1_req文件, 生成的文件是不是有点太多了? 这种多文件,感觉有点像为微服务服务的。 

    1. package admin

      import (
      "hotgo\api\admin"   问题
      )


      gf version =  v2.5.0

      1. 已修复,近期会发布一个fix版本。

    2. 是的,单独一个接口会生成一个对应的控制器处理文件。这个命令是为团队开发时,规范化接口开发使用的,保证大家开发的代码文件名称、路径、方法名称都是一致的,大家只需要填充对应的业务逻辑可以。多人协作时出现文件冲突的概率也会很低。

      1. 这里有个问题,比如我admin模块下的控制器,admin.NewV1,其中不同的接口,有不同的中间件绑定需求,咋解决?难道拿到实例后,对每个接口一个个列出来分组绑定??

  4. 自动生成import重复了

    import (
    "xxxx\api\admin"
    "xxxx/api/admin"
    )

    1. 请更新最新版本。

  5. 目前刚开始使用gf,遇到了关于api的问题,在api中定义了*Req和*Res,在定义Controller的方法返回值是这样的:

    (res *v1.CarGetListRes, err error)

    目前我想返回json给http响应body,我发现直能通过g.RequestFromCtx(ctx).Response.WriteJson()来实现,我若直接return或者给返回值复制,响应结果是不会有任何内容的,请问是我哪里写错了吗



    版本信息:
    GoFrame CLI Tool v2.5.1, https://goframe.org
    GoFrame Version: v2.5.1 in current go.mod
    CLI Installed At: /usr/local/bin/gf
    CLI Built Detail:
      Go Version:  go1.20.4
      GF Version:  v2.5.1
      Git Commit:  2023-07-26 21:27:58 e0e00434cc87d6edf64fc3df40ce7d3f40758794
      Build Time:  2023-07-26 21:32:56

    1. 是不是少了用了ghttp.MiddlewareHandlerResponse这个中间件

  6. 自动生成控制器是减少了大量的重复工作,但我现在遇到个问题是细分路由的时候感觉不方便,分组用不同的中间件现在怎么细化好呢,以前可以分,现在是一个大的接口直接下来了

  7. 多版本时logic文件应该怎么命名呢?不同版本的相同方法怎么命名?有没有例子啊,自动生成的service文件内容是什么样?

  8. 用gf gen ctrl生成的文件注册路由报重复注册问题,这个是有什么特殊的用法吗?

    报错内容:

    2023-08-17 19:27:02.478 [FATA] {b0bad01813287c17ff18f57f65e25fef} duplicated route registry "/generate-gen-code@default" at /gsm/internal/cmd/cmd.go:21 , already registered at /gsm/internal/cmd/cmd.go:21

    cmd.go

    package cmd
    
    import (
    	"context"
    	"github.com/gogf/gf/v2/frame/g"
    	"github.com/gogf/gf/v2/net/ghttp"
    	"github.com/gogf/gf/v2/os/gcmd"
    	"gsm/internal/controller/generate"
    	"gsm/internal/controller/hello"
    )
    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(),
    					generate.NewV1(),
    				)
    			})
    
    			s.Run()
    			return nil
    		},
    	}
    )

    生成的generate_new.go

    // =================================================================================
    // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
    // =================================================================================
    
    package generate
    
    import (
    	"gsm/api/generate"
    )
    
    type ControllerV1 struct{}
    
    func NewV1() generate.IGenerateV1 {
    	return &ControllerV1{}
    }
    1. duplicated route registry   重复路由注册,请检查路由地址是否有重复的
      1. 好的,找到问题了,确实api定义里路由的方法写错了,之前找问题一直纠结在cmd这里,多谢

        1. 设置 s.SetRouteOverWrite(true) 可以先把服务跑起来,然后看下路由列表就比较清晰了
  9. 郭强 注释接口仍然被解析生成接口

  10. 郭强 请问定义了api,生成好了controller的文件,在controller中写了代码后,后面发现api里面定义的有些用不到了,需要删除,结果controller重新被重置为了初始状态,是使用姿势不对吗?

    1. 同问,感觉某些操作会让自动生成失效

    2. 没看明白,请提issue详细描述。

      1. 测试了一下是api定义规范要满足Req/Res,自动生成的controller中不能修改自动生成的req和res,如果修改了后,对应api文件发生了变化,就会自动覆盖掉对应的控制器方法为初始状态

  11. 我想只生成某个目录下的api,怎么做呢?

    我直接gf gen ctrl -s ./api/v1/user为啥不行呢?

  12. +1,若是想指定单个api目录生成controller,命令gf gen ctrl -s '.\api\big_screen'无效,只能全量生成吗

    api目录:

    1. 我发现用自动模式是可以新创建的文件生成

    2. 请问有解决方法了吗?

    3. 我看了下源码,知道了啥原因了。-s目录默认遵循的就是这个规则 /api/模块/版本/定义文件.go   所以你需要指定目录也是需要遵循这个结构。即知道目录后面必须是模块/版本/定义文件.go  这样的目录结构才能生成。说白了就是再加一个目录层次。

      1. 说白了,path是写死的, -s 指定了/path目录,就只会找/path/moudle/v1/xx.go文件生成代码,这都也不“再加一个目录层次”的问题,应该是不支持指定目录下一级go文件生成,难不成为了这个功能又搞三个目录?

  13. +1,gf gen ctrl -s -d 似乎不生效。想把controller文件都生成到  internal/app/模块名称/controller  目录。

  14. api接口每一个接口生成一个控制器文件,这样很不友好,一个大的项目会成上万个文件,不仅太乱,而且热更新会越来越慢,每修改一次就会等好久

    1. 可以使用-merge参数

  15. 有个问题,如果res本身就是要返回一个array,这种情况怎么处理呢

    1. type TestDataList struct {
          Name string
      }
      type TestRes []TestDataList

  16. 请问这种开发规范有没有示例项目呢?

  17. /api/模块/版本/定义文件.go

    请问如果有子模块应该如何定义呢?

    例如如下模块,对应的目录结构和路由地址是怎么样的:

    admin 模块
    ├── user 模块
    ├   ├── create 操作
    ├   ├── ... 其他操作 ├── product 模块 ├── create 操作
    ├── ... 其他操作