go restful 生成 swagger 2.0 文档

什么是swagger

Swagger 允许您描述 API 的结构,以便机器能够读取它们。Swagger 所有能力中最卓越的是 api 描述自身结构的能力。为什么他这么棒?通过阅读API的结构,我们可以自动构建漂亮的交互式 API 文档。我们还可以用多种语言为您的 API 自动生成服务端库,并探索其他可能性,比如自动化测试。Swagger 通过请求 API 返回包含整个 API 详细描述的 YAML 或 JSON 来实现这一点。这个文件本质上是您的API的资源列表,它遵循OpenAPI规范。该规范要求您包括以下信息:

  • 您的API支持哪些操作?
  • 您的API的参数是什么?它返回什么?
  • 您的API需要一些授权吗?
  • 甚至还有一些有趣的东西,比如术语、联系信息和使用API的许可。

您可以手动为您的 API 编写一个 Swagger 规范,或者让它从源代码中的注释自动生成。

一些与swagger 相关的工具:

  • swagger ui 生成交互式API文档,让用户直接在浏览器中尝试API调用。
  • swagger CodeGen 用API文档生成代码
  • swagger edit 集合了 swagger ui 和 swagger CodeGen 的部分功能

搭建swagger相关的工具

这里只需要搭建 swagger ui 和 swagger edit 就可以了,搭建swagger edit 是为了从网页端生成代码。 搭建swagger ui 是为了可以读取API文档,生成交互式API。
以下是使用docker搭建的方式:

搭建 swagger edit

docker run -d -p 8081:8080 swaggerapi/swagger-editor

访问 http://IP:8081即可看到 swagger editor页面了。swagger editor 既可以编写 swagger 2.0 也可编写 swagger 3.0 。 但是,swagger 3.0 可生成代码的种类明显少于2.0,这也是我为什么用2.0 的原因。

搭建 swagger ui

docker run -d -p 8082:8080 swaggerapi/swagger-ui

swagger 2.0 API 文档的结构

swagger 2.0的文档结构可以参考https://swagger.io/docs/specification/2-0/basic-structure/,如果你不愿意读英文的,我简单翻译了一下下https://github.com/SongJXin/swagger-2.0-translate/blob/master/%E5%9F%BA%E6%9C%AC%E7%BB%93%E6%9E%84.md
下面这个 API 文档是由之后的 go-restful 生成的。

swagger: '2.0'
info:
  description: Resource for managing Users
  title: UserService
  contact:
    name: john
    url: 'http://johndoe.org'
    email: john@doe.rp
  license:
    name: MIT
    url: 'http://mit.org'
  version: 1.0.0
paths:
  /users:
    get:
      consumes:
        - application/xml
        - application/json
      produces:
        - application/json
        - application/xml
      tags:
        - usersa
      summary: get all users
      operationId: findAllUsers
      responses:
        '200':
          description: OK
          schema:
            type: array
            items:
              $ref: '#/definitions/main.User'
    put:
      consumes:
        - application/xml
        - application/json
      produces:
        - application/json
        - application/xml
      tags:
        - usersa
      summary: create a user
      operationId: createUser
      parameters:
        - name: body
          in: body
          required: true
          schema:
            $ref: '#/definitions/main.User'
      responses:
        '200':
          description: OK
  '/users/{user-id}':
    get:
      consumes:
        - application/xml
        - application/json
      produces:
        - application/json
        - application/xml
      tags:
        - usersa
      summary: get a user
      operationId: findUser
      parameters:
        - type: integer
          default: 1
          description: identifier of the user
          name: user-id
          in: path
          required: true
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/main.User'
        '404':
          description: Not Found
    put:
      consumes:
        - application/xml
        - application/json
      produces:
        - application/json
        - application/xml
      tags:
        - usersa
      summary: update a user
      operationId: updateUser
      parameters:
        - type: string
          description: identifier of the user
          name: user-id
          in: path
          required: true
        - name: body
          in: body
          required: true
          schema:
            $ref: '#/definitions/main.User'
      responses:
        '200':
          description: OK
    delete:
      consumes:
        - application/xml
        - application/json
      produces:
        - application/json
        - application/xml
      tags:
        - usersa
      summary: delete a user
      operationId: removeUser
      parameters:
        - type: string
          description: identifier of the user
          name: user-id
          in: path
          required: true
      responses:
        '200':
          description: OK
definitions:
  main.User:
    required:
      - id
      - name
      - age
    properties:
      age:
        description: age of the user
        type: integer
        format: int32
        default: 21
      id:
        description: identifier of the user
        type: string
      name:
        description: name of the user
        type: string
        default: john
tags:
  - description: Managing users
    name: users

go restful 集成 swagger 2.0

获取 go restful 和 swagger 相关的包

go get github.com/emicklei/go-restful
go get github.com/emicklei/go-restful-openapi

代码先贴为敬(这个代码是go-restful-openapi中自带的例子):

package main

import (
    "log"
    "net/http"

    "github.com/emicklei/go-restful"
    restfulspec "github.com/emicklei/go-restful-openapi"
    "github.com/go-openapi/spec"
)

type UserResource struct {
    // normally one would use DAO (data access object)
    users map[string]User
}

func (u UserResource) WebService() *restful.WebService {
    ws := new(restful.WebService)
    ws.
        Path("/users").
        Consumes(restful.MIME_XML, restful.MIME_JSON).
        Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well

    tags := []string{"users"}

    ws.Route(ws.GET("/").To(u.findAllUsers).
        // docs
        Doc("get all users").
        Metadata(restfulspec.KeyOpenAPITags, tags).
        Writes([]User{}).
        Returns(200, "OK", []User{}))

    ws.Route(ws.GET("/{user-id}").To(u.findUser).
        // docs
        Doc("get a user").
        Param(ws.PathParameter("user-id", "identifier of the user").DataType("integer").DefaultValue("1")).
        Metadata(restfulspec.KeyOpenAPITags, tags).
        Writes(User{}). // on the response
        Returns(200, "OK", User{}).
        Returns(404, "Not Found", nil))

    ws.Route(ws.PUT("/{user-id}").To(u.updateUser).
        // docs
        Doc("update a user").
        Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
        Metadata(restfulspec.KeyOpenAPITags, tags).
        Reads(User{})) // from the request

    ws.Route(ws.PUT("").To(u.createUser).
        // docs
        Doc("create a user").
        Metadata(restfulspec.KeyOpenAPITags, tags).
        Reads(User{})) // from the request

    ws.Route(ws.DELETE("/{user-id}").To(u.removeUser).
        // docs
        Doc("delete a user").
        Metadata(restfulspec.KeyOpenAPITags, tags).
        Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")))

    return ws
}

// GET http://localhost:8080/users
//
func (u UserResource) findAllUsers(request *restful.Request, response *restful.Response) {
    list := []User{}
    for _, each := range u.users {
        list = append(list, each)
    }
    response.WriteEntity(list)
}

// GET http://localhost:8080/users/1
//
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
    id := request.PathParameter("user-id")
    usr := u.users[id]
    if len(usr.ID) == 0 {
        response.WriteErrorString(http.StatusNotFound, "User could not be found.")
    } else {
        response.WriteEntity(usr)
    }
}

// PUT http://localhost:8080/users/1
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
//
func (u *UserResource) updateUser(request *restful.Request, response *restful.Response) {
    usr := new(User)
    err := request.ReadEntity(&usr)
    if err == nil {
        u.users[usr.ID] = *usr
        response.WriteEntity(usr)
    } else {
        response.WriteError(http.StatusInternalServerError, err)
    }
}

// PUT http://localhost:8080/users/1
// <User><Id>1</Id><Name>Melissa</Name></User>
//
func (u *UserResource) createUser(request *restful.Request, response *restful.Response) {
    usr := User{ID: request.PathParameter("user-id")}
    err := request.ReadEntity(&usr)
    if err == nil {
        u.users[usr.ID] = usr
        response.WriteHeaderAndEntity(http.StatusCreated, usr)
    } else {
        response.WriteError(http.StatusInternalServerError, err)
    }
}

// DELETE http://localhost:8080/users/1
//
func (u *UserResource) removeUser(request *restful.Request, response *restful.Response) {
    id := request.PathParameter("user-id")
    delete(u.users, id)
}

func main() {
    u := UserResource{map[string]User{}}
    restful.DefaultContainer.Add(u.WebService())

    config := restfulspec.Config{
        WebServices: restful.RegisteredWebServices(), // you control what services are visible
        APIPath:     "/apidocs.json",
        PostBuildSwaggerObjectHandler: enrichSwaggerObject}
    restful.DefaultContainer.Add(restfulspec.NewOpenAPIService(config))

    // Optionally, you can install the Swagger Service which provides a nice Web UI on your REST API
    // You need to download the Swagger HTML5 assets and change the FilePath location in the config below.
    // Open http://localhost:8080/apidocs/?url=http://localhost:8080/apidocs.json
    //http.Handle("/apidocs/", http.StripPrefix("/apidocs/", http.FileServer(http.Dir("/Users/emicklei/Projects/swagger-ui/dist"))))

    // Optionally, you may need to enable CORS for the UI to work.
    cors := restful.CrossOriginResourceSharing{
        AllowedHeaders: []string{"Content-Type", "Accept"},
        AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
        CookiesAllowed: false,
        Container:      restful.DefaultContainer}
    restful.DefaultContainer.Filter(cors.Filter)

    log.Printf("Get the API using http://localhost:8080/apidocs.json")
    log.Printf("Open Swagger UI using http://10.110.27.102:8082/?url=http://localhost:8080/apidocs.json")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func enrichSwaggerObject(swo *spec.Swagger) {
    swo.Info = &spec.Info{
        InfoProps: spec.InfoProps{
            Title:       "UserService",
            Description: "Resource for managing Users",
            Contact: &spec.ContactInfo{
                Name:  "john",
                Email: "john@doe.rp",
                URL:   "http://johndoe.org",
            },
            License: &spec.License{
                Name: "MIT",
                URL:  "http://mit.org",
            },
            Version: "1.0.0",
        },
    }
    swo.Tags = []spec.Tag{spec.Tag{TagProps: spec.TagProps{
        Name:        "users",
        Description: "Managing users"}}}
}

// User is just a sample type
type User struct {
    ID   string `json:"id" description:"identifier of the user"`
    Name string `json:"name" description:"name of the user" default:"john"`
    Age  int    `json:"age" description:"age of the user" default:"21"`
}

api 文档生成规则

package mainimport部分就略过了。
UserResource这个类是go-restful的基本结构 关于go-restful框架的这里就不多说了。

enrichSwaggerObject这个函数,是对应生成了swagger 2.0 API 文档(下称API文档)的info和 tags部分。

User 这个结构,即被go-restful使用,作为操作的对象,也是对应了API文档的definitions部分。在反单引号(`)之间的内容规定了这个结构体与definitions的对应关系。

WebService函数中:
ws.path().Consumes().Produces()对应swagger 2.0 中的basePathConsumes、Produces,只不过,在生成API过程中把他们分解到了具体的每一个path
tags就是个字符串数组,在ws.Route.Metadata中用到,规定了这个path的标签,如果enrichSwaggerObject中定义的tags定义了这个tag,将从里面取对应的Description信息,如果没有定义,就当作一个全新的tag
ws.Route生成了API文档的path
ws.Route(ws.GET)对应了一个path的一个operation
ws.Route.To这里是go-restful框架将请求路由到相应的函数(用词可能不准确,大概就是这么个意思,这函数处理这次请求),同时,在生成API文档的时候,函数的名称会做为path.operation.operationId
ws.Route.Doc对应生成了API文档的 path.operation.summary
ws.Route.Writes生成的path.operation.responses.default(这里有个很奇怪的现象。我用 chrome 和 firefox 看到的生成的API是不一样的,chrome 不会显示这个path.operation.responses.default字段,在swagger ui中可以看到这个字段)
ws.Route.Return这里生成path.operation.responses对应的状态码的返回。第一个参数,对应状态码,第二个参数对应description,第三个参数对应了schema
ws.Route.Param生成API文档的parameter
ws.Route.Reads也是生成API文档的parameter,区别是它使用了schema,引用了definitions中的对象

main函数中:
config设置了生成API文档的一些配置:

  • WebServices,为哪个WebServices 生成 API文档
  • APIPath,访问API文档的路径。
  • PostBuildSwaggerObjectHandler,使用的基本信息(infotags

http.Handle是这个程序和swagger ui在同一个服务器上,启动这个程序的时候同时启动swagger ui用的,这里用了外部的,就把它注释掉了,不用它了。
cors 这个过滤器是为了让swagger ui能够访问到这个 API文档,没有的话,用swagger ui访问会报错。

swagger UI 访问API文档。

访问http://swaggerUI-ip:8082/?url=http://localhost:8080/apidocs.json就可以看到 swagger ui 解析API 文档后的交互式API文档了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容