echo和swagger的结合使用(oapi-codegen使用)
相关官网:
- echo官网
- swagger
这里介绍的重点是swagger和echo的整合使用,具体的框架的使用方法请看官方文档。
1. 初衷
swagger官网提供了文档转代码的操作,但转出来的代码可用性不是很高(起码我还得再次修改一下),
我需要的是 swagger 直接转代码的能力,并且这个代码我不需要过多的操作。
兜兜转转我找到了类似的几个项目:
-
go-swagger-github
生成了一大堆代码,但这些不是我想要的,我还得修改很多,不合适
-
echo-swagger-github
注释写swagger文档,然后自动生成swagger yaml或者json文档,就像Java的swagger那样。我不喜欢这个方式,用注释来生成代替Java注解的功能这个点子很好,但这和注释结合在一起,总感觉怪怪的。
他们支持的swagger2.0,我需要的是3.0,并且他们提供的功能我不需要。于是乎找到了它
oapi-codegen
还有,需要有一个前端来显示swagger-ui。让我们可以看到文档, 并且可以利用文档调用接口,如下图所示。
2. oapi-codegen介绍
我选择它的点如下:
- 可以将文档转为代码,并且我不需要做过多的修改。
- 支持echo,gin,chi,net/http 这四种方式的整合。
- 面向对象生成controller,并且可对参数和响应做校验。
- 会生成对应接口的 query对象,requestBody对象,responseBody对象。
- 生成客户端代码。
使用方式
可以按照官网上的来,但官网的文档好像好久都没有更新了,最好看他们的example代码
https://github.com/deepmap/oapi-codegen/tree/master/examples
-
安装
go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@latest
-
写swagger文档
可以直接写按照swagger的规范,下面的演示我都是按照这个例子来的,可以直接cv Expanded Petstore
-
执行命令生成go文件
命令的参数解释如下:
using-oapi-codegen
这里执行如下:
oapi-codegen.exe -package echo_test -generate "types,client,server,spec,strict-server" -o .\gen.go .\main.yaml
参数说明:
-package: 指定包名(必须)
-generate:指定需要生成的内容
-o:指定生成的代码位置
main.yaml:这是swagger文档内容
- 实现接口方法
它已经帮我们抽象出了接口。自己写个结构体实现此接口就好了,这就像java中的controller已经被写好了。直接用就好了。
剩下的就是自己写业务逻辑代码了,它已经帮我们写好controller了,并且路径参数和query参数已经被提取出来了
- 创建echo,将生成的代码和echo的路由注册连接起来
对图中1,2,3说明如下:
- 创建代码。
- 创建我们实现serverInterce接口的结构体。
- 注册。
在生成代码的时候可以指定服务器,它默认支持的echo。它会生成对应服务器的路由注册代码
对图中1,2,3说明如下:
-
是对应的服务器对象。
-
它帮我们生成的controller接口,我们要将创建好的对象传递过来。
-
指定根路由。
-
将代码运行起来,用postman调用就可以了。
到此,它已经帮我们弄好了请求,响应对象的创建,controller的编写,录取注册。
回头再看
- 生成了swagger文档中定义的model
需要注意,文档中可为不是必须的字段model中是对应的,tag不是必须,model中tag的类型为 指针。
- 生成了controller接口
- 将请求query参数和path参数提取了出来
还有path中的id参数,直接变为了方法参数。
- 生成了请求体和响应体
参数名字为 方法名字+RequestBody
参数名字为 方法名字+http状态码+Response
- 生成了客户端代码
上面的比较复杂的,它还提供了一种很简单的如下
New开头+方法名+request就找到了
当然还有response
parse开头+方法名+response就能得到响应了。
- 对它支持的服务器有中间件(请求参数,响应校验,yaml文件校验)
需要注意,每次编辑完 swagger文件之后,都需要重新跑一下上面的命令,最新的代码
例子展示
请求参数校验
-
yaml文件
在petstore-expanded的swagger文档中的
/pets的get方法的name参数
增加如下内容:
这个参数最大值为20,最小值为12
-
重新生成一下代码
oapi-codegen.exe -package echo_test -generate "types,client,server,spec,strict-server" -o .\gen.go .\main.yaml
-
在echo中使用参数校验中间件
package main import ( middleware2 "github.com/deepmap/oapi-codegen/pkg/middleware" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" echo_test "github.com/lc/echo_test1" "github.com/lc/echo_test1/app" "net/http" ) func main() { e := echo.New() server := app.EchoServer{} echo_test.RegisterHandlersWithBaseURL(e,server,"/echo_test") // 这是oapi-codegen对我们swagger文档生成的对象 swagger, err := echo_test.GetSwagger() if err != nil { panic(err) } // 增加参数校验中间件 // 需要将swagger对象传递过去,它会对swagger对象做解析,对请求参数做校验 e.Use(middleware2.OapiRequestValidator(swagger)) err = e.Start(":8090") if err != nil { panic(err) } }
-
验证
-
还可以对参数校验做自定义操作
func main() { e := echo.New() server := app.EchoServer{} echo_test.RegisterHandlersWithBaseURL(e,server,"/echo_test") swagger, err := echo_test.GetSwagger() if err != nil { panic(err) } // 增加参数校验 //e.Use(middleware2.OapiRequestValidator(swagger)) // 自定义参数验证 options := middleware2.Options{ ErrorHandler: func(c echo.Context, err *echo.HTTPError) error { // 打印错误消息 println(err.Error()) // 这只是演示作用,强行将它改为ok return c.String(http.StatusOK, "bad request") }, } e.Use(middleware2.OapiRequestValidatorWithOptions(swagger,&options)) err = e.Start(":8090") if err != nil { panic(err) } }
请求体和响应体
在POST/pets
接口的方法AddPet
中代码改为如下
func (e EchoServer) AddPet(ctx echo.Context) error {
// 它帮我们生成好的请求体,结构体的命格式为:方法名字+json+requestBody
var addPet echo_test.AddPetJSONRequestBody
// 自己绑定参数
ctx.Bind(&addPet)
// 生成的响应体,对应java中的vo对象已经生成好了。结构体的命格式为:方法+状态码+json+Response
AddPet200JSONResponse := echo_test.Pet{
Id: 1,
Name: addPet.Name,
Tag: addPet.Tag,
}
return ctx.JSON(http.StatusOK,AddPet200JSONResponse)
}
客户端的request和response
这里我们还是对上面的方法写单元测试。
func TestEchoServer_AddPet(t *testing.T) {
var (
e = echo.New()
mock_server_addr = "/echo_test/"
tag1 = "tag1"
rec = httptest.NewRecorder()
)
echo_test.RegisterHandlersWithBaseURL(e,EchoServer{},"/echo_test")
body := echo_test.AddPetJSONRequestBody{
Name: "name1",
Tag: &tag1,
}
// 方便的方法,直接可以帮我们创建好request对象,这要放在原来的话,我们得通过 httptest.NewRequest()来自己构建了
request, _ := echo_test.NewAddPetRequest(mock_server_addr,body)
e.ServeHTTP(rec,request)
// 还生成了解析的方法,直接将原始的response传递过去就可以解析好对应的response对象了
response, err := echo_test.ParseAddPetResponse(rec.Result())
assert.Nil(t, err)
assert.Equal(t, body.Name,response.JSON200.Name)
}
swagger-ui展示
现在需要将swagger文档展示出来(在线看文档),并且还可以通过swagger来直接调用接口
想要的结果是
输入一个地址,就可以看到swagger文档。
为了做这个我们需要
- swagger文档显示(将yaml文件或者json文件转换为html页面)
- 怎么和echo做结合
swagger文档显示
swagger提供了swagger-ui
来做展示,swagger-ui-安装-github
需要注意,swagger提供了好几个版本,这些版本的表现形式,提供的功能都不一样,这些和我们本身没有关系,我们只是将swagger文档提供给它,如果不喜欢这个版本的swagger-ui,还可以自己选择一个喜欢的,替换。
这里我们采用的是直接嵌入到html里面的安装方式
如下:
怎么和echo结合
swagger ui页面能干什么事情,是由他们(框架)决定的,我们能做的就是将yaml或者json文件给它就行。
我们只需要给他一个路径,返回yaml文件或者json文件就行。
- 修改html文件,配置swagger文档请求路由
-
在echo中配置中间件,对两种特殊路由来返回不同的数据
- 获取swagger.yaml文件
- 或者swagger-ui html
代码如下:
func main() { e := echo.New() server := app.EchoServer{} echo_test.RegisterHandlersWithBaseURL(e,server,"/echo_test") e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ AllowOrigins: []string{"*"}, })) swagger, err := echo_test.GetSwagger() if err != nil { panic(err) } json, err := swagger.MarshalJSON() if err != nil { panic(err) } println(string(json)) // 这里用pre是因为下面使用了oapi-codegen的校验,必须在它前面弄。 e.Pre(func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { // 返回swagger yaml文件 if c.Request().URL.Path == "/echo_test/swagger.yaml"{ return c.File("main.yaml") } // 返回swagger html页面 if c.Request().URL.Path == "/echo_test/swagger_docs"{ return c.File("test.html") } return next(c) } }) e.Use(middleware2.OapiRequestValidator(swagger)) err = e.Start(":8090") if err != nil { panic(err) } }
小优化点
-
对swagger文档优化
上面,直接返回了swagger文档,建议使用oapi-codegen生成的swagger对象
可变量不要太多,代码都是基于swagger文档来生成的,swagger文档必须要和代码同步。如果直接返回原始的swagger文档,会有不同步的情况出现,就会造成ui页面看着是对的,但代码不对,不能第一时间发现。
修改代码如下:
swagger对象可以转换为swagger的json格式,我们以它为准。
还得修改一下html中配置url,将之前的yaml改为json
-
对html优化
上面我们是直接返回了html文件,将配置写死了,不灵活。这里建议将html直接写到代码中,将可配置的部分抽取出来,利用go的temple做参数渲染。
到这,开发流程如下:
- 写好swagger yaml文件(启动本地项目看swagger文档是否正确)
- 利用oapi-codegen来生成对应的代码。
- 实现接口,做业务逻辑。
- 利用生成的request,response model来做操作。
- 写单元测试,利用生成好的request,response来请求响应。
- 转测,自己可以在swagger-ui中调用接口做验证。
还有,我这里用的echo,oapi-codegen支持的四个服务器都可以这么做,但方法的名称可能不一样,还需要自己在对应的包,和生成的文件中找。
关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。