Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

OpenAPI学习笔记

12
Jul
2019

OpenAPI学习笔记

By Alex
/ in Go,Java,Work
/ tags OpenAPI, Swagger
0 Comments
简介

OpenAPI是一套API规范( OpenAPI Specification ,OAS),用于定义RESTful API的接口。OpenAPI最初来自SmartBear的Swagger规范。

OpenAPI 目前的版本是3.0,当前Swagger和OpenAPI的关系是:

  1. OpenAPI是一套规范
  2. Swagger是实现OpenAPI规范的工具集,包括:
    1. Swagger Editor:允许你使用YAML语言在浏览器中编写规范,并实时查看生成的API文档
    2. Swagger UI:从OAS兼容的API动态生成一套Web的美观的API文档
    3. Swagger Codegen:用于生成OpenAPI的客户端库(SDK)、服务器桩代码、文档
    4. Swagger Parser:从Java解析Open API定义的独立库
    5. Swagger Core:一个Java库,用于创建、消费、使用OpenAPI定义
    6. Swagger Inspector:从现有的API生成OpenAPI定义、验证API的测试工具
    7. SwaggerHub:OpenAPI的API设计、文档平台

Swagger并非唯一支持OpenAPI的工具,到OpenAPI-Specification的GitHub页面可以看到相关工具的列表。

Swagger 2.0
API示例
JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
{
  // 基本信息
  "swagger": "2.0",
  "info": {
    "title": "Kubernetes",
    "version": "v1.12.1"
  },
  // API端点列表
  "paths": {
    // 端点
    "/api/": {
      // 操作
      "get": {
        "description": "get available API versions",
        // 支持的MIME类型
        "consumes": [
          "application/json",
          "application/yaml",
          "application/vnd.kubernetes.protobuf"
        ],
        "produces": [
          "application/json",
          "application/yaml",
          "application/vnd.kubernetes.protobuf"
        ],
        // 支持的协议
        "schemes": [
          "https"
        ],
        "tags": [
          "core"
        ],
        // 操作的唯一标识
        "operationId": "getCoreAPIVersions",
        // 请求参数说明
        "parameters": [
          {
            "type": "string",
            "description": "Username ",
            "name": "username",
            // 这是URL路径变量
            "in": "path",
            "required": true
          },
          {
            "name": "user",
            // 这是请求体参数,通常是映射到模型的JSON
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "$ref": "#/definitions/models.User"
            }
          },
          {
            "type": "integer",
            "name": "size",
            // 这是请求参数
            "in": "query"
          }
        ],
        // 响应说明,每个状态码对应一个元素
        "responses": {
          "200": {
            "description": "OK",
            // 响应的Schema
            "schema": {
              "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIVersions"
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    }
  }
  // Schema定义列表
  "definitions": {
    // Schema名以域名倒写形式
    "io.k8s.apimachinery.pkg.apis.meta.v1.APIVersions": {
      "description": "APIVersions lists the versions that are available, to allow clients to discover the API at /api, which is the root path of the legacy v1 API.",
      // 必须属性
      "required": [
        "versions",
        "serverAddressByClientCIDRs"
      ],
      // 属性规格列表
      "properties": {
        "apiVersion": {
          "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources",
          "type": "string"
        },
        "kind": {
          "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds",
          "type": "string"
        },
        "serverAddressByClientCIDRs": {
          "description": "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.",
          "type": "array",
          "items": {
            "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ServerAddressByClientCIDR"
          }
        },
        "versions": {
          "description": "versions are the api versions that are available.",
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      // K8S扩展字段
      "x-kubernetes-group-version-kind": [
        {
          "group": "",
          "kind": "APIVersions",
          "version": "v1"
        }
      ]
    }
  }
}
转换为OAS3.0
swagger2openapi

这是一个Node.js开发的工具,可以将Swagger 2.0转换为OAS 3.0,执行下面的命令安装:

Shell
1
npm install -g swagger2openapi

调用格式: swagger2openapi source-spec.json [options]

常用选项说明:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
--resolveInternal    # 是否解析内部引用
--warnProperty       # 警告扩展配置,默认x-s2o-warning
-e, --encoding       # 输入输出编码,默认utf8
-f, --fatal          # 如果引用解析失败,则终止转换
-i, --indent         # JSON缩进,默认4
-o, --outfile        # 输出到文件而非标准输出
-p, --patch          # 尝试修复源定义中的错误
-r, --resolve        # 是否解析外部引用
-t, --targetVersion  # OAS版本,默认3.0.0
-u, --url            # 源的URL
-w, --warnOnly       # 遇到不可修复错误时发出警告而非报错
-y, --yaml           # 输出为YAML格式而非JSON
API文档生成

通过某些工具,可以从既有代码中生成Swagger API文档。

swag

该工具能够将Go Annotations转换为Swagger 2.0文档,为多种流行的Go Web框架(例如Gin)提供了插件,从而快速和既有Web项目集成。

执行下面的命令安装到GOPATH下:

Shell
1
go get -u github.com/swaggo/swag/cmd/swag

在项目根目录,使用 swag init命令可以解析Go代码中的注解并且生成docs目录、docs/docs.go。你需要提供General API annotations,如果这些注解没有存放在根目录的main.go文件中,需要用 -g来指定Go文件路径:

Shell
1
swag init -g pkg/route/routers.go

使用swag init命令之后,你需要导入生成的docs包以及swaggo的另外两个包:

Go
1
2
3
_ "github.com/gmemcc/myproject/docs"
import "github.com/swaggo/gin-swagger" // gin-swagger middleware
import "github.com/swaggo/files" // swagger embed files

General API annotations可用字段:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// @title Swagger Example API
// @version 1.0
// @description This is a sample server celler server.
// @termsOfService http://swagger.io/terms/
 
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
 
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
 
// @host localhost:8080
// @BasePath /api/v1
// @query.collection.format multi
 
// @securityDefinitions.basic BasicAuth
 
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
 
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
 
// @securitydefinitions.oauth2.implicit OAuth2Implicit
// @authorizationurl https://example.com/oauth/authorize
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
 
// @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl https://example.com/oauth/token
// @scope.read Grants read access
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
 
// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
// @tokenUrl https://example.com/oauth/token
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
 
// @x-extension-openapi {"example": "value on a json format"}

示例:

Go
1
2
3
4
5
6
7
8
// @BasePath /myproject/apis/v2
// @version 2.0.0
// @title Myproject API
// @description my project
// @contact.name alex
// @contact.email myproject@gmem.cc
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html

swag init生成的docs包,导出了变量 SwaggerInfo,通过此变量你可以编程式的设置各种字段(和上面这种注释方式等效):

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main
 
import (
    "github.com/gin-gonic/gin"
    "github.com/swaggo/files"
    "github.com/swaggo/gin-swagger"
    
    "./docs" // docs is generated by Swag CLI, you have to import it.
)
 
func main() {
    // programmatically set swagger info
    docs.SwaggerInfo.Title = "Swagger Example API"
    docs.SwaggerInfo.Description = "This is a sample server Petstore server."
    docs.SwaggerInfo.Version = "1.0"
    docs.SwaggerInfo.Host = "petstore.swagger.io"
    docs.SwaggerInfo.BasePath = "/v2"
    docs.SwaggerInfo.Schemes = []string{"http", "https"}

为了在当前Web服务(的HTTP服务器)中查看API文档,需要注册路由,以gin为例: 

Go
1
2
3
4
5
6
7
    r := gin.New()
 
    // use ginSwagger middleware to serve the API docs
    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
 
    r.Run()
}

要生成API,你需要在控制器代码中添加 API Operation annotations。这些注解需要加在controller代码中,所谓controller代码,就是包含所有路由处理函数的包,以gin为例:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package controller
 
import (
    "fmt"
    "net/http"
    "strconv"
 
    "github.com/gin-gonic/gin"
    "github.com/swaggo/swag/example/celler/httputil"
    "github.com/swaggo/swag/example/celler/model"
)
 
// 下面就是一份API Operation annotations
// ShowAccount godoc
// @Summary Show a account
// @Description get string by ID
// @ID get-string-by-int
// @Accept  json
// @Produce  json
// @Param id path int true "Account ID"
// @Success 200 {object} model.Account
// @Header 200 {string} Token "qwerty"
// @Failure 400,404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Failure default {object} httputil.DefaultError
// 这个仅仅影响生成的Swagger API文档,你还需要调用gin的接口,注册下面这个处理函数(控制器)的路由
// @Router /accounts/{id} [get]
func (c *Controller) ShowAccount(ctx *gin.Context) {
    id := ctx.Param("id")
    aid, err := strconv.Atoi(id)
    if err != nil {
        httputil.NewError(ctx, http.StatusBadRequest, err)
        return
    }
    account, err := model.AccountOne(aid)
    if err != nil {
        httputil.NewError(ctx, http.StatusNotFound, err)
        return
    }
    ctx.JSON(http.StatusOK, account)
}

这些注解仅仅影响生成的Swagger API文档,不会对Web服务的逻辑产生任何影响。

运行Web服务,可以在http://localhost:port/swagger/index.html查看Swagger 2.0 API文档。访问/swagger/doc.json可以获得JSON格式的API

OAS

OAS是REST API的描述格式,在一个OpenAPI文件中,你可以定义完整的接口规格,包括:

  1. 可用API端点列表,针对每个端点允许的操作
  2. 操作的输入、输出参数
  3. 身份验证方法
  4. 附属信息,包括使用条款、License

关于OAS的编写,需要注意:

  1. 可以用YAML、JSON两种格式编写
  2. 所有关键字都是大小写敏感的
示例
YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# OpenAPI的版本,可用版本  3.0.0, 3.0.1, 3.0.2
openapi: 3.0.0
info:
  title: '标题'
  description: '描述'
  version: '此OAS的版本,示例0.1.9'
 
# 提供此API的服务器地址列表
servers:
  - url: http://api.example.com/v1
    description: Optional server description, e.g. Main (production) server
  - url: http://staging-api.example.com
    description: Optional server description, e.g. Internal staging server for testing
 
# API 端点列表
paths:
  # 端点 /users
  /users:
    # 端点/users的GET方法
    get:
      # 描述性信息
      summary: Returns a list of users.
      description: Optional extended description in CommonMark or HTML.
      # 响应规格说明
      responses:
        # 200响应说明
        '200':    # status code
          description: A JSON array of user names
          # 200响应是JSON格式
          content:
            application/json:
              # JSON的Schema
              schema:
                # 数组类型
                type: array
                items:
                  type: string
    # 端点/users的POST方法
    post:
      summary: Creates a user.
      # 请求体
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                username:
                  type: string
      responses:
        '201':
          description: Created
  # 端点/user/{userId}的POST方法
  # {} 中的是路径变量
  /user/{userId}:
    get:
      summary: Returns a user by ID.
      # 参数说明
      parameters:
        - name: userId
          # 这是一个路径变量
          in: path
          required: true
          description: The ID of the user to return.
          # Schema中可以包含字段的验证规则
          schema:
            type: integer
            format: int64
            minimum: 1
      responses:
        '200':
          description: A user object.
          content:
            application/json:
              schema:
                # 对象类型
                type: object
                # 包含属性声明
                properties:
                  id:
                    type: integer
                    format: int64
                    example: 4
                  name:
                    type: string
                    example: Jessica Smith
        # 异常处理
        '400':
          description: The specified user ID is invalid (not a number).
        '404':
          description: A user with the specified ID was not found.
        default:
          description: Unexpected error
Schema定义和引用
YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
components:
  # 定义了一个名为User的Schema
  schemas:
    User:
      properties:
        id:
          type: integer
        name:
          type: string
      # User包含两个属性,都是必须属性
      required:  
        - id
        - name

下面的API引用上述Schema:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
paths:
  /users/{userId}:
    get:
      summary: Returns a user by ID.
      parameters:
        - in: path
          name: userId
          required: true
          type: integer
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                # 引用Schema
                $ref: '#/components/schemas/User'
API服务器和Base URL

OAS中的所有API端点,相对于Base URL,假设Base URL为https://api.example.com/v1则端点/users的访问路径为https://api.example.com/v1/users。

Open API 3.0允许在servers数组中声明多个Base URL。

服务器URL模板

API服务器的URL中,可以包含变量:

YAML
1
2
3
4
5
6
7
8
9
10
  - url: https://{customerId}.saas-app.com:{port}/v2
    variables:
      customerId:
        default: demo
        description: Customer ID assigned by the service provider
      port:
        enum:
          - '443'
          - '8443'
        default: '443'
覆盖服务器URL

你可以在路径级别,甚至操作(方法) 级别,覆盖servers数组:

YAML
1
2
3
4
5
  /ping:
    get:
      servers:
        - url: https://echo.example.com
          description: Override base path for the GET /ping operation
服务器相对URL 

servers数组中的URL可以是相对URL,这种情况下,URL相对于提供OpenAPI定义的Web服务器。

举例来说,如果Open API定义存放在http://localhost:3001/openapi.yaml,则servers元素/v2解析为http://localhost:3001/v2 

媒体类型

请求或者响应的媒体类型,在content元素下声明:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
# 200响应,媒体类型为JSON
paths:
  /employees:
    get:
      summary: Returns a list of employees.
      responses:
        '200':      # Response
          description: OK
          content:  # Response body
            application/json:  # Media type
              schema:          # Must-have
                type: object   # Data type
多种媒体类型

可以指定多种媒体类型:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
paths:
  /employees:
    get:
      responses:
        '200':
          description: OK
          content:
            # JSON
            application/json:
             schema:
               $ref: '#/components/schemas/Employee'
            # XML
            application/xml:
             schema:
               $ref: '#/components/schemas/Employee'
通配媒体类型 

如果需要为多种媒体类型指定相同的Schema,可以使用 */*或者 application/*这样的通配符: 

YAML
1
2
3
4
5
6
7
8
9
10
11
paths:
  /info/logo:
    get:
      responses:
        '200':
          description: OK
          content:
            image/*:
             schema:
               type: string
               format: binary
路径和操作

在OAS中,路径即端点(资源),操作即HTTP方法。

路径

路径可以包括路径变量,例如:

YAML
1
2
3
/users/{id}
/organizations/{orgId}/members/{memberId}
/report.{format}

路径的description支持多行文本,还允许使用Markdown语法。

操作

OpenAPI 3.0支持的HTTP方法包括:get, post, put, patch, delete, head, options,  trace。每个路径可以支持多个操作。

OpenAPI 3.0支持通过路径、查询字符串、请求头、Cookie传递参数。对于POST/PUT/PATCH请求,还可以通过请求体传递数据。

查询字符串参数

这种参数不得定义在路径中,下面是错误的用法:

YAML
1
2
paths:
  /users?role={role}:

下面则是正确的用法:

YAML
1
2
3
4
5
6
7
8
9
10
paths:
  /users:
    get:
      parameters:
        - in: query
          name: role
          schema:
            type: string
            enum: [user, poweruser, admin]
          required: true
operationId

你可以为操作指定一个标识符:

YAML
1
2
3
/users:
  get:
    operationId: getUsers

此标识符必须在OAS中是唯一的。 operationId的使用场景包括:

  1. 某些代码生成器使用operationId生成对应的方法名
描述参数

参数需要定义在operation或path的parameters字段下。每个参数包括名字、位置、数据类型(通过schema或content定义)等属性。

参数位置

参数可以通过以下HTTP元素传递:

  1. 路径参数
  2. 查询参数
  3. 请求头参数
  4. Cookie参数 
路径参数
YAML
1
2
3
4
5
6
7
8
9
10
11
  /users/{id}:
    get:
      parameters:
        - in: path
          # 必须和路径变量{}中的一样
          name: id  
          required: true
          schema:
            type: integer
            minimum: 1
          description: The user ID

路径参数可以是数组,或者对象。这种参数在URL中的串行化形式可以是:

  1. 路径风格展开,分号分隔,示例: /map/point;x=50;y=20
  2. 标签展开,点号前缀,示例: /color.R=100.G=200.B=150
  3. 简单形式,逗号分隔,示例: /users/12,34,56

具体使用哪种串行化风格,通过style、explode关键字指定。

查询参数

这是最常见的参数形式,出现在URL的?后面。

YAML
1
2
3
4
5
6
7
8
9
     parameters:
        - in: query
          name: offset
          schema:
            type: integer
        - in: query
          name: limit
          schema:
            type: integer

RFC 3986规定 :/?#[]@!$&'()*+,;=为URI特殊字符。如果查询参数中包含这些字符,必须以%HEX形式编码。

请求头参数
YAML
1
2
3
4
5
6
7
8
9
10
11
paths:
  /ping:
    get:
      summary: Checks if the server is alive
      parameters:
        - in: header
          name: X-Request-ID
          schema:
            type: string
            format: uuid
          required: true
Cookie参数 
YAML
1
2
3
4
5
6
7
8
9
10
11
      parameters:
        - in: cookie
          name: debug
          schema:
            type: integer
            enum: [0, 1]
            default: 0
        - in: cookie
          name: csrftoken
          schema:
            type: string
参数默认值

使用default关键字可以为optional参数提供默认值:

YAML
1
2
3
4
5
6
7
8
parameters:
  - in: query
    name: offset
    schema:
      type: integer
      minimum: 0
      default: 0
    required: false 
schema和content

要描述复杂参数的内容,可以使用schema或者content。这两个关键字是互斥的,用于不同的场景下。

大部分情况下,可以考虑使用schema,使用schema可以描述原始类型、串行化为字符串的对象、简单数组。对象、数组的串行化方法在style、explode关键字中定义:

YAML
1
2
3
4
5
6
7
8
9
- in: query
  name: color
  schema:
    type: array
    items:
      type: string
  # 串行化为 color=blue,black,brown 形式
  style: form
  explode: false

style、explode无法满足的复杂串行化需求,可以使用content:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
parameters:
  - in: query
    name: filter
    content:
      # 细粒度的控制如何串行化为JSON
      application/json:
        schema:
          type: object
          properties:
            type:
              type: string
            color:
              type: string
枚举类型参数

可以为某个参数指定可选值的集合,这些值的类型必须和参数类型匹配:

YAML
1
2
3
4
5
6
7
8
9
parameters:
  - in: query
    name: status
    schema:
      type: string
      enum:
        - available
        - pending
        - sold

常量参数 —— 仅仅包含一个枚举值的参数:

YAML
1
2
3
4
5
6
7
8
parameters:
  - in: query
    name: rel_date
    required: true
    schema:
      type: string
      enum:
        - now
可空参数

OpenAPI 3.0允许仅仅有名字,而没有值的参数,在URL中表现为 /path?parm的形式:

YAML
1
2
3
4
5
6
parameters:
  - in: query
    name: metadata
    schema:
      type: boolean
    allowEmptyValue: true

你也可以在schema中声明nullable属性:

YAML
1
2
3
4
schema:
  type: integer
  format: int32
  nullable: true
废弃参数 

可以将一个参数标记为已经废弃:

YAML
1
2
3
4
5
6
7
- in: query
  name: format
  required: true
  schema:
    type: string
    enum: [json, xml, yaml]
  deprecated: true
公共参数

所谓公共参数,即一个path下,所有方法都使用的参数定义:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
/user/{id}:
  parameters:
    - in: path
      name: id
      schema:
        type: integer
      required: true
      description: The user ID
  get:
    ...
  patch:
    ...
  delete:
身份验证和授权

OpenAPI支持以多种身份验证方式保护API:

  1. 基于Authorization头的身份认证:
    1. 不记名令牌(Bearer)
    2. 基本认证
  2. 请求头、Cookie、查询字符串中的API Key
  3. OAuth2
  4. OpenID连接发现
定义securitySchemes
YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
securitySchemes:
 
  BasicAuth:
    type: http
    scheme: basic
 
  BearerAuth:
    type: http
    scheme: bearer
 
  ApiKeyAuth:
    type: apiKey
    in: header
    name: X-API-Key
 
  OpenID:
    type: openIdConnect
    openIdConnectUrl: https://example.com/.well-known/openid-configuration
 
  OAuth2:
    type: oauth2
    flows:
      authorizationCode:
        authorizationUrl: https://example.com/oauth/authorize
        tokenUrl: https://example.com/oauth/token
        scopes:
          read: Grants read access
          write: Grants write access
          admin: Grants access to admin operations
应用到方法

为方法添加security字段:

YAML
1
2
3
4
5
security:
  - ApiKeyAuth: []
  - OAuth2:
      - read
      - write 
引用

引用Schema:

YAML
1
2
schema:
  $ref: '#/components/schemas/User'

本地引用: #/components/schemas/user

远程引用:

  1. 当前服务器下其它文件: $ref: 'document.json'
  2. 文件中的元素: $ref: 'document.json#/myElement'
  3. 其它服务器中的元素: $ref:  http://path/to/your/resource.json#myElement
代码生成
openapi-generator

OpenAPITools/openapi-generator项目,用于从OpenAPI Spec(2和3版本)自动生成客户端库、服务器代码存根、文档以及配置文件。

openapi-generator广泛的支持各种常用语言和框架,对于Go来说,支持的Web框架包括Gin、Echo等。

Go
deepmap/oapi-codegen

用于生成OpenAPI 3.0的Go样板代码,以Echo为默认的Web框架。该库致力于尽量的简单化,而非通用化,它不会为所有OpenAPI Schemas生成强类型的Go代码。

默认情况下oapi-codegen会生成包括客户端、服务器、类型定义、内嵌Swagger Spec在内的所有代码。对应命令行选项
-generate=types,client,server,spec。

下面我们看看从宠物商店的OpenAPI Spec生成Go样板代码:

petstore-expanded.yaml
YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
openapi: "3.0.0"
info:
  version: 1.0.0
  title: Swagger Petstore
  description: A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification
  termsOfService: http://swagger.io/terms/
  contact:
    name: Swagger API Team
    email: apiteam@swagger.io
    url: http://swagger.io
  license:
    name: Apache 2.0
    url: https://www.apache.org/licenses/LICENSE-2.0.html
servers:
  - url: http://petstore.swagger.io/api
paths:
  /pets:
    get:
      description: |
        Returns all pets from the system that the user has access to
        Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia.
        Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien.
      operationId: findPets
      parameters:
        - name: tags
          in: query
          description: tags to filter by
          required: false
          style: form
          schema:
            type: array
            items:
              type: string
        - name: limit
          in: query
          description: maximum number of results to return
          required: false
          schema:
            type: integer
            format: int32
      responses:
        '200':
          description: pet response
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Pet'
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    post:
      description: Creates a new pet in the store. Duplicates are allowed
      operationId: addPet
      requestBody:
        description: Pet to add to the store
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewPet'
      responses:
        '200':
          description: pet response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /pets/{id}:
    get:
      description: Returns a user based on a single ID, if the user does not have access to the pet
      operationId: find pet by id
      parameters:
        - name: id
          in: path
          description: ID of pet to fetch
          required: true
          schema:
            type: integer
            format: int64
      responses:
        '200':
          description: pet response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    delete:
      description: deletes a single pet based on the ID supplied
      operationId: deletePet
      parameters:
        - name: id
          in: path
          description: ID of pet to delete
          required: true
          schema:
            type: integer
            format: int64
      responses:
        '204':
          description: pet deleted
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    Pet:
      allOf:
        - $ref: '#/components/schemas/NewPet'
        - type: object
          required:
          - id
          properties:
            id:
              type: integer
              format: int64
 
    NewPet:
      type: object
      required:
        - name  
      properties:
        name:
          type: string
        tag:
          type: string    
 
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: integer
          format: int32
        message:
          type: string

通过下面的命令生成样板代码:

Shell
1
2
go get github.com/deepmap/oapi-codegen/cmd/oapi-codegen
oapi-codegen petstore-expanded.yaml  > petstore.gen.go

 OpenAPI的 /components/schemas段中定义了可复用对象类型,oapi-codegen会生成对应的Go结构:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Type definition for component schema "Error"
type Error struct {
    Code    int32  `json:"code"`
    Message string `json:"message"`
}
 
// Type definition for component schema "NewPet"
type NewPet struct {
    Name string  `json:"name"`
    Tag  *string `json:"tag,omitempty"`
}
 
// Type definition for component schema "Pet"
type Pet struct {
    // Embedded struct due to allOf(#/components/schemas/NewPet)
    NewPet
    // Embedded fields due to inline allOf schema
    Id int64 `json:"id"`
}
 
// Type definition for component schema "Pets"
type Pets []Pet

对于在handler中定义的inline类型,只会生成内联的、匿名的Go结构。这会导致重复的代码,应该避免。

对于paths中的每一个元素,都会生成一个Go Handler函数:

Go
1
2
3
4
5
6
7
8
9
10
type ServerInterface interface {
    //  (GET /pets)
    FindPets(ctx echo.Context, params FindPetsParams) error
    //  (POST /pets)
    AddPet(ctx echo.Context) error
    //  (DELETE /pets/{id})
    DeletePet(ctx echo.Context, id int64) error
    //  (GET /pets/{id})
    FindPetById(ctx echo.Context, id int64) error
}

请求参数通过以下方式传递:

  1. 大部分情况下,函数被编解码到echo.Context中
  2. 路径变量作为Handler函数的参数
  3. 其它请求头参数、查询参数、Cookie参数被存放在params变量中,例如:
    Go
    1
    2
    3
    4
    5
    // Parameters object for FindPets
    type FindPetsParams struct {
       Tags  *[]string `json:"tags,omitempty"`
       Limit *int32   `json:"limit,omitempty"`  // 可选参数,作为指针
    }

使用命令行选项 -generate server可以为Echo生成Handlers注册函数: 

Go
1
2
3
4
5
6
7
8
9
func RegisterHandlers(router codegen.EchoRouter, si ServerInterface) {
    wrapper := ServerInterfaceWrapper{
        Handler: si,
    }
    router.GET("/pets", wrapper.FindPets)
    router.POST("/pets", wrapper.AddPet)
    router.DELETE("/pets/:id", wrapper.DeletePet)
    router.GET("/pets/:id", wrapper.FindPetById)
}

使用下面的代码注册Handlers到Echo服务器:

Go
1
2
3
4
5
6
func SetupHandler() {
    var myApi PetStoreImpl  // 这是你的服务器端实现
    e := echo.New()
    petstore.RegisterHandlers(e, &myApi)
    ...
}

类似的,使用命令行选项 -generate chi-server可以生成Chi或net/http的Handlers注册函数。

OpenAPI默认隐含additionalProperties=true,也就是说请求中提供的任何没有明确定义的字段都应该被接受。由于在Go语言中处理这种动态属性需要大量样板代码,oapi-codegen默认假设additionalProperties=false,要改变此默认行为你需要修改OpenAPI Schema:

YAML
1
2
3
4
5
6
7
8
9
10
    NewPet:
      required:
        - name
      properties:
        name:
          type: string
        tag:
          type: string
      additionalProperties:
        type: string

这样会生成如下结构:

YAML
1
2
3
4
5
6
// NewPet defines model for NewPet.
type NewPet struct {
    Name                 string            `json:"name"`
    Tag                  *string           `json:"tag,omitempty"`
    AdditionalProperties map[string]string `json:"-"`
}

使用命令行选项 -generate=client可以生成客户端代码:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// The interface specification for the client above.
type ClientInterface interface {
 
    // FindPets request
    FindPets(ctx context.Context, params *FindPetsParams, reqEditors ...RequestEditorFn) (*http.Response, error)
 
    // AddPet request with JSON body
    AddPet(ctx context.Context, body NewPet, reqEditors ...RequestEditorFn) (*http.Response, error)
 
    // DeletePet request
    DeletePet(ctx context.Context, id int64, reqEditors ...RequestEditorFn) (*http.Response, error)
 
    // FindPetById request
    FindPetById(ctx context.Context, id int64, reqEditors ...RequestEditorFn) (*http.Response, error)
}
 
 
// Client which conforms to the OpenAPI3 specification for this service.
type Client struct {
    // The endpoint of the server conforming to this interface, with scheme,
    // https://api.deepmap.com for example.
    Server string
 
    // HTTP client with any customized settings, such as certificate chains.
    Client http.Client
 
    // A callback for modifying requests which are generated before sending over
    // the network.
    RequestEditors []func(ctx context.Context, req *http.Request) error
}

每个OpenAPI Schema中定义的操作都对应了一个客户端函数。 

openapi-gen

此工具专门用于从K8S模型类生成Open API模型(以Go结构的形式存放在GetOpenAPIDefinitions函数中)。

下载并安装:

Shell
1
2
3
4
5
git clone https://github.com/kubernetes/kube-openapi.git
 
export GOPROXY=https://goproxy.io
export GO111MODULE=on
go install cmd/openapi-gen/openapi-gen.go

要为指定的包生成模型,执行命令:

Shell
1
2
3
4
5
6
            # 输入包的导入路径
openapi-gen -i git.pacloud.io/pks/helm-operator/pkg/apis/pks/v1
            # 输出包的导入路径
            -p git.pacloud.io/pks/helm-operator/pkg/apis/pks/v1
            # 生成的函数所在文件的名称前缀
            -O zz_generated.openapi

你的CRD通常会引用K8S核心库中的模型,因此需要同时为它们生成模型:

Shell
1
2
openapi-gen -i k8s.io/api/core/v1 -p git.pacloud.io/pks/helm-operator/pkg/apis/core/v1
openapi-gen -i k8s.io/apimachinery/pkg/apis/meta/v1 -p git.pacloud.io/pks/helm-operator/pkg/apis/meta/v1

使用Operator Framework开发CRD的控制器时,你可以使用注释 +k8s:openapi-gen=true来生成Open API模型,生成的Schema示例如下:

生成Swagger Spec

下面的代码调用GetOpenAPIDefinitions函数,生成Swagger 2.0.0 API定义的JSON:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package main
 
import (
    "encoding/json"
    "fmt"
    pksv1 "git.pacloud.io/pks/helm-operator/pkg/apis/pks/v1"
    "github.com/go-openapi/spec"
    "k8s.io/kube-openapi/pkg/common"
    "log"
    "os"
    "strings"
)
 
func main() {
    if len(os.Args) <= 1 {
        log.Fatal("version required")
    }
    version := os.Args[1]
    if !strings.HasPrefix(version, "v") {
        version = "v" + version
    }
    oAPIDefs := pksv1.GetOpenAPIDefinitions(func(name string) spec.Ref {
        return spec.MustCreateRef("#/definitions/" + common.EscapeJsonPointer(swaggify(name)))
    })
    defs := spec.Definitions{}
    for defName, val := range oAPIDefs {
        defs[swaggify(defName)] = val.Schema
    }
 
    swagger := spec.Swagger{
        SwaggerProps: spec.SwaggerProps{
            Swagger:     "2.0",
            Definitions: defs,
            Paths:       &spec.Paths{Paths: map[string]spec.PathItem{}},
            Info: &spec.Info{
                InfoProps: spec.InfoProps{
                    Title:   "Helm Release",
                    Version: version,
                },
            },
        },
    }
    jsonBytes, err := json.MarshalIndent(swagger, "", "  ")
    if err != nil {
        log.Fatal(err.Error())
    }
    fmt.Println(string(jsonBytes))
}
 
func swaggify(name string) string {
    name = strings.Replace(name, "git.pacloud.io/pks/helm-operator/pkg/apis", "yun.gmem.cc", -1)
    parts := strings.Split(name, "/")
    hostParts := strings.Split(parts[0], ".")
    for i, j := 0, len(hostParts)-1; i < j; i, j = i+1, j-1 {
        hostParts[i], hostParts[j] = hostParts[j], hostParts[i]
    }
    parts[0] = strings.Join(hostParts, ".")
    return strings.Join(parts, ".")
}

生成的Swagger API,仅仅包含模型信息,也就是definitions字段,但是不包含API端点(paths)。

如果需要生成API端点,则需要启动一个API Server并将需要生成API端点的模型注册进去:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
// k8s.io/*包的API非常不稳定,本样例基于kubernetes 1.13.9
package main
 
import (
    "context"
    "encoding/json"
    "flag"
    "fmt"
    pkscorev1 "git.pacloud.io/pks/helm-operator/pkg/apis/core/v1"
    pksmetav1 "git.pacloud.io/pks/helm-operator/pkg/apis/meta/v1"
    pksv1 "git.pacloud.io/pks/helm-operator/pkg/apis/pks/v1"
    "github.com/go-openapi/spec"
    metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/runtime/schema"
    "k8s.io/apimachinery/pkg/runtime/serializer"
    "k8s.io/apimachinery/pkg/watch"
    openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
    "k8s.io/apiserver/pkg/registry/rest"
    genericapiserver "k8s.io/apiserver/pkg/server"
    genericoptions "k8s.io/apiserver/pkg/server/options"
    "k8s.io/klog"
    "k8s.io/kube-openapi/pkg/builder"
    "k8s.io/kube-openapi/pkg/common"
    "net"
    "os"
)
 
// 这个Storage是资源CRUD操作的提供者
type StandardStorage struct {
    cfg ResourceInfo
}
 
// 强制它实现以下接口
var _ rest.GroupVersionKindProvider = &StandardStorage{}
var _ rest.Scoper = &StandardStorage{}
var _ rest.StandardStorage = &StandardStorage{}
 
// GroupVersionKindProvider
func (r *StandardStorage) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind {
    return r.cfg.gvk
}
 
// Scoper
func (r *StandardStorage) NamespaceScoped() bool {
    return r.cfg.namespaceScoped
}
 
// Getter
func (r *StandardStorage) New() runtime.Object {
    return r.cfg.obj
}
 
func (r *StandardStorage) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
    return r.New(), nil
}
 
func (r *StandardStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
    return r.New(), nil
}
 
// Lister
func (r *StandardStorage) NewList() runtime.Object {
    return r.cfg.list
}
 
func (r *StandardStorage) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
    return r.NewList(), nil
}
 
// CreaterUpdater
func (r *StandardStorage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
    return r.New(), true, nil
}
 
// GracefulDeleter
func (r *StandardStorage) Delete(ctx context.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
    return r.New(), true, nil
}
 
// CollectionDeleter
func (r *StandardStorage) DeleteCollection(ctx context.Context, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) {
    return r.NewList(), nil
}
 
// Watcher
func (r *StandardStorage) Watch(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) {
    return nil, nil
}
 
type ResourceInfo struct {
    gvk             schema.GroupVersionKind
    obj             runtime.Object
    list            runtime.Object
    namespaceScoped bool
}
 
type TypeInfo struct {
    GroupVersion    schema.GroupVersion
    Resource        string
    Kind            string
    NamespaceScoped bool
}
type Config struct {
    Scheme             *runtime.Scheme
    Codecs             serializer.CodecFactory
    Info               spec.InfoProps
    OpenAPIDefinitions []common.GetOpenAPIDefinitions
    Resources          []TypeInfo
}
 
func (c *Config) GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
    out := map[string]common.OpenAPIDefinition{}
    for _, def := range c.OpenAPIDefinitions {
        for k, v := range def(ref) {
            out[k] = v
        }
    }
    return out
}
func RenderOpenAPISpec(cfg Config) (string, error) {
 
    // 总是需要向Scheme注册corev1、metav1
    metav1.AddToGroupVersion(cfg.Scheme, schema.GroupVersion{Version: "v1"})
    unversioned := schema.GroupVersion{Group: "", Version: "v1"}
    cfg.Scheme.AddUnversionedTypes(unversioned,
        &metav1.Status{},
        &metav1.APIVersions{},
        &metav1.APIGroupList{},
        &metav1.APIGroup{},
        &metav1.APIResourceList{},
    )
 
    // API Server选项
    options := genericoptions.NewRecommendedOptions("/registry/pks.yun.gmem.cc", cfg.Codecs.LegacyCodec(), &genericoptions.ProcessInfo{})
    options.SecureServing.BindPort = 6445
    options.Etcd = nil
    options.Authentication = nil
    options.Authorization = nil
    options.CoreAPI = nil
    options.Admission = nil
    // 自动生成的服务器证书的存放目录
    options.SecureServing.ServerCert.CertDirectory = "/tmp/helm-operator"
 
    // 启动的API Server,监听的地址
    publicAddr := "localhost"
    ips := []net.IP{net.ParseIP("127.0.0.1")}
 
    // 尝试自动生成服务器证书
    if err := options.SecureServing.MaybeDefaultWithSelfSignedCerts(publicAddr, nil, ips); err != nil {
        klog.Fatal(err)
    }
 
    // API Server配置
    serverConfig := genericapiserver.NewRecommendedConfig(cfg.Codecs)
    if err := options.ApplyTo(serverConfig, cfg.Scheme); err != nil {
        return "", err
    }
    serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(cfg.GetOpenAPIDefinitions, openapinamer.NewDefinitionNamer(cfg.Scheme))
    serverConfig.OpenAPIConfig.Info.InfoProps = cfg.Info
    //                             完成配置,  创建服务器
    genericServer, err := serverConfig.Complete().New("helm-operator-server", genericapiserver.NewEmptyDelegate())
    if err != nil {
        return "", err
    }
 
    // 这里处理本服务器需要Serve的资源列表
    table := map[schema.GroupVersion]map[string]rest.Storage{}
    {
        for _, ti := range cfg.Resources {
            // 对于每一种资源,根据其GV寻找存储库
            var resmap map[string]rest.Storage
            if m, found := table[ti.GroupVersion]; found {
                resmap = m
            } else {
                // 如果找不到,则创建存储库(每个GV一个存储库)
                resmap = map[string]rest.Storage{}
                table[ti.GroupVersion] = resmap
            }
 
            gvk := ti.GroupVersion.WithKind(ti.Kind)
            // 创建这种资源的一个对象
            obj, err := cfg.Scheme.New(gvk)
            if err != nil {
                return "", err
            }
            // 创建这种资源的列表对象
            list, err := cfg.Scheme.New(ti.GroupVersion.WithKind(ti.Kind + "List"))
            if err != nil {
                return "", err
            }
 
            // 为资源创建存储,并Put到它的GV的存储库中
            resmap[ti.Resource] = &StandardStorage{ResourceInfo{
                // GVK信息
                gvk: gvk,
                // 资源和资源列表的原型
                obj:  obj,
                list: list,
                // 提示此资源是否命名空间化
                namespaceScoped: ti.NamespaceScoped,
            }}
        }
    }
    for gv, resmap := range table {
        // 为每个组创建API组信息
        apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(gv.Group, cfg.Scheme, metav1.ParameterCodec, cfg.Codecs)
        storage := map[string]rest.Storage{}
        for r, s := range resmap {
            storage[r] = s
        }
        apiGroupInfo.VersionedResourcesStorageMap[gv.Version] = storage
 
        // 并安装到此服务器
        if err := genericServer.InstallAPIGroup(&apiGroupInfo); err != nil {
            return "", err
        }
    }
    // 构件API规范,也就是Swagger 2.0 Spec
    spec, err := builder.BuildOpenAPISpec(genericServer.Handler.GoRestfulContainer.RegisteredWebServices(), serverConfig.OpenAPIConfig)
    if err != nil {
        return "", err
    }
    data, err := json.MarshalIndent(spec, "", "  ")
    if err != nil {
        return "", err
    }
    return string(data), nil
}
 
func main() {
    flag.Parse()
    if len(os.Args) <= 1 {
        panic("version required")
    }
    version := os.Args[1]
 
    scheme := runtime.NewScheme()
    // 将我们需要处理的类型加入到scheme
    scheme.AddKnownTypeWithName(schema.GroupVersion{Group: "pks.yun.gmem.cc", Version: "v1"}.WithKind("Release"), &pksv1.Release{})
    scheme.AddKnownTypeWithName(schema.GroupVersion{Group: "pks.yun.gmem.cc", Version: "v1"}.WithKind("ReleaseList"), &pksv1.Release{})
    scheme.AddKnownTypeWithName(schema.GroupVersion{Group: "pks.yun.gmem.cc", Version: "v1"}.WithKind("WatchEvent"), &pksv1.Release{})
 
    spec, err := RenderOpenAPISpec(Config{
        Info: spec.InfoProps{
            Version: version,
            Title:   "Helm Operator OpenAPI",
        },
        Scheme: scheme,
        Codecs: serializer.NewCodecFactory(scheme),
        OpenAPIDefinitions: []common.GetOpenAPIDefinitions{
            pkscorev1.GetOpenAPIDefinitions,
            pksmetav1.GetOpenAPIDefinitions,
            pksv1.GetOpenAPIDefinitions,
        },
        Resources: []TypeInfo{
            {
                GroupVersion:    schema.GroupVersion{Group: "pks.yun.gmem.cc", Version: "v1"},
                Kind:            "Release",
                Resource:        "Release",
                NamespaceScoped: true,
            },
        },
    })
    if err != nil {
        klog.Fatal(err.Error())
    }
    fmt.Println(spec)
}
Java
Gradle

可以使用下面的插件:

Groovy
1
2
3
plugins {
  id 'org.hidetake.swagger.generator' version '2.18.1'
}

根据API规格的版本,选择依赖:

Groovy
1
2
3
4
5
dependencies {
  swaggerCodegen 'io.swagger:swagger-codegen-cli:2.4.2'             // Swagger Codegen V2
  swaggerCodegen 'io.swagger.codegen.v3:swagger-codegen-cli:3.0.5'  // Swagger Codegen V3
  swaggerCodegen 'org.openapitools:openapi-generator-cli:3.3.4'     // penAPI Generator
}

控制代码生成的配置片段:

Groovy
1
2
3
4
5
6
7
8
9
swaggerSources {
  petstore {
    inputFile = file('petstore.yaml')
    code {
      language = 'spring'
      configFile = file('config.json')
    }
  }
}

执行命令: ./gradlew generateSwaggerCode,将自动生成代码到 build/swagger-code-petstore目录。

configFile字段可以指定一个外部(本质上就是 swagger-codegen-cli generate的)配置文件,格式如下:

Groovy
1
2
3
4
5
6
{
  "library": "spring-mvc",
  "modelPackage": "example.model",
  "apiPackage": "example.api",
  "invokerPackage": "example"
}

对于language=java,常用配置项列表:

配置项 说明
sortParamsByRequiredFlag 排序风发参数,将必须参数放在前面
ensureUniqueParams 是否一个操作内,保证参数名的唯一性
allowUnicodeIdentifiers 是否允许Unicode标识符
modelPackage 生成的模型类的包名
apiPackage 生成的API类的包名
invokerPackage 生成的代码的根包名
groupId 生成的POM的信息
artifactId
artifactVersion
artifactDescription
sourceFolder 源码目录
localVariablePrefix 局部变量前缀
serializableModel 生成的模型是否实现Serializable接口
fullJavaUtil 如果生成Java代码,那么是否用全限定名称引用java.util下的类
withXml 生成的类是否添加序列化为XML的支持
dateLibrary

Java日期库:

joda 用于遗留应用
legacy 使用java.util.Date,用于遗留应用
java8-localdatetime   使用LocalDateTime,用于遗留应用
java8 使用Java 8原生的JSR310
threetenbp  低版本的JSR310垫片

java8 使用Java 8提供的API
disableHtmlEscaping 使用JSON时禁止HTML转译,如果使用byte[]字段应该设置为true
useGzipFeature 是否使用Gzip编码请求
useRuntimeException 是否使用RuntimeException代替Exception
library

库模板,对于language=java,默认okhttp-gson。可选:

jersey1,基于JacksonJSON串行化
jersey2,基于JacksonJSON串行化
feign,基于JacksonJSON串行化
okhttp-gson,基于GSON串行化
retrofit2 基于OKHttp 3.x,基于GSON串行化
resttemplate 基于Spring RestTemplate,基于JacksonJSON串行化

  

← Maven插件开发
日出•印象 →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • gRPC学习笔记
  • 基于Kurento搭建WebRTC服务器
  • SOFAStack学习笔记
  • Protocol Buffers初探
  • Eclipse 4.3.2开发环境搭建

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • Bazel学习笔记 37 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • Ceph学习笔记 27 people like this
  • 基于Calico的CNI 27 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2