Swagger 入門使用


概述

使用 Swagger 解決什么問題, 怎么使用 Swagger, 如何規范 go-swagger 的使用.

背景介紹

為了解決與后端對數據的的強耦合,  使用 HTTP 接口進行解耦.

而 Swagger 一方面可以非常友好的對外展示接口, 文檔即接口, 另

一方面可以使用 go-swagger 自動生成部分 server 端代碼, 快速實現接口開發. 方便以后可以快速開發 HTTP 服務接口

主要內容

簡介

Swagger 是一個簡單但功能強大的 API 表達工具。

Swagger 使用 OpenAPI 規范(試圖通過定義一種用來描述API格式或API定義的語言,來規范RESTful服務開發過程).

使用 Swagger 生成 API,我們可以得到交互式文檔,自動生成代碼的 SDK 以及 API 的發現特性等。

wagger 主要包括三部分 Swagger API Spec,描述 Rest API 的語言。Swagger UI,將 Swagger API Spec 以 HTML 頁面展現出來的模塊。Swagger Editor,Swagger API Spec 的編輯器

為什么使用 swagger

主要是因為工作的核心在實時服務方面.

一方面: swagger能夠幫助我們節省編寫接口文檔的時間,提高我們開發時的效率.

另一方面: 保證文檔的即時性,准確性以及一致性, 減少不必要的溝通工作, 文檔都是同一份

另外一方面: 使用 go-swagger 自動生成部分代碼, 減少寫重復代碼.

go-swagger 簡介

go-swagger 是 Swagger 2.0 的 Go 語言實現。將 swagger 接口文檔生成客戶端、服務端代碼

怎么使用 Swagger

使用步驟

  1. 使用 swagger-editor 定義 API
  2. 使用 swagger-ui 展示 API 定義
  3. 使用 go-swagger 生成代碼
  4. 基於代碼增加業務處理邏輯

swagger-spec

Swagger 使用 OpenAPI 規范開發 API。后來, SmartBear Software 將 Swagger 規范捐贈給 Linux Foundation,並將規范重命名為OpenAPI規范。 SmartBear 成為OpenAPI Initiative(OAI)的創始成員,該機構以開放和透明的方式管理 OAS 的發展。

簡而言之 Swagger 包含了一套 API 規范,並且提供一系列的生態組件
OpenAPI = 規范
Swagger = 實現規范的組件

swagger-ui

除官方提供的 swagger-ui 外, 還有一個Re-Doc, 界面交互我覺得更好一點, 但是存在一點的規范丟失.

go-swagger 生成服務端、客戶端代碼

swagger generate server --target  --name --spec ../../swagger.yaml 

swagger generate client -f  swagger.yml -A  應用名稱 -t 目錄

如何自定義 handler

可以在生成代碼的restapi.configure_*中手動將handler 的業務邏輯實現, 缺點是對生成的代碼有修改, 約束性較高.

參考 kv-store (官方推薦), 每一個 handler 都定義一個 struct.

舉例說明

接口列表

重新生成回放

批量查詢重新生成回放狀態

查詢所有在線教師, 包括正在上課的老師以及在線的老師

批量查詢連線信息

批量查詢回放信息

定時同步session 信息

swagger 接口定義

swagger: '2.0'
info:
  description: '開放 API, 主要用於獲取連線相關的信息'
  version: 1.0.0
  title: Swagger Session
  contact:
    name: zhanghaojie
    email: zhanghaojie@iyunxiao.com
externalDocs:
  description: Find out more about Swagger
  url: 'http://swagger.io'
basePath: /v1/session
tags:
  - name:  session
    description: '連線信息'
schemes:
  - http
host: testhfsfd-sessions.haofenshu.com
consumes:
  - application/json
produces:
  - application/json
paths:
  /playbackInfos:
    post:
      tags:
        - session
      summary: '批量獲取回放信息'
      description: '如果請求內容中包含不存在的連線 ID, 響應中不會包含此 ID 的任何信息'
      operationId: getPlaybackInfosBySessionIds
      parameters:
        - name: sessionIds
          description: '連線 ID 的數組'
          in: body
          required: true
          schema:
            $ref: '#/definitions/SessionIds'
      responses:
        '201':
          description: '請求成功'
          schema:
            type: object
            properties:
              result:
                type: string
                description: '結果情況, success 代表成功, 其他 代表失敗'
                enum:
                  - success
                  - fail
                example:
                  "success"
              msg:
                type: string
                description: '對結果的描述'
                example:
                  "獲取成功"
              data:
                type: array
                items:
                  $ref: '#/definitions/PlaybackInfo'
        '500':
          $ref: '#/responses/Standard500ErrorResponse'
  /{sessionId}/playbackStatus/regeneration:
    put:
      tags:
        - session
      summary: '重新生成回放'
      description: '重新生成回放'
      operationId: setPlaybackStatusToRegeneration
      parameters:
        - name: sessionId
          description: '連線 ID'
          required: true
          in: path
          type: string
      responses:
        '201':
          description: '請求成功'
          schema:
            type: object
            properties:
              result:
                type: string
                description: '結果情況, success 代表成功, 其他情況 代表失敗'
                enum:
                  - success
                  - sessionNoFound
                  - repeatSubmit
                  - generating
                example:
                  "success"
              msg:
                type: string
                example:
                  "重新生成成功"
        '500':
          $ref: '#/responses/Standard500ErrorResponse'
  /playbackStatuses:
    post:
      tags:
        - session
      summary: '批量獲取回放狀態信息'
      description: '如果請求內容中包含不存在的連線 ID, 響應中不會包含此 ID 的任何信息'
      operationId: getPlaybackStatusesBySessionIds
      parameters:
        - name: sessionIds
          description: '連線 ID 的數組'
          in: body
          schema:
            $ref: '#/definitions/SessionIds'
      responses:
        '201':
          description: '請求成功'
          schema:
            type: object
            properties:
              result:
                type: string
                description: '結果情況, success 代表成功, 其他 代表失敗'
                enum:
                  - success
                  - fail
                example:
                  "success"
              msg:
                type: string
                description: '對結果的描述'
                example:
                  "獲取成功"
              data:
                type: array
                items:
                  $ref: '#/definitions/PlaybackStatus'
        '500':
          $ref: '#/responses/Standard500ErrorResponse'
  /teachers/{teacherStatus}:
    get:
      tags:
        - session
      summary: '獲取不同狀態下所有的老師'
      operationId: getAllTeachersByStatus
      description: 'online 包含 inClass 中的老師'
      parameters:
        - name: teacherStatus
          description: 'online 表示在線的老師, inClass 表示連線中的老師'
          in: path
          required: true
          type: string
          enum:
            - online
            - inClass
      responses:
        '200':
          description: '請求成功'
          schema:
            type: object
            properties:
              result:
                type: string
                description: '結果情況, success 代表成功, 其他 代表失敗'
                enum:
                  - success
                  - fail
                example:
                  "success"
              msg:
                type: string
                description: '對結果的描述'
                example:
                  "獲取成功"
              data:
                type: array
                items:
                  type: string
        '500':
          $ref: '#/responses/Standard500ErrorResponse'
  /sessionInfosByRange:
    get:
      tags:
        - session
      summary: '同步課堂信息, 通過傳入 連線 開始時間的時間區間'
      operationId: getSessionInfosByRange
      parameters:
        - name: start
          in: query
          required: true
          description: '連線 開始時間的起始時間戳(unix 時間戳(毫秒))'
          type: integer
          format: int64
        - name: end
          in: query
          required: true
          description: '連線 開始時間的截止時間戳(unix 時間戳(毫秒))'
          type: integer
          format: int64
      responses:
        '201':
          description: '請求成功'
          schema:
            type: object
            properties:
              result:
                type: string
                description: '結果情況, success 代表成功, 其他 代表失敗'
                enum:
                  - success
                  - fail
              msg:
                type: string
                description: '對結果的描述'
              data:
                type: array
                items:
                  $ref: '#/definitions/SessionInfo'
        '500':
          $ref: '#/responses/Standard500ErrorResponse'
  /sessionInfosBySessionIds:
    post:
      tags:
        - session
      summary: '批量查詢連線信息'
      description: '如果請求內容中包含不存在的連線 ID, 響應中不會包含此 ID 的任何信息'
      operationId: getSessionInfoBySessionIds
      parameters:
        - name: sessionIds
          description: SessionId's array
          in: body
          required: true
          schema:
            $ref: '#/definitions/SessionIds'
      responses:
        '201':
          description: '請求成功'
          schema:
            type: object
            properties:
              result:
                type: string
                description: '結果情況, success 代表成功, 其他 代表失敗'
                enum:
                  - success
                  - fail
              msg:
                type: string
                description: '對結果的描述'
              data:
                type: array
                items:
                  $ref: '#/definitions/SessionInfo'
        '500':
          $ref: '#/responses/Standard500ErrorResponse'
definitions:
  PlaybackStatus:
    type: object
    required:
      - sessionId
      - status
    properties:
      sessionId:
        type: string
        description: '數據庫 session 表中的 session 字段'
        example:
          "7210000"
      status:
        type: string
        description: '
        回放狀態信息: <br>
          playbackNotGenerated 回放還未生成, 包括需要重新生成, 還未生成 <br>
          sessionNotExist 課程不存在 <br>
          sessionNotFinish 課程正在上課 <br>
          playbackFailedGenerated 回放生成失敗, 包括只有語音、只有畫圖等各種錯誤情況 <br>
          playbackSuccessGenerated 回放生成成功'
        default: sessionNotExist
        enum:
          - sessionNotExist
          - sessionNotFinish
          - playbackNotGenerated
          - playbackFailedGenerated
          - playbackSuccessGenerated
        example:
          "playbackSuccessGenerated"
  PlaybackInfo:
    type: object
    required:
      - sessionId
    properties:
      sessionId:
        type: string
        description: '回放視頻 ID'
        example:
          "7210000"
      status:
        type: string
        description: '
        回放狀態信息: <br>
          sessionNotExist 課程不存在 <br>
          sessionNotFinish 課程正在上課 <br>
          playbackNotGenerated 回放還未生成, 包括需要重新生成, 還未生成 <br>
          playbackFailedGenerated 回放生成失敗, 包括音頻錯誤等各種錯誤情況 <br>
          playbackSuccessGenerated 回放生成成功, 存在下載地址, 視頻大小 <br>
          playbackNotVideo 老回放, 只有視頻地址 沒有下載地址'
        default: sessionNotExist
        enum:
          - sessionNotExist
          - sessionNotFinish
          - playbackNotGenerated
          - playbackFailedGenerated
          - playbackSuccessGenerated
          - playbackNotVideo
        example:
          "playbackSuccessGenerated"
      videoUrl:
        type: string
        description: '新回放的下載地址'
        example:
          "http://yx-fudao.ks3-cn-beijing.ksyun.com/testreplayer_data/7210000/7210000.mp4?Expires=1548152332&AWSAccessKeyId=AKLT6GLT4mf1RoiAY5DCcsd_3Q&Signature=zXSJNkX9C3ovtmPYwF3Y2fNcXdY%3D"
      videoSize:
        type: integer
        format: int64
        default: -1
        description: '新回放的文件大小(單位: byte)'
        example:
          1026323
      expire:
        type: integer
        format: int64
        description: '新回放下載地址的過期時間(unix 時間戳(毫秒))'
        example:
          1548507047292
      webUrl:
        type: string
        description: '舊回放的直接播放地址'
        example:
          "testhfsfd-replayer.haofenshu.com/entry?sid=7210000"
  SessionInfo:
    type: object
    required:
      - sessionId
    properties:
      sessionId:
        type: string
        description: '數據庫 session 表中的 session 字段'
        example:
          "7210000"
      teacher:
        type: string
        description: '老師的用戶名'
        example:
          "muyi"
      student:
        type: string
        description: '學生的用戶名'
        example:
          "test014"
      status:
        type: string
        description: '
        回放狀態信息: <br>
          playbackNotGenerated 回放還未生成, 包括需要重新生成, 還未生成 <br>
          sessionNotExist 課程不存在 <br>
          sessionNotFinish 課程正在上課 <br>
          playbackFailedGenerated 回放生成失敗, 包括只有語音、只有畫圖等各種錯誤情況 <br>
          playbackSuccessGenerated 回放生成成功'
        default: sessionNotExist
        enum:
          - sessionNotExist
          - sessionNotFinish
          - playbackNotGenerated
          - playbackFailedGenerated
          - playbackSuccessGenerated
        example:
          "playbackSuccessGenerated"
      classType:
        type: string
        description: '課程類型, UnFormal 代表非正式課, Formal 代表正式課'
        enum:
          - UnFormal
          - Formal
        example:
          "Formal"
      startTime:
        type: integer
        format: int64
        description: '課程開始時間(unix 時間戳(毫秒))'
        example:
          1548085855
      endTime:
        type: integer
        format: int64
        description: '課程結束時間(unix 時間戳(毫秒))'
        example:
          1548067573
  SessionIds:
    type: object
    description: '連線 ID 的數組'
    required:
      - sessionIds
    properties:
      sessionIds:
        type: array
        uniqueItems: true
        minItems: 1
        items:
          type: string
          minLength: 1
          example:
            "7210000"
        example:
          ["7210000","7210001","7210002","7210003","7210004","7210005"]
  Error:
    type: object
    required:
      - message
    properties:
      message:
        type: string
        example:
          "database error"
responses:
  Standard500ErrorResponse:
    description: '服務器內部異常'
    schema:
      $ref: '#/definitions/Error'

接口定義規范

go-swagger 會做部分業務校驗, 此時的返回碼是RESTful風格 的HTTP Code

在 Swagger 中的描述性話語盡量使用中文. summary 使用簡單概述, description 盡可能描述清楚

不必非得遵循 RESTful 風格. HTTP Method 僅使用 GET、POST、PUT 、DELETE 這些常用的方法, HTTP Code 也僅使用常用狀態碼

接口保證好的擴展性

個人思考

個人思考

為什么使用 Swagger

選用 Swagger 的主要考慮點在代碼接口層跟接口文檔的統一, 並且我們的主要工作並不是以寫接口為主, 因此選用 Swagger 可以方便管理文檔與兼顧效率.

Go 常用的 HTTP 框架如 Gin 與 Beego 都提供了對 Swagger 的支持, 但是需要將相應的注釋或注解編寫到方法上,再利用生成器自動生成說明Swagger文件. 他們將一些不是代碼相關的內容注入到代碼中, 增加部分冗余.

go-swagger、gin、beego 的區別

gin 在 Go 社區的流行度非常高, 遠高於其他. 流行程度可以方便問題的處理. beego 在國內來說是使用的人數也不錯, 但從論壇中看出大家開始放棄 Beego, 因為它的大而全. go-swagger 使用的人數還是相當較少, 從官方文檔中看到使用go-swagger的一些項目中, 很多是使用它做 Client, 而非 Server.

go-swagger 的默認路由是 naoina's denco, 官方的文檔中說明其是一個 ternary search tree, 相對於 gin 的 httprounter 中的 radix tree性能更好. (我也沒有測試). beego 相關的路由實現我未找到, 其支持正則匹配. 

go-swagger 對 handler 的管理

個人覺得這是一個很棘手的問題, 如果每個 handler 都寫一個 struct 其實現其接口方法, 如果接口越來越多, 導致 main 方法中對 API 注入越來越多, 並且一個接口一個 struct 這種方式, 個人還是覺得非常不優雅的.大家有什么好的方法可以提供建議.

go-swagger 散失代碼的靈活性

go-swagger 對 swagger spec 中的校驗有實現, 可以說非常的方便, 減少業務代碼的冗長. go-swagger 可以支持增加middleware, 進行一些功能擴展.

使用框架必然會散失部分靈活性, 這是一個權衡的問題.

總結

Swagger 結合 go-swagger 可以快速的開發 API 接口, 並且可以實現代碼與接口文檔的一致, 保證接口的一致. 結合當前項目還是非常棒的選擇

 

參考資料

    1. Swagger Spec 結構
    2. 如何編寫基於OpenAPI規范的API文檔
    3. API接口管理之道
    4. Go HTTP路由基准測試


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM