GO 的 Web 开发系列(五)—— 使用 Swagger 生成一份好看的接口文档
<p>经过前面的文章,已经完成了 Web 系统基础功能的搭建,也实现了 API 接口、HTML 模板渲染等功能。接下来要做的就是使用 <code>Swagger</code> 工具,为这些 Api 接口生成一份好看的接口文档。</p><h2 id="一-写注释">一、写注释</h2><p>注释是 <code>Swagger</code> 的灵魂,<code>Swagger</code> 是通过特定格式的注释生成接口文档的。</p><h3 id="1-1-基础注释">1.1 基础注释</h3><p>这部分基础注释对全接口文档通用,指定接口文档的基础信息,可添加在 <code>main</code> 函数上。</p><table><thead><tr><th>注释</th><th>说明</th><th>示例</th></tr></thead><tbody><tr><td>title</td><td><strong>必填</strong> 应用程序的名称。</td><td>// @title Swagger Example API</td></tr><tr><td>version</td><td><strong>必填</strong> 提供应用程序API的版本。</td><td>// @version 1.0</td></tr><tr><td>description</td><td>应用程序的简短描述。</td><td>// @description This is a sample server celler server.</td></tr><tr><td>tag.name</td><td>标签的名称。</td><td>// @tag.name This is the name of the tag</td></tr><tr><td>tag.description</td><td>标签的描述。</td><td>// @tag.description Cool Description</td></tr><tr><td>tag.docs.url</td><td>标签的外部文档的URL。</td><td>// @tag.docs.url<a href="https://example.com/">https://example.com</a></td></tr><tr><td>tag.docs.description</td><td>标签的外部文档说明。</td><td>// @tag.docs.description Best example documentation</td></tr><tr><td>termsOfService</td><td>API的服务条款。</td><td>// @termsOfService<a href="http://swagger.io/terms/">http://swagger.io/terms/</a></td></tr><tr><td>contact.name</td><td>公开的API的联系信息。</td><td>// @contact.name API Support</td></tr><tr><td>contact.url</td><td>联系信息的URL。 必须采用网址格式。</td><td>// @contact.url<a href="http://www.swagger.io/support">http://www.swagger.io/support</a></td></tr><tr><td>contact.email</td><td>联系人/组织的电子邮件地址。 必须采用电子邮件地址的格式。</td><td>// @contact.email<a href="mailto:support@swagger.io">support@swagger.io</a></td></tr><tr><td>license.name</td><td><strong>必填</strong> 用于API的许可证名称。</td><td>// @license.name Apache 2.0</td></tr><tr><td>license.url</td><td>用于API的许可证的URL。 必须采用网址格式。</td><td>// @license.url<a href="http://www.apache.org/licenses/LICENSE-2.0.html">http://www.apache.org/licenses/LICENSE-2.0.html</a></td></tr><tr><td>host</td><td>运行API的主机(主机名或IP地址)。</td><td>// @host localhost:8080</td></tr><tr><td>BasePath</td><td>运行API的基本路径。</td><td>// @BasePath /api/v1</td></tr><tr><td>accept</td><td>API 可以使用的 MIME 类型列表。 请注意,Accept 仅影响具有请求正文的操作,例如 POST、PUT 和 PATCH。 值必须如“<a href="https://github.com/swaggo/swag/blob/master/README_zh-CN.md#mime%E7%B1%BB%E5%9E%8B">Mime类型</a>”中所述。</td><td>// @accept json</td></tr><tr><td>produce</td><td>API可以生成的MIME类型的列表。值必须如“<a href="https://github.com/swaggo/swag/blob/master/README_zh-CN.md#mime%E7%B1%BB%E5%9E%8B">Mime类型</a>”中所述。</td><td>// @produce json</td></tr><tr><td>query.collection.format</td><td>请求URI query里数组参数的默认格式:csv,multi,pipes,tsv,ssv。 如果未设置,则默认为csv。</td><td>// @query.collection.format multi</td></tr><tr><td>schemes</td><td>用空格分隔的请求的传输协议。</td><td>// @schemes http https</td></tr><tr><td>externalDocs.description</td><td>Description of the external document.</td><td>// @externalDocs.description OpenAPI</td></tr><tr><td>externalDocs.url</td><td>URL of the external document.</td><td>// @externalDocs.url<a href="https://swagger.io/resources/open-api/">https://swagger.io/resources/open-api/</a></td></tr><tr><td>x-name</td><td>扩展的键必须以x-开头,并且只能使用json值</td><td>// @x-example-key {"key": "value"}</td></tr></tbody></table><h3 id="1-2-API-接口注释">1.2 API 接口注释</h3><p>这部分注释用于声明一个接口,可将这部分注释添加到相应的接口方法上。</p><table><thead><tr><th>注释</th><th>描述</th></tr></thead><tbody><tr><td>description</td><td>操作行为的详细说明。</td></tr><tr><td>description.markdown</td><td>应用程序的简短描述。该描述将从名为<code>endpointname.md</code>的文件中读取。</td></tr><tr><td>id</td><td>用于标识操作的唯一字符串。在所有API操作中必须唯一。</td></tr><tr><td>tags</td><td>每个API操作的标签列表,以逗号分隔。</td></tr><tr><td>summary</td><td>该操作的简短摘要。</td></tr><tr><td>accept</td><td>API 可以使用的 MIME 类型列表。 请注意,Accept 仅影响具有请求正文的操作,例如 POST、PUT 和 PATCH。 值必须如“<a href="https://github.com/swaggo/swag/blob/master/README_zh-CN.md#mime%E7%B1%BB%E5%9E%8B">Mime类型</a>”中所述。</td></tr><tr><td>produce</td><td>API可以生成的MIME类型的列表。值必须如“<a href="https://github.com/swaggo/swag/blob/master/README_zh-CN.md#mime%E7%B1%BB%E5%9E%8B">Mime类型</a>”中所述。</td></tr><tr><td>param</td><td>用空格分隔的参数。<code>param name</code>,<code>param type</code>,<code>data type</code>,<code>is mandatory?</code>,<code>comment</code> <code>attribute(optional)</code></td></tr><tr><td>security</td><td>每个API操作的<a href="https://github.com/swaggo/swag/blob/master/README_zh-CN.md#%E5%AE%89%E5%85%A8%E6%80%A7">安全性</a>。</td></tr><tr><td>success</td><td>以空格分隔的成功响应。<code>return code</code>,<code>{param type}</code>,<code>data type</code>,<code>comment</code></td></tr><tr><td>failure</td><td>以空格分隔的故障响应。<code>return code</code>,<code>{param type}</code>,<code>data type</code>,<code>comment</code></td></tr><tr><td>response</td><td>与success、failure作用相同</td></tr><tr><td>header</td><td>以空格分隔的头字段。<code>return code</code>,<code>{param type}</code>,<code>data type</code>,<code>comment</code></td></tr><tr><td>router</td><td>以空格分隔的路径定义。<code>path</code>,<code>[httpMethod]</code></td></tr><tr><td>deprecatedrouter</td><td>与router相同,但是是deprecated的。</td></tr><tr><td>x-name</td><td>扩展字段必须以<code>x-</code>开头,并且只能使用json值。</td></tr><tr><td>deprecated</td><td>将当前API操作的所有路径设置为deprecated</td></tr></tbody></table><h3 id="1-3-类型枚举">1.3 类型枚举</h3><p>以上接口注解中用到的枚举类型的介绍。</p><p>Mime 类型枚举:</p><p><code>swag</code> 接受所有格式正确的 MIME 类型, 即使匹配 <code>*/*</code>。除此之外,<code>swag</code> 还接受某些 MIME 类型的别名。</p><table><thead><tr><th>Alias</th><th>MIME Type</th></tr></thead><tbody><tr><td>json</td><td>application/json</td></tr><tr><td>xml</td><td>text/xml</td></tr><tr><td>plain</td><td>text/plain</td></tr><tr><td>html</td><td>text/html</td></tr><tr><td>mpfd</td><td>multipart/form-data</td></tr><tr><td>x-www-form-urlencoded</td><td>application/x-www-form-urlencoded</td></tr><tr><td>json-api</td><td>application/vnd.api+json</td></tr><tr><td>json-stream</td><td>application/x-json-stream</td></tr><tr><td>octet-stream</td><td>application/octet-stream</td></tr><tr><td>png</td><td>image/png</td></tr><tr><td>jpeg</td><td>image/jpeg</td></tr><tr><td>gif</td><td>image/gif</td></tr></tbody></table><p>参数类型枚举:</p><table><thead><tr><th>参数类型</th><th>描述</th></tr></thead><tbody><tr><td>query</td><td>请求的 url 参数</td></tr><tr><td>path</td><td>放在请求路径中的参数</td></tr><tr><td>header</td><td>请求 header 中的参数</td></tr><tr><td>body</td><td>请求 body 中的参数</td></tr><tr><td>formData</td><td><code>x-www-form-urlencoded</code> 请求是的表单参数</td></tr></tbody></table><p>数据类型枚举:</p><table><thead><tr><th>数据类型</th><th>对应实际类型</th></tr></thead><tbody><tr><td>string</td><td>string</td></tr><tr><td>integer</td><td>int, uint, uint32, uint64</td></tr><tr><td>number</td><td>float32</td></tr><tr><td>boolean</td><td>bool</td></tr><tr><td>结构体</td><td>结构体类型</td></tr></tbody></table><p>更多用法内容可参考官方文档:<a href="https://github.com/swaggo/swag/blob/master/README_zh-CN.md">https://github.com/swaggo/swag/blob/master/README_zh-CN.md</a></p><h3 id="1-4-示例">1.4 示例</h3><p><strong>通用 API 示例:</strong></p><p>以下注释指定了文档的基本信息,以及基于 <code>apikey</code> 方式的一种安全校验方式:</p><pre><code class="language-go">// @title Aurora Admin-API 文档// @version v0.0.1// @description Aurora 建站// @contact.name nineya// @contact.url https://www.nineya.com// @contact.email 361654768@qq.com// @schemes http https// @host localhost:8888// @BasePath /api/admin// @produce json// @securityDefinitions.apikey admin// @in header// @name Admin-Authorization</code></pre><blockquote><p>注解不能放在 <code>@securityDefinitions</code> 相关注解的后面,否则将不会被解析</p></blockquote><p><strong>接口 API 示例:</strong></p><pre><code class="language-go">// @summary Upload attachment by id// @description Upload attachment by id// @tags attachment// @accept json// @produce json// @param id path id true "Attachment id"// @param param body request.UpdateAttachmentParam true "Attachment name and team information"// @success 200 {object} response.Response// @security admin// @router /attachment/{id} [put]</code></pre><h2 id="二-文档生成">二、文档生成</h2><p>使用命令下载 swag:</p><pre><code class="language-bash">go install github.com/swaggo/swag/cmd/swag@latest</code></pre><p>使用命令生成 swag 所需的文件:</p><pre><code class="language-bash">swag init</code></pre><p>这将会扫描源程序,解析注释并生成 <code>docs</code> 文件夹和文档信息文件。</p><p><img src="https://blog.nineya.com/upload/2024/02/image.png" alt="go-swagger"></p><h2 id="三-与-Gin-集成">三、与 Gin 集成</h2><p>下载安装 <code>gin-swagger</code> :</p><pre><code class="language-bash">go get -u github.com/swaggo/gin-swaggergo get -u github.com/swaggo/files</code></pre><p>导入 <code>docs</code>下的文件:</p><pre><code class="language-go">import ( _ "go-project-name/docs")</code></pre><p>添加 Gin 路由:</p><pre><code class="language-go">swaggerGroup := router.Group("swagger")swaggerGroup.GET("/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))</code></pre><p>通过 <code>/swagger/index.html</code> 可以访问到文档:</p><p><img src="https://blog.nineya.com/upload/2024/02/image-1706785148451.png" alt="访问 swagger 接口文档"></p><h2 id="四-多文档">四、多文档</h2><p>我们一个系统可能包含开发者、用户、管理员多种角色,我们需要为不同的角色分别创建接口文档。</p><h3 id="4-1-生成文档">4.1 生成文档</h3><p>要生成多份文档,在生成文档时就不能直接执行 <code>swag init</code> 命令了,需要指定更多的参数:</p><pre><code class="language-bash">swag init -hNAME: swag init - Create docs.goUSAGE: swag init [command options] [arguments...]OPTIONS: --generalInfo value, -g value API通用信息所在的go源文件路径,如果是相对路径则基于API解析目录 (默认: "main.go") --dir value, -d value API解析目录 (默认: "./"),多个目录可用逗号分隔 --exclude value 解析扫描时排除的目录,多个目录可用逗号分隔(默认:空) --propertyStrategy value, -p value 结构体字段命名规则,三种:snakecase,camelcase,pascalcase (默认: "camelcase") --output value, -o value 文件(swagger.json, swagger.yaml and doc.go)输出目录 (默认: "./docs") --parseVendor 是否解析vendor目录里的go源文件,默认不 --parseDependency 是否解析依赖目录中的go源文件,默认不 --markdownFiles value, --md value 指定API的描述信息所使用的markdown文件所在的目录 --generatedTime 是否输出时间到输出文件docs.go的顶部,默认是 --codeExampleFiles value, --cef value 解析包含用于 x-codeSamples 扩展的代码示例文件的文件夹,默认禁用 --parseInternal 解析 internal 包中的go文件,默认禁用 --parseDepth value 依赖解析深度 (默认: 100) --instanceName value 设置文档实例名 (默认: "swagger")</code></pre><p>可分别为每份文档执行以下命令方法生成多份文档:</p><pre><code class="language-bash">swag init -g 通用API所在Go文件 -d API解析目录 --exclude 排除的目录 -o 文档输出目录 --instanceName 文档实例名</code></pre><p>举例小玖的结构体对象所在目录为 <code>internal/application/param</code>,<code>Admin</code> 接口所在目录为<code>internal/application/router/api/admin</code>,<code>Content</code> 接口所在目录为 <code>internal/application/router/api/content</code>。</p><p><strong>Admin 文档命令:</strong></p><pre><code class="language-bash">swag init -g index.go -d internal/application/router/api/admin,internal/application/param -o ./docs/admin --instanceName=admin</code></pre><p><strong>Content 文档命令:</strong></p><pre><code class="language-bash">swag init -g index.go -d internal/application/router/api/content,internal/application/param -o ./docs/content --instanceName=content</code></pre><p><strong>注意:</strong></p><p><code>-g</code> 参数的路径相对于 <code>-d</code> 参数的第一个路径。</p><p>如果 <code>-d</code> 指定的路径下没有 Go 文件,会有 <code>execute go list command, exit status 1, stdout:, stderr:no Go files in ...</code> 错误提示,无影响。</p><h3 id="4-2-与-Gin-集成">4.2 与 Gin 集成</h3><p>导入 <code>docs</code> 下的文件:</p><pre><code class="language-go">import ( _ "go-project-name/docs/admin" _ "go-project-name/docs/content")</code></pre><p>添加 Gin 路由:</p><pre><code class="language-go">swaggerGroup := router.Group("swagger")swaggerGroup.GET("/admin/*any", ginSwagger.WrapHandler( swaggerFiles.NewHandler(), func(config *ginSwagger.Config) { config.InstanceName = "admin" }))swaggerGroup.GET("/content/*any", ginSwagger.WrapHandler( swaggerFiles.NewHandler(), func(config *ginSwagger.Config) { config.InstanceName = "content" }))</code></pre><p>通过 <code>/swagger/admin/index.html</code> 和 <code>/swagger/content/index.html</code> 可以分别访问到两份文档。</p>