Go-web如何一步步整合swagger-ui

2,166 阅读5分钟

我们可以官方提供的方式:github.com/go-swagger/… , 主要方式是:1、写我们的go程序,2、让swagger工具扫描我们的go文件,3、生成swagger注释。

我们先说存在的问题:1、注释本地生成,会因为swagger版本不一致出现问题,出现各种git冲突,需要采用打包编译时去执行生成。2、违背了编程的方式。 3、为什么Java的swagger那么火,是因为和spring-boot完美整合,那么Spring解决的其实就是ioc的控制,所以go的web也不需要程序去控制,路由,输入,输出,所以我们需要这么做。

前期准备

1、需要实现Spring-Boot的 controoler接口的方式

​ 大致需求:1、拿到路由,2、拿到请求、响应(这个是go所有web框架做不到的,所以需要转变思想)

@RestController
public class UserController {
    @Autowired
    private UserRepository repository;

    @PostMapping("/save")
    public User save(UserInfoDto info) {
        return repository.save(User.builder().id(info.getId()).userName(info.getName()).build());
    }
}

2、转变思想

​ 这个反射调用的框架,我写了一个,希望大家多多提交bug,因为反射本身缺陷很多:github.com/Anthony-Don…

var (
	userService = service.NewUserService()
)

func userRoute(e *engines.Engine) {
	g := e.Group("/report")
	g.POST("benchmark", http.NewHandlerFunc(userService.BenchMark))
}

// 定义 context.Context(必不可少),类似于Java的ThreadLocal
// request *dto.BenchmarkRequest 请求
// *dto.BenchmarkResponse 响应
// error 由于go本身没有throw,所以需要显示的申明
// 后期考虑加入web模块,但是目前主流web框架都是依赖于ctx向下传递,所以go和Java这点不一样(目前不考虑,可以考虑使用gin的context),虽然Springmvc中也可以接受http-request,http-response 但是由于它的bind,用户都不care了(这就是申明式编程的好处)
type UserService interface {
	BenchMark(ctx context.Context, request *dto.BenchmarkRequest) (*dto.BenchmarkResponse, error)
}

实现了这个,那么我们就开始吧,因为这些要求的,我们都可以拿到。

3、定义强路由

1、因为go本身并没有方法级别的注解,如果我们可以在每个interface上申明接口,在每个方法上申明路由

// path=/user_service
type UserService interface {
	// path=/benchmark method=get
	BenchMark(ctx context.Context, request *dto.BenchmarkRequest) (*dto.BenchmarkResponse, cerror.Cerror)
}

如果这样子,那么对于go的开发者特别不友好

2、所有采用func注册的方式

​ 可以在接口后面指定get/其他

g.POST("benchmark", http.NewHandlerFunc(userService.BenchMark),op.Get())

​ 目前采用的这种方式:1、可以显示的申明,符合go开发者的习惯,2、可以不用扫描go文件(spring-boot采用的这种方式)

整合swagger客户端

​ 找到swagger的web端资源包,然后将其都暴漏出去,接下来核心就是/swagger.json 了。

​ 资源在我的这个项目的swagger里面 github.com/Anthony-Don… ,后期会考虑静态资源使用api调用的方式(转发的方式),目前是借助工具写在了go文件里:github.com/a-urth/go-b… ,这个工具很nice,可以讲文件写入到go文件里,以二进制的形式,我们知道go是不可以打包成jar包的,只有二进制文件,静态资源是一个很难受的事情,所以需要这么做。

const (
	js   = "js"
	css  = "css"
	html = "html"
	byte = "byte"
	json = "json"
)

func addSwagger(path string, _type string) func(writer http.ResponseWriter, request *http.Request) {
	return func(writer http.ResponseWriter, request *http.Request) {
		body, _ := swagger.Asset(path)
		switch _type {
		case js:
			writer.Header().Set("content-type", "application/javascript")
		case css:
			writer.Header().Set("content-type", "text/css")
		case json:
			writer.Header().Set("content-type", "application/json")
		}
		if _type == byte {
			fmt.Fprint(writer, body)
			return
		}
		fmt.Fprint(writer, string(body))
	}
}

func addSwaggerRouter(path string, _type string) {
	http.HandleFunc("/"+path, addSwagger(path, _type))
}

func main() {
  // swagger内置服务端需要的东西
	addSwaggerRouter("swagger-ui/absolute-path.js", js)
	addSwaggerRouter("swagger-ui/favicon-16x16.png", byte)
	addSwaggerRouter("swagger-ui/favicon-32x32.png", byte)
	addSwaggerRouter("swagger-ui/index.html", html)
	addSwaggerRouter("swagger-ui/index.js", js)
	addSwaggerRouter("swagger-ui/oauth2-redirect.html", html)
	addSwaggerRouter("swagger-ui/package.json", json)
	addSwaggerRouter("swagger-ui/swagger-ui-bundle.js", js)
	addSwaggerRouter("swagger-ui/swagger-ui-bundle.js.map", html)
	addSwaggerRouter("swagger-ui/swagger-ui-standalone-preset.js", js)
	addSwaggerRouter("swagger-ui/swagger-ui-standalone-preset.js.map", html)
	addSwaggerRouter("swagger-ui/swagger-ui.css", css)
	addSwaggerRouter("swagger-ui/swagger-ui.css.map", html)
	addSwaggerRouter("swagger-ui/swagger-ui.js", js)
	addSwaggerRouter("swagger-ui/swagger-ui.js.map", html)

	http.HandleFunc("/swagger.json", func(writer http.ResponseWriter, request *http.Request) {
		writer.Header().Set("content-type", "application/json")
		fmt.Fprintf(writer, doc)
	})
	http.ListenAndServe(":8888", nil)
}

swagger-json

​ 这个文件来自于 faygo,swagger核心是一个api接口,类似于下面这个样子

{
    "swagger": "2.0",
    "info": {
        "description": "Spring Swaager2 REST API",
        "version": "V1",
        "title": "REST API",
        "contact": {
            "name": "anthony",
            "url": "https://github.com/Anthony-Dong",
            "email": "574986060@qq.com"
        },
        "license": {
            "name": "The Apache License",
            "url": "https://opensource.org/licenses/MIT"
        }
    },
    "host": "localhost:8888",
    "basePath": "/",
    "tags": [
        {
            "name": "user-controller",
            "description": "User Controller"
        }
    ],
    "paths": {
        "/find/{id}": {
            "get": {
                "tags": [
                    "user-controller"
                ],
                "summary": "find",
                "operationId": "findUsingGET",
                "produces": [
                    "*/*"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "id",
                        "required": true,
                        "type": "integer",
                        "format": "int64"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/User"
                        }
                    }
                },
                "deprecated": false
            }
        },
        "/save/{name}": {
            "get": {
                "tags": [
                    "user-controller"
                ],
                "summary": "echo",
                "operationId": "echoUsingGET",
                "produces": [
                    "*/*"
                ],
                "parameters": [
                    {
                        "name": "name",
                        "in": "path",
                        "description": "name",
                        "required": true,
                        "type": "string"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/User"
                        }
                    }
                },
                "deprecated": false
            }
        }
    },
    "definitions": {
        "User": {
            "type": "object",
            "properties": {
                "id": {
                    "type": "integer",
                    "format": "int64"
                },
                "userName": {
                    "type": "string"
                }
            },
            "title": "User"
        }
    }
}

一步步写doc

​ 下面是swagger-doc的大致结构,我们必须将我们的api注入进去

doc := swagger.Swagger{
  Version: swagger.Version,
  Info: &swagger.Info{
    Title: "REST API",
    Contact: &swagger.Contact{
      Email: "fanhaodong516@gmail.com",
    },
    License: &swagger.License{
      Name: "The Apache License",
      Url:  "https://opensource.org/licenses/MIT",
    },
  },
  Host:        "localhost:8888",
  BasePath:    "/",
  Tags:        []*swagger.Tag{}, //tag
  Paths:       map[string]map[string]*swagger.Opera{},// 所有的api路由
  Definitions: map[string]*swagger.Definition{},// 定义dto对象
}

tag 是什么

​ user-controller 就是一个tag ,对于go来说一般是group可以定义为一个 controller

image-20200705104411833
// Tag object
Tag struct {
  Name        string `json:"name"` // 一个标签:user-controller
  Description string `json:"description"` // 一个des:user-controller
}

同时所有的path都可以指定多个tag

Opera struct {
  Tags        []string              `json:"tags"` // 这里是tag
  Summary     string                `json:"summary"`
  Description string                `json:"description"`
  OperationId string                `json:"operationId"`
  Consumes    []string              `json:"consumes,omitempty"`
  Produces    []string              `json:"produces,omitempty"`
  Parameters  []*Parameter          `json:"parameters,omitempty"`
  Responses   map[string]*Resp      `json:"responses"` // {"httpcode":resp}
  Security    []map[string][]string `json:"security,omitempty"`
}

认识path

​ 这就是一个path

Paths               map[string]map[string]*Opera      `json:"paths,omitempty"`

结构是个两级map

"/save": {
    "post": {
        "tags": [
            "user-controller"
        ],
        "summary": "echo",
        "operationId": "echoUsingPOST",
        "consumes": [ //请求类型
            "application/json"
        ],
        "produces": [ // 响应类型
            "*/*"
        ],
        "parameters": [ // 核心关注的点 ,如果多级别
            {
                "name": "id", // 字段描述
                "in": "query", // 查询
              "required": false, // 是否强需求,可以借助于binding,也就是go的Validator(https://github.com/go-playground/validator)
                "type": "integer", // 类型
              "format": "int64" // 真实类型(go类型)
            },
            {
                "name": "name",
                "in": "query",
                "required": false,
                "type": "string"
            },
            {
              "name": "user.id",
              "in": "query",
              "required": false,
              "type": "integer",
              "format": "int64"
          },
          {
              "name": "user.userName",
              "in": "query",
              "required": false,
              "type": "string"
          }
        ],
        "responses": {
            "200": {// 状态吗
                "description": "OK", // 响应描述
                "schema": {
                    "$ref": "#/definitions/User" // 指定路由:(nice,所以我们的dto全部放到modle里)
                }
            }
        },
        "deprecated": false
    }
},

总结

通过以上,我们可以get到,确实是不是特比难,是有一定规律的,那么接下来,我们就开始设计框架了。