Go-web如何一步步整合swagger-ui_go web界面
Go-web如何一步步整合swagger-ui
我们可以官方提供的方式:
https://github.com/go-swagger/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 echo(UserInfoDto info) {
return repository.save(User.builder().id(info.getId()).userName(info.getName()).build());
}
}
2、转变思想
? 这个反射调用的框架,我写了一个,希望大家多多提交bug,因为反射本身缺陷很多:
https://github.com/Anthony-Dong/gorpc
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里面
https://github.com/Anthony-Dong/gorpc ,后期会考虑静态资源使用api调用的方式(转发的方式),目前是借助工具写在了go文件里:
https://github.com/a-urth/go-bindata ,这个工具很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
// 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到,确实是不是特别难,是有一定规律的,那么接下来,我们就可以开始设计框架了。