概述
使用 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
使用步驟
- 使用 swagger-editor 定義 API
- 使用 swagger-ui 展示 API 定義
- 使用 go-swagger 生成代碼
- 基於代碼增加業務處理邏輯
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 接口, 並且可以實現代碼與接口文檔的一致, 保證接口的一致. 結合當前項目還是非常棒的選擇