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
使用步骤
- 使用 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 接口, 并且可以实现代码与接口文档的一致, 保证接口的一致. 结合当前项目还是非常棒的选择