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/基本结构.md
下面这个 API 文档是由之后的 go-restful 生成的。

swagger: '2.0'
info:
  description: Resource for managing Users
  title: UserService
  contact:
    name: john
    url: 'http://johndoe.org'
    email: [email protected]
  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: "[email protected]",
				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文档了。

猜你喜欢

转载自blog.csdn.net/s7799653/article/details/88747057
今日推荐