阅读(4375) (11)

GoFrame 路由注册-规范路由

2022-04-12 11:22:10 更新

一、基本介绍

从v2.0版本开始,框架的​Server​组件提供了规范化的路由注册方式,更加适合团队规范化的使用场景,实现了以下特性:

  • 规范化API按照结构化编程设计
  • 规范化API接口方法参数风格定义
  • 更加简化的路由注册与维护
  • 统一接口返回数据格式设计
  • 保障代码与接口文档同步维护
  • 自动的API参数对象化接收与校验
  • 自动生成基于标准​OpenAPIv3​协议的接口文档
  • 自动生成​SwaggerUI​页面

二、简单示例

1、配置文件

这里使用​YAML​配置文件:​config.yaml

server:
  address:     ":8199"
  openapiPath: "/api.json"
  swaggerPath: "/swagger"

2、示例代码

我们从一个简单的​Hello​例子开始:

package main

import (
	"context"
	"fmt"

	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
)

type HelloReq struct {
	g.Meta `path:"/hello" method:"get"`
	Name   string `v:"required" dc:"Your name"`
}
type HelloRes struct {
	Reply string `dc:"Reply content"`
}

type Hello struct{}

func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) {
	g.Log().Debugf(ctx, `receive say: %+v`, req)
	res = &HelloRes{
		Reply: fmt.Sprintf(`Hi %s`, req.Name),
	}
	return
}

func main() {
	s := g.Server()
	s.Use(ghttp.MiddlewareHandlerResponse)
	s.Group("/", func(group *ghttp.RouterGroup) {
		group.Bind(
            new(Hello),
        )
	})
	s.Run()
}

拷贝这段代码,我们运行起来试试,终端输出信息如下:

2021-11-19 23:31:35.277 25580: http server started listening on [:8199] 

  SERVER  | DOMAIN  | ADDRESS | METHOD |   ROUTE    |                          HANDLER                          |    MIDDLEWARE      
----------|---------|---------|--------|------------|-----------------------------------------------------------|--------------------
  default | default | :8199   | ALL    | /*         | github.com/gogf/gf/v2/net/ghttp.MiddlewareHandlerResponse | GLOBAL MIDDLEWARE  
----------|---------|---------|--------|------------|-----------------------------------------------------------|--------------------
  default | default | :8199   | ALL    | /api.json  | github.com/gogf/gf/v2/net/ghttp.(*Server).openapiSpec-fm  |                    
----------|---------|---------|--------|------------|-----------------------------------------------------------|--------------------
  default | default | :8199   | GET    | /hello     | main.(*Hello).Say                                         |                    
----------|---------|---------|--------|------------|-----------------------------------------------------------|--------------------
  default | default | :8199   | ALL    | /swagger/* | github.com/gogf/gf/v2/net/ghttp.(*Server).swaggerUI-fm    | HOOK_BEFORE_SERVE  
----------|---------|---------|--------|------------|-----------------------------------------------------------|--------------------

可以看到,除了我们的业务路由之外,​Server​自动帮我们注册了两个路由:​/api.json​和​/swagger/*​。前者是自动生成的基于标准的​OpenAPIv3​协议的接口文档,后者是自动生成​SwaggerUI​页面,方便开发者查看和调试。这两个功能默认是关闭的,开发者可以通过前面示例中的​openapiPath​和​swaggerPath​两个配置项开启。

3、接口文档

接口文档通过​OpenAPIv3​协议生成,一般来说需要结合相应的​UI​工具查看,地址:http://127.0.0.1:8199/api.json

由于​OpenAPIv3​协议是规范的接口定义协议,因此开发者根据协议内容可以做很多事,例如:自定义​UI​展示、​Client​代码生成、协议转换等等。

4、SwaggerUI

我们来看看这个​SwaggerUI​页面:http://127.0.0.1:8199/swagger/


这里只有一个我们的路由地址以及对应的输入输出结构体。当然,这只是个简单的示例,你可以在真实的项目中通过一些配置使得页面更加丰富多彩。

我们接着在这个页面上做下接口测试吧:


嗯,接口返回了一个固定数据格式的​Json​内容,但是能看到其中的​data​为我们需要的返回结果。

5、返回中间件

等等,似乎漏掉了什么东西?是的,我们这里使用了一个​Server​组件提供的中间件,它是拿来做什么的呢?我们开看看它的方法定义:


是的,它在我们没有提供自定义的返回数据格式处理中间件时,使用了一个默认的中间件处理我们的请求,并返回了一个默认的数据格式。

三、如何使用

1、路由方法定义

从上面的例子中,我们可以看到,路由方法定义使用固定的格式:

func Handler(ctx context.Context, req *Request) (res *Response, err error)

其中输入参数和输出参数都是两个,并且都是必须的一个都不能少。简单介绍下:

参数
说明
注意事项
ctx context.Context 上下文 Server组件会自动从请求中获取并传递给接口方法
req *Request 请求对象

就算没有接收参数也要定义,因为请求结构体中不仅仅包含请求参数的定义,也包含了接口的请求定义。

res *Response 返回对象

就算没有返回参数也要定义,因为返回结构体中不仅仅包含返回参数的定义,也可以包含接口返回定义。

err error 错误对象 Server通过该参数判断接口执行成功或失败。

2、请求/返回结构体

在规范化路由注册中,非常重要的是请求/返回结构体的定义,在该结构体不仅仅包含了输入参数的定义,也包含了接口的定义,特别是路由地址、请求方法、接口描述等信息。为保证命名规范化,输入数据结构以​XxxReq​方式命名,输出数据结构以​XxxRes​方式命名。即便输入或者输出参数为空,也需要定义相应的数据结构,这样的目的一个是便于后续扩展,另一个是便于接口信息的管理。

请求参数自动转换到请求数据结构,字段映射转换不区分大小写,也会自动忽略特殊字符。


3、数据校验

请求结构体在进入API接口执行前将会被自动执行校验,如果其中一条规则校验失败,那么将终止后续规则的校验。校验功能使用的是框架统一的校验组件。

4、数据返回

接口的数据返回处理需要设置统一的后置中间件,当然也可以使用​Server​默认提供的数据返回中间件。开发者自定义中间件时可以参考​Server​默认提供的中间件。注意其中的一个重要的方法:

// GetHandlerResponse retrieves and returns the handler response object and its error. 
// return type handlerResponse struct {
//		Object interface{}
//		Error  error
//	}
func (r *Request) GetHandlerResponse() interface{} 

通过后置中间件执行时通过请求对象的​GetHandlerResponse​方法获取当前业务执行的结果,并根据需要做相应处理。

5、路由注册

我们推荐使用对象化的方式来管理所有路由方法,并通过分组路由的​Bind​方法执行统一注册。

需要注意的是,在规范化路由方式下,路由地址以及请求方式将由请求结构体在​g.Meta​中定义,通过分组路由可以定义分组下的所有路由前缀。


四、OpenAPIv3协议

Server​组件自动生成的接口文档使用的是最新的​OpenAPIv3​协议。

五、Ctx中的Request对象

我们可以通过​RequestFromCtx​/​g.RequestFromCtx​方法从​ctx​中获取​Request​对象。

方法定义:

func RequestFromCtx(ctx context.Context) *Request

使用示例:

func (c *cHello) Hello(ctx context.Context, req *apiv1.HelloReq) (res *apiv1.HelloRes, err error) {
	g.RequestFromCtx(ctx).Response.Writeln("Hello World!")
	return
}

六、常见问题

1、在规范路由下,同一接口如何支持多种HTTP Method提交方式

一个接口应当只做一件事情,​HTTP Method​是有意义的,一个接口支持多种​HTTP Method​方式是接口设计不合理,在规范路由下不支持。建议重新审视接口设计。

如果确实需要注册类似于​ALL​这种请求处理路由,在标准的​OpenAPI​协议里面不支持,可以不使用规范路由,而是使用普通的路由注册方式,将方法定义为​func(r *ghttp.Request)​即可。不同的路由注册方式可以结合使用,虽然不是很推荐。

2、在使用默认提供的Response结构体下,如何让Data字段只返回数组而无需指定名称的键值对

使用类型别名即可。

源码地址:https://github.com/gogf/gf/tree/master/example/httpserver/response_with_json_array

结果示例: