<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; Swagger</title>
	<atom:link href="https://blog.gmem.cc/tag/swagger/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Tue, 21 Apr 2026 10:40:56 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>OpenAPI学习笔记</title>
		<link>https://blog.gmem.cc/openapi</link>
		<comments>https://blog.gmem.cc/openapi#comments</comments>
		<pubDate>Fri, 12 Jul 2019 09:08:45 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Go]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Work]]></category>
		<category><![CDATA[OpenAPI]]></category>
		<category><![CDATA[Swagger]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=28075</guid>
		<description><![CDATA[<p>简介 OpenAPI是一套API规范（ OpenAPI Specification ，OAS），用于定义RESTful API的接口。OpenAPI最初来自SmartBear的Swagger规范。 OpenAPI 目前的版本是3.0，当前Swagger和OpenAPI的关系是： OpenAPI是一套规范 Swagger是实现OpenAPI规范的工具集，包括： Swagger Editor：允许你使用YAML语言在浏览器中编写规范，并实时查看生成的API文档 Swagger UI：从OAS兼容的API动态生成一套Web的美观的API文档 Swagger Codegen：用于生成OpenAPI的客户端库（SDK）、服务器桩代码、文档 Swagger Parser：从Java解析Open API定义的独立库 Swagger Core：一个Java库，用于创建、消费、使用OpenAPI定义 Swagger <a class="read-more" href="https://blog.gmem.cc/openapi">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/openapi">OpenAPI学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>OpenAPI是一套API规范（ OpenAPI Specification ，OAS），用于定义RESTful API的接口。OpenAPI<span style="background-color: #c0c0c0;">最初来自SmartBear的Swagger规范</span>。</p>
<p>OpenAPI 目前的版本是3.0，当前Swagger和OpenAPI的关系是：</p>
<ol>
<li>OpenAPI是一套规范</li>
<li>Swagger是实现OpenAPI规范的工具集，包括：
<ol>
<li>Swagger Editor：允许你使用YAML语言在浏览器中编写规范，并实时查看生成的API文档</li>
<li>Swagger UI：从OAS兼容的API动态生成一套Web的美观的API文档</li>
<li>Swagger Codegen：用于生成OpenAPI的客户端库（SDK）、服务器桩代码、文档</li>
<li>Swagger Parser：从Java解析Open API定义的独立库</li>
<li>Swagger Core：一个Java库，用于创建、消费、使用OpenAPI定义</li>
<li>Swagger Inspector：从现有的API生成OpenAPI定义、验证API的测试工具</li>
<li>SwaggerHub：OpenAPI的API设计、文档平台</li>
</ol>
</li>
</ol>
<p>Swagger并非唯一支持OpenAPI的工具，到<a href="https://github.com/OAI/OpenAPI-Specification/blob/master/IMPLEMENTATIONS.md">OpenAPI-Specification</a>的GitHub页面可以看到相关工具的列表。</p>
<div class="blog_h1"><span class="graybg">Swagger 2.0</span></div>
<div class="blog_h2"><span class="graybg">API示例</span></div>
<pre class="crayon-plain-tag">{
  // 基本信息
  "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"
        }
      ]
    }
  }
}</pre>
<div class="blog_h2"><span class="graybg">转换为OAS3.0</span></div>
<div class="blog_h3"><span class="graybg">swagger2openapi</span></div>
<p>这是一个Node.js开发的工具，可以将Swagger 2.0转换为OAS 3.0，执行下面的命令安装：</p>
<pre class="crayon-plain-tag">npm install -g swagger2openapi</pre>
<p>调用格式：<pre class="crayon-plain-tag">swagger2openapi source-spec.json [options]</pre></p>
<p>常用选项说明：</p>
<pre class="crayon-plain-tag">--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</pre>
<div class="blog_h2"><span class="graybg">API文档生成</span></div>
<p>通过某些工具，可以从既有代码中生成Swagger API文档。</p>
<div class="blog_h3"><span class="graybg">swag</span></div>
<p><a href="https://github.com/swaggo/swag">该工具</a>能够将Go Annotations转换为Swagger 2.0文档，为多种流行的Go Web框架（例如Gin）提供了插件，从而快速和既有Web项目集成。</p>
<p>执行下面的命令安装到GOPATH下：</p>
<pre class="crayon-plain-tag">go get -u github.com/swaggo/swag/cmd/swag</pre>
<p>在项目根目录，使用<pre class="crayon-plain-tag">swag init</pre>命令可以解析Go代码中的注解并且生成docs目录、docs/docs.go。你需要提供General API annotations，如果这些注解没有存放在根目录的main.go文件中，需要用<pre class="crayon-plain-tag">-g</pre>来指定Go文件路径：</p>
<pre class="crayon-plain-tag">swag init -g pkg/route/routers.go</pre>
<p>使用swag init命令之后，你需要导入生成的docs包以及swaggo的另外两个包：</p>
<pre class="crayon-plain-tag">_ "github.com/gmemcc/myproject/docs"
import "github.com/swaggo/gin-swagger" // gin-swagger middleware
import "github.com/swaggo/files" // swagger embed files</pre>
<p>General API annotations可用字段：</p>
<pre class="crayon-plain-tag">// @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"}</pre>
<p>示例：</p>
<pre class="crayon-plain-tag">// @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</pre>
<p>swag init生成的docs包，导出了变量<pre class="crayon-plain-tag">SwaggerInfo</pre>，通过此变量你可以编程式的设置各种字段（和上面这种注释方式等效）：</p>
<pre class="crayon-plain-tag">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"}</pre>
<p>为了在当前Web服务（的HTTP服务器）中查看API文档，需要注册路由，以gin为例： </p>
<pre class="crayon-plain-tag">r := gin.New()

	// use ginSwagger middleware to serve the API docs
	r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

	r.Run()
}</pre>
<p>要生成API，你需要在控制器代码中添加 API Operation annotations。这些注解需要加在controller代码中，所谓controller代码，就是包含所有路由处理函数的包，以gin为例：</p>
<pre class="crayon-plain-tag">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)
}</pre>
<p>这些注解仅仅影响生成的Swagger API文档，不会对Web服务的逻辑产生任何影响。</p>
<p>运行Web服务，可以在http://localhost:port/swagger/index.html查看Swagger 2.0 API文档。访问/swagger/doc.json可以获得JSON格式的API</p>
<div class="blog_h1"><span class="graybg">OAS</span></div>
<p>OAS是REST API的描述格式，在一个OpenAPI文件中，你可以定义完整的接口规格，包括：</p>
<ol>
<li>可用API端点列表，针对每个端点允许的操作</li>
<li>操作的输入、输出参数</li>
<li>身份验证方法</li>
<li>附属信息，包括使用条款、License</li>
</ol>
<p>关于OAS的编写，需要注意：</p>
<ol>
<li>可以用YAML、JSON两种格式编写</li>
<li>所有关键字都是大小写敏感的</li>
</ol>
<div class="blog_h2"><span class="graybg">示例</span></div>
<pre class="crayon-plain-tag"># 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</pre>
<div class="blog_h2"><span class="graybg">Schema定义和引用</span></div>
<pre class="crayon-plain-tag">components:
  # 定义了一个名为User的Schema
  schemas:
    User:
      properties:
        id:
          type: integer
        name:
          type: string
      # User包含两个属性，都是必须属性
      required:  
        - id
        - name</pre>
<p>下面的API引用上述Schema：</p>
<pre class="crayon-plain-tag">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'</pre>
<div class="blog_h2"><span class="graybg">API服务器和Base URL</span></div>
<p>OAS中的所有API端点，相对于Base URL，假设Base URL为https://api.example.com/v1则端点/users的访问路径为https://api.example.com/v1/users。</p>
<p>Open API 3.0允许在servers数组中声明多个Base URL。</p>
<div class="blog_h3"><span class="graybg">服务器URL模板</span></div>
<p>API服务器的URL中，可以包含变量：</p>
<pre class="crayon-plain-tag">- 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'</pre>
<div class="blog_h3"><span class="graybg">覆盖服务器URL</span></div>
<p>你可以在路径级别，甚至操作（方法） 级别，覆盖servers数组：</p>
<pre class="crayon-plain-tag">/ping:
    get:
      servers:
        - url: https://echo.example.com
          description: Override base path for the GET /ping operation</pre>
<div class="blog_h3"><span class="graybg">服务器相对URL </span></div>
<p>servers数组中的URL可以是相对URL，这种情况下，URL相对于提供OpenAPI定义的Web服务器。</p>
<p>举例来说，如果Open API定义存放在http://localhost:3001/openapi.yaml，则servers元素/v2解析为http://localhost:3001/v2 </p>
<div class="blog_h2"><span class="graybg">媒体类型</span></div>
<p>请求或者响应的媒体类型，在content元素下声明：</p>
<pre class="crayon-plain-tag"># 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</pre>
<div class="blog_h3"><span class="graybg">多种媒体类型</span></div>
<p>可以指定多种媒体类型：</p>
<pre class="crayon-plain-tag">paths:
  /employees:
    get:
      responses:
        '200':
          description: OK
          content:
            # JSON
            application/json:
             schema: 
               $ref: '#/components/schemas/Employee'
            # XML
            application/xml:
             schema: 
               $ref: '#/components/schemas/Employee'</pre>
<div class="blog_h3"><span class="graybg">通配媒体类型 </span></div>
<p>如果需要为多种媒体类型指定相同的Schema，可以使用<pre class="crayon-plain-tag">*/*</pre>或者<pre class="crayon-plain-tag">application/*</pre>这样的通配符： </p>
<pre class="crayon-plain-tag">paths:
  /info/logo:
    get:
      responses:
        '200':
          description: OK
          content:
            image/*:
             schema: 
               type: string
               format: binary</pre>
<div class="blog_h2"><span class="graybg">路径和操作</span></div>
<p>在OAS中，路径即端点（资源），操作即HTTP方法。</p>
<div class="blog_h3"><span class="graybg">路径</span></div>
<p>路径可以包括路径变量，例如：</p>
<pre class="crayon-plain-tag">/users/{id}
/organizations/{orgId}/members/{memberId}
/report.{format}</pre>
<p>路径的description支持多行文本，还允许使用Markdown语法。</p>
<div class="blog_h3"><span class="graybg">操作</span></div>
<p>OpenAPI 3.0支持的HTTP方法包括：get, post, put, patch, delete, head, options,  trace。每个路径可以支持多个操作。</p>
<p>OpenAPI 3.0支持通过路径、查询字符串、请求头、Cookie传递参数。对于POST/PUT/PATCH请求，还可以通过请求体传递数据。</p>
<div class="blog_h3"><span class="graybg">查询字符串参数</span></div>
<p>这种参数不得定义在路径中，下面是错误的用法：</p>
<pre class="crayon-plain-tag">paths:
  /users?role={role}:</pre>
<p>下面则是正确的用法：</p>
<pre class="crayon-plain-tag">paths:
  /users:
    get:
      parameters:
        - in: query
          name: role
          schema:
            type: string
            enum: [user, poweruser, admin]
          required: true</pre>
<div class="blog_h3"><span class="graybg">operationId</span></div>
<p>你可以为操作指定一个标识符：</p>
<pre class="crayon-plain-tag">/users:
  get:
    operationId: getUsers</pre>
<p>此标识符必须在OAS中是唯一的。 operationId的使用场景包括：</p>
<ol>
<li>某些代码生成器使用operationId生成对应的方法名</li>
</ol>
<div class="blog_h2"><span class="graybg">描述参数</span></div>
<p>参数需要定义在operation或path的parameters字段下。每个参数包括名字、位置、数据类型（通过schema或content定义）等属性。</p>
<div class="blog_h3"><span class="graybg">参数位置</span></div>
<p>参数可以通过以下HTTP元素传递：</p>
<ol>
<li>路径参数</li>
<li>查询参数</li>
<li>请求头参数</li>
<li>Cookie参数 </li>
</ol>
<div class="blog_h3"><span class="graybg">路径参数</span></div>
<pre class="crayon-plain-tag">/users/{id}:
    get:
      parameters:
        - in: path
          # 必须和路径变量{}中的一样
          name: id   
          required: true
          schema:
            type: integer
            minimum: 1
          description: The user ID</pre>
<p>路径参数可以是数组，或者对象。这种参数在URL中的串行化形式可以是：</p>
<ol>
<li>路径风格展开，分号分隔，示例：<pre class="crayon-plain-tag">/map/point;x=50;y=20</pre></li>
<li>标签展开，点号前缀，示例：<pre class="crayon-plain-tag">/color.R=100.G=200.B=150</pre></li>
<li>简单形式，逗号分隔，示例：<pre class="crayon-plain-tag">/users/12,34,56</pre></li>
</ol>
<p>具体使用哪种串行化风格，通过style、explode关键字指定。</p>
<div class="blog_h3"><span class="graybg">查询参数</span></div>
<p>这是最常见的参数形式，出现在URL的?后面。</p>
<pre class="crayon-plain-tag">parameters:
        - in: query
          name: offset
          schema:
            type: integer
        - in: query
          name: limit
          schema:
            type: integer</pre>
<p>RFC 3986规定<pre class="crayon-plain-tag">:/?#[]@!$&amp;'()*+,;=</pre>为URI特殊字符。如果查询参数中包含这些字符，必须以%HEX形式编码。</p>
<div class="blog_h3"><span class="graybg">请求头参数</span></div>
<pre class="crayon-plain-tag">paths:
  /ping:
    get:
      summary: Checks if the server is alive
      parameters:
        - in: header
          name: X-Request-ID
          schema:
            type: string
            format: uuid
          required: true</pre>
<div class="blog_h3"><span class="graybg">Cookie参数 </span></div>
<pre class="crayon-plain-tag">parameters:
        - in: cookie
          name: debug
          schema:
            type: integer
            enum: [0, 1]
            default: 0
        - in: cookie
          name: csrftoken
          schema:
            type: string</pre>
<div class="blog_h3"><span class="graybg">参数默认值</span></div>
<p>使用default关键字可以为optional参数提供默认值：</p>
<pre class="crayon-plain-tag">parameters:
  - in: query
    name: offset
    schema:
      type: integer
      minimum: 0
      default: 0
    required: false </pre>
<div class="blog_h3"><span class="graybg">schema和content</span></div>
<p>要描述复杂参数的内容，可以使用schema或者content。这两个关键字是互斥的，用于不同的场景下。</p>
<p>大部分情况下，可以考虑使用schema，使用schema可以描述原始类型、串行化为字符串的对象、简单数组。对象、数组的串行化方法在style、explode关键字中定义：</p>
<pre class="crayon-plain-tag">- in: query
  name: color
  schema:
    type: array
    items:
      type: string
  # 串行化为 color=blue,black,brown 形式
  style: form
  explode: false</pre>
<p>style、explode无法满足的复杂串行化需求，可以使用content：</p>
<pre class="crayon-plain-tag">parameters:
  - in: query
    name: filter
    content:
      # 细粒度的控制如何串行化为JSON
      application/json:
        schema:
          type: object
          properties:
            type:
              type: string
            color:
              type: string</pre>
<div class="blog_h3"><span class="graybg">枚举类型参数</span></div>
<p>可以为某个参数指定可选值的集合，这些值的类型必须和参数类型匹配：</p>
<pre class="crayon-plain-tag">parameters:
  - in: query
    name: status
    schema:
      type: string
      enum:
        - available
        - pending
        - sold</pre>
<p><span class="graybg">常量参数 —— 仅仅包含一个枚举值的参数</span>：</p>
<pre class="crayon-plain-tag">parameters:
  - in: query
    name: rel_date
    required: true
    schema:
      type: string
      enum:
        - now</pre>
<div class="blog_h3"><span class="graybg">可空参数</span></div>
<p>OpenAPI 3.0允许仅仅有名字，而没有值的参数，在URL中表现为<pre class="crayon-plain-tag">/path?parm</pre>的形式：</p>
<pre class="crayon-plain-tag">parameters:
  - in: query
    name: metadata
    schema:
      type: boolean
    allowEmptyValue: true</pre>
<p>你也可以在schema中声明nullable属性：</p>
<pre class="crayon-plain-tag">schema:
  type: integer
  format: int32
  nullable: true</pre>
<div class="blog_h3"><span class="graybg">废弃参数 </span></div>
<p>可以将一个参数标记为已经废弃：</p>
<pre class="crayon-plain-tag">- in: query
  name: format
  required: true
  schema:
    type: string
    enum: [json, xml, yaml]
  deprecated: true</pre>
<div class="blog_h3"><span class="graybg">公共参数</span></div>
<p>所谓公共参数，即一个path下，所有方法都使用的参数定义：</p>
<pre class="crayon-plain-tag">/user/{id}:
  parameters:
    - in: path
      name: id
      schema:
        type: integer
      required: true
      description: The user ID
  get:
    ...
  patch:
    ...
  delete:</pre>
<div class="blog_h2"><span class="graybg">身份验证和授权</span></div>
<p>OpenAPI支持以多种身份验证方式保护API：</p>
<ol>
<li>基于Authorization头的身份认证：
<ol>
<li>不记名令牌（Bearer）</li>
<li>基本认证</li>
</ol>
</li>
<li>请求头、Cookie、查询字符串中的API Key</li>
<li>OAuth2</li>
<li>OpenID连接发现</li>
</ol>
<div class="blog_h3"><span class="graybg">定义securitySchemes</span></div>
<pre class="crayon-plain-tag">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</pre>
<div class="blog_h3"><span class="graybg">应用到方法</span></div>
<p>为方法添加security字段：</p>
<pre class="crayon-plain-tag">security:
  - ApiKeyAuth: []
  - OAuth2:
      - read
      - write </pre>
<div class="blog_h2"><span class="graybg">引用</span></div>
<p>引用Schema：</p>
<pre class="crayon-plain-tag">schema: 
  $ref: '#/components/schemas/User'</pre>
<p>本地引用：<pre class="crayon-plain-tag">#/components/schemas/user</pre></p>
<p>远程引用：</p>
<ol>
<li>当前服务器下其它文件：<pre class="crayon-plain-tag">$ref: 'document.json'</pre></li>
<li>文件中的元素：<pre class="crayon-plain-tag">$ref: 'document.json#/myElement'</pre></li>
<li>其它服务器中的元素：<pre class="crayon-plain-tag">$ref:  http://path/to/your/resource.json#myElement</pre></li>
</ol>
<div class="blog_h1"><span class="graybg">代码生成</span></div>
<div class="blog_h2"><span class="graybg">openapi-generator</span></div>
<p><a href="https://github.com/OpenAPITools/openapi-generator">OpenAPITools/openapi-generator</a>项目，用于从OpenAPI Spec（2和3版本）自动生成客户端库、服务器代码存根、文档以及配置文件。</p>
<p>openapi-generator广泛的支持各种常用语言和框架，对于Go来说，支持的Web框架包括Gin、Echo等。</p>
<div class="blog_h2"><span class="graybg">Go</span></div>
<div class="blog_h3"><span class="graybg">deepmap/oapi-codegen</span></div>
<p>用于生成OpenAPI 3.0的Go样板代码，以Echo为默认的Web框架。该库致力于尽量的简单化，而非通用化，它不会为所有OpenAPI Schemas生成强类型的Go代码。</p>
<p>默认情况下oapi-codegen会生成包括客户端、服务器、类型定义、内嵌Swagger Spec在内的所有代码。对应命令行选项<br /><pre class="crayon-plain-tag">-generate=types,client,server,spec</pre>。</p>
<p>下面我们看看从宠物商店的OpenAPI Spec生成Go样板代码：</p>
<pre class="crayon-plain-tag">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</pre>
<p>通过下面的命令生成样板代码：</p>
<pre class="crayon-plain-tag">go get github.com/deepmap/oapi-codegen/cmd/oapi-codegen
oapi-codegen petstore-expanded.yaml  &gt; petstore.gen.go</pre>
<p> OpenAPI的<pre class="crayon-plain-tag">/components/schemas</pre>段中定义了可复用对象类型，oapi-codegen会生成对应的Go结构：</p>
<pre class="crayon-plain-tag">// 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</pre>
<p>对于在handler中定义的inline类型，只会生成内联的、匿名的Go结构。这会导致重复的代码，应该避免。</p>
<p>对于paths中的每一个元素，都会生成一个Go Handler函数：</p>
<pre class="crayon-plain-tag">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
}</pre>
<p>请求参数通过以下方式传递：</p>
<ol>
<li>大部分情况下，函数被编解码到echo.Context中</li>
<li>路径变量作为Handler函数的参数</li>
<li>其它请求头参数、查询参数、Cookie参数被存放在params变量中，例如：<br />
<pre class="crayon-plain-tag">// Parameters object for FindPets
type FindPetsParams struct {
   Tags  *[]string `json:"tags,omitempty"`
   Limit *int32   `json:"limit,omitempty"`  // 可选参数，作为指针
}</pre>
</li>
</ol>
<p>使用命令行选项<pre class="crayon-plain-tag">-generate server</pre>可以为Echo生成Handlers注册函数： </p>
<pre class="crayon-plain-tag">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)
}</pre>
<p>使用下面的代码注册Handlers到Echo服务器：</p>
<pre class="crayon-plain-tag">func SetupHandler() {
    var myApi PetStoreImpl  // 这是你的服务器端实现
    e := echo.New()
    petstore.RegisterHandlers(e, &amp;myApi)
    ...
}</pre>
<p>类似的，使用命令行选项<pre class="crayon-plain-tag">-generate chi-server</pre>可以生成Chi或net/http的Handlers注册函数。</p>
<p>OpenAPI默认隐含additionalProperties=true，也就是说请求中提供的任何没有明确定义的字段都应该被接受。由于在Go语言中处理这种动态属性需要大量样板代码，oapi-codegen默认假设additionalProperties=false，要改变此默认行为你需要修改OpenAPI Schema：</p>
<pre class="crayon-plain-tag">NewPet:
      required:
        - name
      properties:
        name:
          type: string
        tag:
          type: string
      additionalProperties:
        type: string</pre>
<p>这样会生成如下结构：</p>
<pre class="crayon-plain-tag">// NewPet defines model for NewPet.
type NewPet struct {
	Name                 string            `json:"name"`
	Tag                  *string           `json:"tag,omitempty"`
	AdditionalProperties map[string]string `json:"-"`
}</pre>
<p>使用命令行选项<pre class="crayon-plain-tag">-generate=client</pre>可以生成客户端代码：</p>
<pre class="crayon-plain-tag">// 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
}</pre>
<p>每个OpenAPI Schema中定义的操作都对应了一个客户端函数。 </p>
<div class="blog_h3"><span class="graybg"><a id="openapi-gen"></a>openapi-gen</span></div>
<p>此工具专门用于从K8S模型类生成Open API模型（以Go结构的形式存放在GetOpenAPIDefinitions函数中）。</p>
<p>下载并安装：</p>
<pre class="crayon-plain-tag">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</pre>
<p>要为指定的包生成模型，执行命令：</p>
<pre class="crayon-plain-tag"># 输入包的导入路径
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</pre>
<p>你的CRD通常会引用K8S核心库中的模型，因此需要同时为它们生成模型：</p>
<pre class="crayon-plain-tag">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</pre>
<p>使用Operator Framework开发CRD的控制器时，你可以使用注释<pre class="crayon-plain-tag">+k8s:openapi-gen=true</pre>来生成Open API模型，生成的Schema示例如下：</p>
<div class="blog_h3"><span class="graybg">生成Swagger Spec</span></div>
<p>下面的代码调用GetOpenAPIDefinitions函数，生成Swagger 2.0.0 API定义的JSON：</p>
<pre class="crayon-plain-tag">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) &lt;= 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:       &amp;spec.Paths{Paths: map[string]spec.PathItem{}},
			Info: &amp;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 &lt; j; i, j = i+1, j-1 {
		hostParts[i], hostParts[j] = hostParts[j], hostParts[i]
	}
	parts[0] = strings.Join(hostParts, ".")
	return strings.Join(parts, ".")
}</pre>
<p>生成的Swagger API，仅仅包含模型信息，也就是definitions字段，但是不包含API端点（paths）。</p>
<p>如果需要生成API端点，则需要启动一个API Server并将需要生成API端点的模型注册进去：</p>
<pre class="crayon-plain-tag">// 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 = &amp;StandardStorage{}
var _ rest.Scoper = &amp;StandardStorage{}
var _ rest.StandardStorage = &amp;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,
		&amp;metav1.Status{},
		&amp;metav1.APIVersions{},
		&amp;metav1.APIGroupList{},
		&amp;metav1.APIGroup{},
		&amp;metav1.APIResourceList{},
	)

	// API Server选项
	options := genericoptions.NewRecommendedOptions("/registry/pks.yun.gmem.cc", cfg.Codecs.LegacyCodec(), &amp;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] = &amp;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(&amp;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) &lt;= 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"), &amp;pksv1.Release{})
	scheme.AddKnownTypeWithName(schema.GroupVersion{Group: "pks.yun.gmem.cc", Version: "v1"}.WithKind("ReleaseList"), &amp;pksv1.Release{})
	scheme.AddKnownTypeWithName(schema.GroupVersion{Group: "pks.yun.gmem.cc", Version: "v1"}.WithKind("WatchEvent"), &amp;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)
}</pre>
<div class="blog_h2"><span class="graybg">Java</span></div>
<div class="blog_h3"><span class="graybg">Gradle</span></div>
<p>可以使用下面的插件：</p>
<pre class="crayon-plain-tag">plugins {
  id 'org.hidetake.swagger.generator' version '2.18.1'
}</pre>
<p>根据API规格的版本，选择依赖：</p>
<pre class="crayon-plain-tag">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
}</pre>
<p>控制代码生成的配置片段：</p>
<pre class="crayon-plain-tag">swaggerSources {
  petstore {
    inputFile = file('petstore.yaml')
    code {
      language = 'spring'
      configFile = file('config.json')
    }
  }
}</pre>
<p>执行命令：<pre class="crayon-plain-tag">./gradlew generateSwaggerCode</pre>，将自动生成代码到<pre class="crayon-plain-tag">build/swagger-code-petstore</pre>目录。</p>
<p>configFile字段可以指定一个外部（本质上就是<pre class="crayon-plain-tag">swagger-codegen-cli generate</pre>的）配置文件，格式如下：</p>
<pre class="crayon-plain-tag">{
  "library": "spring-mvc",
  "modelPackage": "example.model",
  "apiPackage": "example.api",
  "invokerPackage": "example"
}</pre>
<p>对于language=java，常用配置项列表：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">配置项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>sortParamsByRequiredFlag</td>
<td>排序风发参数，将必须参数放在前面</td>
</tr>
<tr>
<td>ensureUniqueParams</td>
<td>是否一个操作内，保证参数名的唯一性</td>
</tr>
<tr>
<td>allowUnicodeIdentifiers</td>
<td>是否允许Unicode标识符</td>
</tr>
<tr>
<td>modelPackage</td>
<td>生成的模型类的包名</td>
</tr>
<tr>
<td>apiPackage</td>
<td>生成的API类的包名</td>
</tr>
<tr>
<td>invokerPackage</td>
<td>生成的代码的根包名</td>
</tr>
<tr>
<td>groupId</td>
<td rowspan="4">生成的POM的信息</td>
</tr>
<tr>
<td>artifactId</td>
</tr>
<tr>
<td>artifactVersion</td>
</tr>
<tr>
<td>artifactDescription</td>
</tr>
<tr>
<td>sourceFolder</td>
<td>源码目录</td>
</tr>
<tr>
<td>localVariablePrefix</td>
<td>局部变量前缀</td>
</tr>
<tr>
<td>serializableModel</td>
<td>生成的模型是否实现Serializable接口</td>
</tr>
<tr>
<td>fullJavaUtil</td>
<td>如果生成Java代码，那么是否用全限定名称引用java.util下的类</td>
</tr>
<tr>
<td>withXml</td>
<td>生成的类是否添加序列化为XML的支持</td>
</tr>
<tr>
<td>dateLibrary</td>
<td>
<p>Java日期库：</p>
<p style="padding-left: 30px;">joda 用于遗留应用<br />legacy 使用java.util.Date，用于遗留应用<br />java8-localdatetime   使用LocalDateTime，用于遗留应用<br />java8 使用Java 8原生的JSR310<br />threetenbp  低版本的JSR310垫片</p>
</td>
</tr>
<tr>
<td>java8</td>
<td>使用Java 8提供的API</td>
</tr>
<tr>
<td>disableHtmlEscaping</td>
<td>使用JSON时禁止HTML转译，如果<span style="background-color: #c0c0c0;">使用byte[]字段应该设置为true</span></td>
</tr>
<tr>
<td>useGzipFeature</td>
<td>是否使用Gzip编码请求</td>
</tr>
<tr>
<td>useRuntimeException</td>
<td>是否使用RuntimeException代替Exception</td>
</tr>
<tr>
<td>library</td>
<td>
<p>库模板，对于language=java，默认okhttp-gson。可选：</p>
<p style="padding-left: 30px;">jersey1，基于JacksonJSON串行化<br />jersey2，基于JacksonJSON串行化<br />feign，基于JacksonJSON串行化<br />okhttp-gson，基于GSON串行化<br />retrofit2 基于OKHttp 3.x，基于GSON串行化<br />resttemplate 基于Spring RestTemplate，基于JacksonJSON串行化</p>
</td>
</tr>
</tbody>
</table>
<p>  </p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/openapi">OpenAPI学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/openapi/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
