在项目的api目录下,我们开始定义我们的CURD接口。

  • 接口我们使用RESTful风格设计,充分使用GET/POST/PUT/DELETEHTTP Method,这样规范设计的接口会非常优雅。
  • 同样的,我们默认开始使用v1版本。使用版本号做为良好的开发习惯,有利于未来接口的兼容性维护。

user api definition

创建接口

api/user/v1/user.go

  1. type CreateReq struct {
  2. g.Meta `path:"/user" method:"post" tags:"User" summary:"Create user"`
  3. Name string `v:"required|length:3,10" dc:"user name"`
  4. Age uint `v:"required|between:18,200" dc:"user age"`
  5. }
  6. type CreateRes struct {
  7. Id int64 `json:"id" dc:"user id"`
  8. }

简要介绍:

  • 接口定义中,使用g.Meta来管理接口的元数据信息,这些元数据信息通过标签的形式定义在g.Meta属性上。这里的元数据信息包括:path路由地址、method请求方式、tags接口分组(用于生成接口文档)、summary接口描述。这些元数据信息都是OpenAPIv3里面的东西,我们这里不做详细介绍,大家了解即可,感兴趣可以参考章节:接口文档-OpenAPIv3
  • 这里的NameAge属性即是咱们接口的参数定义。其中dc标签是description的缩写,表示参数的含义;v标签是valid得缩写,表示参数的校验规则。我们这里使用到了3条内置的校验规则:
    • required:该参数是必需参数。
    • length:参数的长度校验。
    • between:参数的大小校验。
    • 这里仅做了解即可,更多的校验规则请参考章节 数据校验-校验规则
  • 请求的参数结构体CreateReq中,我们并没有定义参数的接收方式,因为GoFrame框架支持非常强大灵活的参数接收方式,能够自动识别Query String/Form/Json/Xml等提交方式,并将提交参数自动映射到请求参数接收对象上。
  • 只有返回的参数结构体中带有json标签,因为返回的数据往往需要转换为json格式给前端使用,通过snake的参数命名的方式更符合前端命名习惯。

Step3 - 编写api接口定义 - 图2提示

RESTful风格的接口设计中,我们通常使用HTTP Method中的POST来表示写入操作,而使用PUT来表示更新操作。

删除接口

api/user/v1/user.go

  1. type DeleteReq struct {
  2. g.Meta `path:"/user/{id}" method:"delete" tags:"User" summary:"Delete user"`
  3. Id int64 `v:"required" dc:"user id"`
  4. }
  5. type DeleteRes struct{}

这里的路由标签path使用的/user/{id},其中的{id}表示一个字段匹配路由,该参数通过URL Path的方式传递,参数名称为id。可以看到,我们在请求参数对象中正好定义了一个Id参数,是的,从路由中匹配到的id参数会不区分参数字母大小写直接映射到该Id上。

举个例子:路由/user/1中,id参数的值便是1;在路由/user/100中,id参数的值便是100

更新接口

api/user/v1/user.go

  1. // Status marks user status.
  2. type Status int
  3. const (
  4. StatusOK Status = 0 // User is OK.
  5. StatusDisabled Status = 1 // User is disabled.
  6. )
  7. type UpdateReq struct {
  8. g.Meta `path:"/user/{id}" method:"put" tags:"User" summary:"Update user"`
  9. Id int64 `v:"required" dc:"user id"`
  10. Name *string `v:"length:3,10" dc:"user name"`
  11. Age *uint `v:"between:18,200" dc:"user age"`
  12. Status *Status `v:"in:0,1" dc:"user status"`
  13. }
  14. type UpdateRes struct{}

在这里:

  • 我们这里定义了一个用户状态类型Status,采用的是Golang里面约定俗成的enums定义方式。这里大家了解即可。
  • Status参数的校验使用了in:0,1校验规则,该规则将会校验传递的Status的值必需是我们定义的常量的两个值StatusOK/StatusDisabled,即0/1
  • 接口参数我们使用了指针来接收,目的是避免类型默认值对我们修改接口的影响。举个例子,假如Status不定义为指针,那么它就会有默认值0的影响,那么在处理逻辑中,很难判断到底调用端有没有传递该参数,是否要真正修改数值为0。但我们使用指针后,当用户没有传递该参数时,该参数的默认值就是nil,处理逻辑便很好做判断。

查询接口(单个)

api/user/v1/user.go

  1. type GetOneReq struct {
  2. g.Meta `path:"/user/{id}" method:"get" tags:"User" summary:"Get one user"`
  3. Id int64 `v:"required" dc:"user id"`
  4. }
  5. type GetOneRes struct {
  6. *entity.User `dc:"user"`
  7. }

这里的返回结果我们使用了*entity.User结构体,该结构是前面我们通过make dao命令生成的entity,该数据结构与数据表字段一一对应。

查询接口(列表)

api/user/v1/user.go

  1. type GetListReq struct {
  2. g.Meta `path:"/user" method:"get" tags:"User" summary:"Get users"`
  3. Age *uint `v:"between:18,200" dc:"user age"`
  4. Status *Status `v:"in:0,1" dc:"user age"`
  5. }
  6. type GetListRes struct {
  7. List []*entity.User `json:"list" dc:"user list"`
  8. }

该接口可以根据AgeStatus进行查询,返回的是多条记录List []*entity.User

学习小结

本章节的示例源码:https://github.com/gogf/quick-demo/blob/main/api/user/v1/user.go

可以看到,在GoFrame框架的脚手架项目定义api接口相当优雅,并且支持自动的数据校验、元数据注入、灵活的路由配置等实用特性。这种接口定义方式,可以自动化地生成接口文档,代码即文档,我们也可以保证代码和文档的一致性。

并且,这还并不是GoFrame魅力的全部,只是玫瑰上的一片花瓣。下一步,我们将使用脚手架工具,自动化地帮我们生成对应的controller控制代码。