我们可以官方提供的方式: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、拿到路由,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()); } } 复制代码
这个反射调用的框架,我写了一个,希望大家多多提交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) } 复制代码
实现了这个,那么我们就开始吧,因为这些要求的,我们都可以拿到。
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的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) } 复制代码
这个文件来自于 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" } } } 复制代码
下面是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对象 } 复制代码
user-controller 就是一个tag ,对于go来说一般是group可以定义为一个 controller
// 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
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到,确实是不是特比难,是有一定规律的,那么接下来,我们就开始设计框架了。