接口文档-OpenAPIv3 - 图1提示

OpenAPIv3 协议主要使用在规范路由中,阅读接口文档协议介绍之前,请先了解一下规范路由: 路由注册-规范路由

一、 OpenAPIv3

详细的 OpenAPIv3 协议介绍请参考: https://swagger.io/specification/

二、 g.Meta 元数据

接口的元数据信息可以通过为输入结构体embedded方式嵌入g.Meta结构,并通过g.Meta的属性标签方式来实现。

关于元数据组件的介绍,详情请参考章节: 元数据-gmeta

三、常用协议标签

输入输出结构体中的属性的标签完整支持 OpenAPIv3 协议,因此只要增加了对应的协议标签,那么生成的 OpenAPIv3 接口信息中将会自动包含该属性。

大部分的标签属性已经被 Server 组件自动生成,开发者需要手动设置的标签不多。

1、基础标签

常见的基础标签包括:

常见OpenAPIv3标签说明备注
path结合注册时的前缀共同构成接口URI路径用于 g.Meta 标识接口元数据
tags接口所属的标签,用于接口分类用于 g.Meta 标识接口元数据
method接口的请求方式: GET/PUT/POST/DELETE…(不区分大小写)用于 g.Meta 标识接口元数据
deprecated标记该接口废弃用于 g.Meta 标识接口元数据
summary接口/参数概要描述缩写 sm
description接口/参数详细描述缩写 dc
in参数的提交方式header/path/query/cookie
default参数的默认值缩写 d
mime接口的 MIME 类型,例如 multipart/form-data 一般是全局设置,默认为 application/json用于 g.Meta 标识接口元数据
type参数的类型,一般不需要设置,特殊参数需要手动设置,例如 file仅用于参数属性

接口文档-OpenAPIv3 - 图2提示

更多标签请参考标准的 OpenAPIv3 协议: https://swagger.io/specification/

除此之外,响应结构体的 g.Meta 还支持额外的标签以设置更详细的文档信息:

标签说明备注
status设置响应的默认返回状态码用于 g.Meta 标识接口元数据,默认值为 200
responseExample设置响应的默认返回示例的 json 文件路径用于 g.Meta 标识接口元数据,缩写 resEg

responseExample 支持的 json 文件格式如下:

  • Array
  • Object
  1. [
  2. {
  3. "code": 0,
  4. "message": "Success",
  5. "data": null
  6. },
  7. {
  8. "code": 1,
  9. "message": "Internal Server Error",
  10. "data": null
  11. }
  12. ]
  1. {
  2. "success": {
  3. "code": 0,
  4. "message": "Success",
  5. "data": null
  6. },
  7. "error": {
  8. "code": 1,
  9. "message": "Internal Server Error",
  10. "data": null
  11. }
  12. }

2、扩展标签

OpenAPI 规范里面,所有名称以 x- 开头的标签是开发者可自定义的扩展标签。扩展标签可以在任意的接口、属性中以 Golang struct tag 的形式定义,在接口文档生成时,将会作为独立的字段返回。例如:

  1. package main
  2. import (
  3. "context"
  4. "github.com/gogf/gf/v2/errors/gcode"
  5. "github.com/gogf/gf/v2/errors/gerror"
  6. "github.com/gogf/gf/v2/frame/g"
  7. "github.com/gogf/gf/v2/net/ghttp"
  8. )
  9. type GetListReq struct {
  10. g.Meta `path:"/user" tags:"User" method:"get" x-group:"User/Info" summary:"Get user list with basic info."`
  11. Page int `dc:"Page number" d:"1" x-sort:"1"`
  12. Size int `dc:"Size for per page." d:"10" x-sort:"2"`
  13. }
  14. type GetListRes struct{}
  15. type Controller struct{}
  16. func (c *Controller) GetList(ctx context.Context, req *GetListReq) (res *GetListRes, err error) {
  17. return nil, gerror.NewCode(gcode.CodeNotImplemented)
  18. }
  19. func main() {
  20. s := g.Server()
  21. s.Group("/", func(group *ghttp.RouterGroup) {
  22. group.Bind(new(Controller))
  23. })
  24. s.SetOpenApiPath("/api.json")
  25. s.SetSwaggerPath("/swagger")
  26. s.SetPort(8199)
  27. s.Run()
  28. }

执行后,访问地址 http://127.0.0.1:8199/swagger 可以查看 swagger ui,访问 http://127.0.0.1:8199/api.json 可以查看对应的 OpenAPIv3 接口文档。其中生成的 OpenAPIv3 接口文档如下:

  1. {
  2. "openapi": "3.0.0",
  3. "components": {
  4. "schemas": {
  5. "main.GetListReq": {
  6. "properties": {
  7. "Page": {
  8. "default": 1,
  9. "description": "Page number",
  10. "format": "int",
  11. "properties": {},
  12. "type": "integer",
  13. "x-sort": "1"
  14. },
  15. "Size": {
  16. "default": 10,
  17. "description": "Size for per page.",
  18. "format": "int",
  19. "properties": {},
  20. "type": "integer",
  21. "x-sort": "2"
  22. }
  23. },
  24. "type": "object",
  25. "x-group": "User/Info"
  26. },
  27. "main.GetListRes": {
  28. "properties": {},
  29. "type": "object"
  30. }
  31. }
  32. },
  33. "info": {
  34. "title": "",
  35. "version": ""
  36. },
  37. "paths": {
  38. "/user": {
  39. "get": {
  40. "parameters": [
  41. {
  42. "description": "Page number",
  43. "in": "query",
  44. "name": "Page",
  45. "schema": {
  46. "default": 1,
  47. "description": "Page number",
  48. "format": "int",
  49. "properties": {},
  50. "type": "integer",
  51. "x-sort": "1"
  52. },
  53. "x-sort": "1"
  54. },
  55. {
  56. "description": "Size for per page.",
  57. "in": "query",
  58. "name": "Size",
  59. "schema": {
  60. "default": 10,
  61. "description": "Size for per page.",
  62. "format": "int",
  63. "properties": {},
  64. "type": "integer",
  65. "x-sort": "2"
  66. },
  67. "x-sort": "2"
  68. }
  69. ],
  70. "responses": {
  71. "200": {
  72. "content": {
  73. "application/json": {
  74. "schema": {
  75. "$ref": "#/components/schemas/main.GetListRes"
  76. }
  77. }
  78. },
  79. "description": ""
  80. }
  81. },
  82. "summary": "Get user list with basic info.",
  83. "tags": [
  84. "User"
  85. ],
  86. "x-group": "User/Info"
  87. },
  88. "x-group": "User/Info"
  89. }
  90. }
  91. }

可以看到,扩展标签已经生成到了接口文档中。

四、扩展响应结构体信息

对于需要多种响应状态码的请求,框架在 goai 组件中提供了 IEnhanceResponseStatus 接口,开发者可以通过实现该接口来扩展响应结构体的信息。该接口的相关定义如下:

  1. type EnhancedStatusCode = int
  2. type EnhancedStatusType struct {
  3. Response any
  4. Examples any
  5. }
  6. type IEnhanceResponseStatus interface {
  7. EnhanceResponseStatus() map[EnhancedStatusCode]EnhancedStatusType
  8. }

Response 为响应结构体,和普通的响应结构体类似,你也可以为他添加 g.Meta 标签来添加文档信息,并且在设置了 mime 标签后,结构体也会覆盖通用响应结构体的内容。而 Examples 为响应示例,你可以使用错误码列表自动生成示例内容并显示在文档中,这样可以保证文档内容与实际业务内容的同步。例如:

  1. package main
  2. import (
  3. "context"
  4. "github.com/gogf/gf/v2/errors/gcode"
  5. "github.com/gogf/gf/v2/errors/gerror"
  6. "github.com/gogf/gf/v2/frame/g"
  7. "github.com/gogf/gf/v2/net/ghttp"
  8. "github.com/gogf/gf/v2/net/goai"
  9. )
  10. type StoreMessageReq struct {
  11. g.Meta `path:"/messages" method:"post" summary:"Store a message"`
  12. Content string `json:"content"`
  13. }
  14. type StoreMessageRes struct {
  15. g.Meta `status:"201"`
  16. Id string `json:"id"`
  17. }
  18. type EmptyRes struct {
  19. g.Meta `mime:"application/json"`
  20. }
  21. type CommonRes struct {
  22. Code int `json:"code"`
  23. Message string `json:"message"`
  24. Data interface{} `json:"data"`
  25. }
  26. var StoreMessageErr = map[int]gcode.Code{
  27. 500: gcode.New(1, "Server Dead", nil),
  28. }
  29. func (r StoreMessageRes) EnhanceResponseStatus() (resList map[int]goai.EnhancedStatusType) {
  30. examples := []interface{}{}
  31. example500 := CommonRes{
  32. Code: StoreMessageErr[500].Code(),
  33. Message: StoreMessageErr[500].Message(),
  34. Data: nil,
  35. }
  36. examples = append(examples, example500)
  37. return map[int]goai.EnhancedStatusType{
  38. 403: {
  39. Response: EmptyRes{},
  40. },
  41. 500: {
  42. Response: struct{}{},
  43. Examples: examples,
  44. },
  45. }
  46. }
  47. type Controller struct{}
  48. func (c *Controller) StoreMessage(ctx context.Context, req *StoreMessageReq) (res *StoreMessageRes, err error) {
  49. return nil, gerror.NewCode(gcode.CodeNotImplemented)
  50. }
  51. func main() {
  52. s := g.Server()
  53. s.Group("/", func(group *ghttp.RouterGroup) {
  54. group.Bind(new(Controller))
  55. })
  56. oai := s.GetOpenApi()
  57. oai.Config.CommonResponse = CommonRes{}
  58. oai.Config.CommonResponseDataField = `Data`
  59. s.SetOpenApiPath("/api.json")
  60. s.SetSwaggerPath("/swagger")
  61. s.SetPort(8199)
  62. s.Run()
  63. }

执行后,访问地址 http://127.0.0.1:8199/swagger 可以查看 swagger ui,访问 http://127.0.0.1:8199/api.json 可以查看对应的 OpenAPIv3 接口文档。其中生成的 OpenAPIv3 接口文档如下:

  1. {
  2. "openapi": "3.0.0",
  3. "components": {
  4. "schemas": {
  5. "main.StoreMessageReq": {
  6. "properties": {
  7. "content": {
  8. "format": "string",
  9. "type": "string"
  10. }
  11. },
  12. "type": "object"
  13. },
  14. "main.StoreMessageRes": {
  15. "properties": {
  16. "id": {
  17. "format": "string",
  18. "type": "string"
  19. }
  20. },
  21. "type": "object"
  22. },
  23. "interface": {
  24. "properties": {},
  25. "type": "object"
  26. },
  27. "main.EmptyRes": {
  28. "properties": {},
  29. "type": "object"
  30. },
  31. "struct": {
  32. "properties": {},
  33. "type": "object"
  34. }
  35. }
  36. },
  37. "info": {
  38. "title": "",
  39. "version": ""
  40. },
  41. "paths": {
  42. "/messages": {
  43. "post": {
  44. "requestBody": {
  45. "content": {
  46. "application/json": {
  47. "schema": {
  48. "$ref": "#/components/schemas/main.StoreMessageReq"
  49. }
  50. }
  51. }
  52. },
  53. "responses": {
  54. "201": {
  55. "content": {
  56. "application/json": {
  57. "schema": {
  58. "properties": {
  59. "code": {
  60. "format": "int",
  61. "type": "integer"
  62. },
  63. "message": {
  64. "format": "string",
  65. "type": "string"
  66. },
  67. "data": {
  68. "properties": {
  69. "id": {
  70. "format": "string",
  71. "type": "string"
  72. }
  73. },
  74. "type": "object"
  75. }
  76. },
  77. "type": "object"
  78. }
  79. }
  80. },
  81. "description": ""
  82. },
  83. "403": {
  84. "content": {
  85. "application/json": {
  86. "schema": {
  87. "$ref": "#/components/schemas/main.EmptyRes"
  88. }
  89. }
  90. },
  91. "description": ""
  92. },
  93. "500": {
  94. "content": {
  95. "application/json": {
  96. "schema": {
  97. "properties": {
  98. "code": {
  99. "format": "int",
  100. "type": "integer"
  101. },
  102. "message": {
  103. "format": "string",
  104. "type": "string"
  105. },
  106. "data": {
  107. "properties": {},
  108. "type": "object"
  109. }
  110. },
  111. "type": "object"
  112. },
  113. "examples": {
  114. "example 1": {
  115. "value": {
  116. "code": 1,
  117. "message": "Server Dead",
  118. "data": null
  119. }
  120. }
  121. }
  122. }
  123. },
  124. "description": ""
  125. }
  126. },
  127. "summary": "Store a message"
  128. }
  129. }
  130. }
  131. }

可以看到默认的响应状态码已经被更改为 201,并且响应示例也自动生成。

五、扩展 OpenAPIv3 信息

核心的接口信息已经自动生成,如果开发者想要更进一步完善接口信息,可以通过 s.GetOpenApi() 接口获取到 OpenAPIv3 的结构体对象,并手动填充对应的属性内容即可。我们来看一个示例,在该示例中,我们设计每个接口外层公共的数据结构:

接口文档-OpenAPIv3 - 图3

我们可以发现通过通用的 OpenAPIv3 对象我们可以自定义修改其内容,并且根据它生成其他各种自定义类型的接口文档。

六、添加api.json(swagger)自定义鉴权

对于需要进行api文档鉴权的情况,可以使用 ghttp.BindHookHandler 方法对 s.GetOpenApiPath() 路由绑定前置方法进行鉴权,示例如下:

  1. func main() {
  2. s := g.Server()
  3. // if api.json requires authentication, add openApiBasicAuth handler
  4. s.BindHookHandler(s.GetOpenApiPath(), ghttp.HookBeforeServe, openApiBasicAuth)
  5. s.Run()
  6. }
  7. func openApiBasicAuth(r *ghttp.Request) {
  8. if !r.BasicAuth("OpenApiAuthUserName", "OpenApiAuthPass", "Restricted") {
  9. r.ExitAll()
  10. return
  11. }
  12. }