• tags: Go

目标

当跟随这篇文章完成后将产出如下内容:

准备

  • Go1.14 及以上版本
  • 安装 go-swagger :参见 官方文档
  • 接下来使用 gin 框架作为示例,如果之前没接触过可以先了解下该框架

创建一个项目

$ mkdir go-swagger-example
$ cd go-swagger-example/
$ go mod init gitlab.17zuoye.net/vgo/go-swagger-example

开始使用

首先在你的 `main.go` 定义 go generate 像下面这样:

//go:generate swagger generate spec -o ./swagger.yml
package main

func main() {
		println("Hello world!");
}

此时如果运行 go generate 在项目目录下就会生成一个 swagger.yml 文件:

paths: {}
swagger: "2.0"

使用单独的包托管 swagger 相关定义

在之前实践的过程中发现,如果在多个包中定义了相同名称的结构体会到只一个结构体覆盖另外一个结构体的定义。 所以为了解决这个问题,我把所有 swagger 相关的定义都放在同一个包下来避免相同名字的结构体。

创建 swagger/swagger.go 填充如下内容:

// Package swagger defines API documentation.
//
// Swagger 演示后端接口
//
//    Schemes: http
//    Host: 10.200.242.35:8080
//    BasePath: /api/
//    Version: 0.1.0
//    Contact: 王会<hui.wang.a@17zuoye.com>
//
//    Consumes:
//    - application/json
//
//    Produces:
//    - application/vnd.17zuoye.v1+json
//
// swagger:meta
package swagger

上面文件通过注释来定义了一些接口相关的信息,包括:

  • Schemes 定义可用的协议
  • Host 定义接口地址
  • BasePath 定义接口基础路径
  • Consumes 定义复杂请求的类型(可以覆盖)
  • Produces 定义接口响应类型(可以覆盖)
  • 还有一些其他的信息,比如联系人等。

通过 swagger:meta 来结束声明。

此时我们再次运行 go generate 将会得到如下 swagger.yml 定义:

basePath: /api/
consumes:
- application/json
host: 10.200.242.35:8080
info:
	contact:
		email: hui.wang.a@17zuoye.com
		name: 王会
	description: Swagger 演示后端接口
	title: defines API documentation.
	version: 0.1.0
paths: {}
produces:
- application/vnd.17zuoye.v1+json
schemes:
- http
swagger: "2.0"

编写接口文档

准备 service 包

我们打算将接口实现相关代码放在 service 包下,首先来创建 service/service.go

package service

import (
	"github.com/gin-gonic/gin"
)


var Engine = gin.Default()
var Router = Engine.Group("/api")

func init() {
	// Mount handlers to gin here
}

POST 提交信息

假设我们编写一个创建用户信息的接口,需要名字和年龄两个参数。我们在 service 包下创建 user.go

package service

import (
	"github.com/gin-gonic/gin"
)

func CreateUser(c *gin.Context) {

}

func init() {
	// swagger:route POST /users users-create
	//
	// 创建用户。
	//
	// Responses:
	//   default: DefaultResponse
	//   201: UserResponse
	Router.POST("/users", CreateUser)
}

通过上面代码中的注释我们创建了一个 Swagger 中的 route 其 ID 是 users-create 。 我们在注释中也声明了响应,但是我们目前还没有定义参数和对应的响应。

Go Swagger 的参数定义是反向的,意思就是你需要定义一个 parameter 然后指明用在哪个 routeoperation 上(通过对应的 ID)。下面就让我们一起来看一看,我们来创建 swagger/user.go

package swagger

// UserCreateForm 用于创建用户的表单,可以供 gin 使用
type UserCreateForm struct {
	Username string `json:"username"`
	Age      int    `json:"age"`
}

// UserCreateParams 声明 Swagger 参数生成文档
// swagger:parameters users-create
type UserCreateParams struct {
	// in: body
	Body UserCreateForm
}

// UserEntity user entity to respond
type UserEntity struct {
	ID       uint64 `json:"id"`
	Username string `json:"username"`
	Age      int    `json:"age"`
}

// UserResponse 声明 Swagger 响应用于文档生成
// swagger:response
type UserResponse struct {
	// in: body
	Body UserEntity
}

接下来调整 swagger/swagger.go 定义 DefaultResponse:

// Package swagger defines API documentation.
//
// Swagger 演示后端接口
//
//    Schemes: http
//    Host: 10.200.242.35:8080
//    BasePath: /api/
//    Version: 0.1.0
//    Contact: 王会<hui.wang.a@17zuoye.com>
//
//    Consumes:
//    - application/json
//
//    Produces:
//    - application/vnd.17zuoye.v1+json
//
// swagger:meta
package swagger

// Default default entity
type Default struct {
	Code    int    `json:"code"`
	Message string `json:"msg"`
}

// DefaultResponse 默认响应,用于 Swagger 文档生成
// swagger:response
type DefaultResponse struct {
	// in: body
	Body Default
}

现在我们运行 go generate 将会生成如下 swagger.yml

basePath: /api/
consumes:
- application/json
definitions:
	Default:
		description: Default default entity
		properties:
			code:
				format: int64
				type: integer
				x-go-name: Code
			msg:
				type: string
				x-go-name: Message
		type: object
		x-go-package: gitlab.17zuoye.net/vgo/go-swagger-example/swagger
	UserEntity:
		description: UserEntity user entity to respond
		properties:
			age:
				format: int64
				type: integer
				x-go-name: Age
			id:
				format: uint64
				type: integer
				x-go-name: ID
			username:
				type: string
				x-go-name: Username
		type: object
		x-go-package: gitlab.17zuoye.net/vgo/go-swagger-example/swagger
	UserCreateForm:
		description: UserCreateForm 用于创建用户的表单,可以供 gin 使用
		properties:
			age:
				format: int64
				type: integer
				x-go-name: Age
			username:
				type: string
				x-go-name: Username
		type: object
		x-go-package: gitlab.17zuoye.net/vgo/go-swagger-example/swagger
host: 10.200.242.35:8080
info:
	contact:
		email: hui.wang.a@17zuoye.com
		name: 王会
	description: Swagger 演示后端接口
	title: defines API documentation.
	version: 0.1.0
paths:
	/users:
		post:
			operationId: users-create
			parameters:
			- in: body
				name: Body
				schema:
					$ref: '#/definitions/UserCreateForm'
			responses:
				"201":
					$ref: '#/responses/UserResponse'
				default:
					$ref: '#/responses/DefaultResponse'
			summary: 创建用户。
produces:
- application/vnd.17zuoye.v1+json
responses:
	DefaultResponse:
		description: DefaultResponse 默认响应,用于 Swagger 文档生成
		schema:
			$ref: '#/definitions/Default'
	UserResponse:
		description: UserResponse 声明 Swagger 响应用于文档生成
		schema:
			$ref: '#/definitions/UserEntity'
schemes:
- http
swagger: "2.0"

运行 swagger serve -F swagger swagger.yml 可查看文档。

查询参数

接下来我们来编写查询接口文档,这次和上面创建稍有不同,参数通过 Query 进行传递, 编辑 service/user.go

package service

import (
	"github.com/gin-gonic/gin"
)

func CreateUser(c *gin.Context) {

}

func QueryUsers(c *gin.Context) {

}

func init() {
	// swagger:route POST /users users-create
	//
	// 创建用户。
	//
	// Responses:
	//   default: DefaultResponse
	//   201: UserResponse
	Router.POST("/users", CreateUser)

	// swagger:route GET /users users-query
	//
	// 查询用户。
	//
	// Respones:
	//   default: DefaultResponse
	//   200: UsersResponse
	Router.GET("/users", QueryUsers)
}

接下来调整 swagger/user.go 定义参数和响应:

package swagger

// UserCreateForm 用于创建用户的表单,可以供 gin 使用
type UserCreateForm struct {
	Username string `json:"username"`
	Age      int    `json:"age"`
}

// UserCreateParams 声明 Swagger 参数生成文档
// swagger:parameters users-create
type UserCreateParams struct {
	// in: body
	Body UserCreateForm
}

// UserEntity user entity to respond
type UserEntity struct {
	ID       uint64 `json:"id"`
	Username string `json:"username"`
	Age      int    `json:"age"`
}

// UserResponse 声明 Swagger 响应用于文档生成
// swagger:response
type UserResponse struct {
	// in: body
	Body UserEntity
}

// UserQueryParams 声明 Swagger 参数生成文档
// swagger:parameters users-query
type UserQueryParams struct {
	// json tag 用于 swagger
	// in: query
	Username string `json:"username" form:"username"`
}

// UsersResponse 用户列表响应
// swagger:response
type UsersResponse struct {
	// in: body
	Body struct {
		Page    int          `json:"page"`
		PerPage int          `json:"perPage"`
		Total   int          `json:"total"`
		Users   []UserEntity `json:"users"`
	}
}

运行 go generate && swagger serve -F swagger swagger.yml 可查看效果。

URL 中 Path 参数

如果我们要更新用户信息,按照 RESTful 的设计方式,请求的方式应该是 PATCH /api/users/:id , 此时 route 已经无法满足,需要借助 operation 编写一些原始的 YAML 来实现,下面是调整 后的 service/user.go:

package service

import (
	"github.com/gin-gonic/gin"
)

func CreateUser(c *gin.Context) {

}

func QueryUsers(c *gin.Context) {

}

func UpdateUser(c *gin.Context) {

}

func init() {
	// swagger:route POST /users users-create
	//
	// 创建用户。
	//
	// Responses:
	//   default: DefaultResponse
	//   201: UserResponse
	Router.POST("/users", CreateUser)

	// swagger:route GET /users users-query
	//
	// 查询用户。
	//
	// Responses:
	//   default: DefaultResponse
	//   200: UsersResponse
	Router.GET("/users", QueryUsers)

	// swagger:operation PATCH /users/{userId} users-update
	//
	// 更新用户信息。
	//
	// ---
	// parameters:
	// - in: path
	//   name: userId
	//   type: int
	//   description: 用户 ID
	// - in: body
	//   name: Body
	//   schema:
	//     "$ref": "#/definitions/UserCreateForm"
	// respones:
	//  "200":
	//    "$ref": "#/responses/UserResponse"
	//  "default":
	//    "$ref": "#/responses/DefaultResponse"
	Router.PATCH("/users/:id", UpdateUser)
}

部署

可以通过 CI/CD 生成 swagger.yml 进行部署,然后将对应的 JSON 地址结合公司 http://swagger.17zuoye.net/ 进行部署查看。

相关示例可以参考 http://gitlab.17zuoye.net/vgo/go-swagger-example