09|自研or借力(下):集成Gin替换已有核心

文章讲述了在Golang项目中如何将Gin框架迁移到自定义框架内,主要涉及两种引入方式:通过Gomod和复制源码。由于Gomod不支持对第三方库的源码修改,因此选择了复制源码的方法。迁移过程中,只对Request和Response进行了封装,通过添加新接口并重命名冲突函数来最小化改动。其他如Context、路由和中间件等组件保持不变。
摘要由CSDN通过智能技术生成

如何将 Gin 迁移进入我们的框架

在 Golang 中,要在一个项目中引入另外一个项目,一般有两种做法,一种做法是把要引用的项目通过 go mod 引入到目标库中,而另外一种做法则费劲的多,使用复制源码的方式引入要引用的项目。

go mod 是 Go 官方提供的第三方库管理工具。go mod 的使用方式是在代码方面,也就是在你要使用第三方库功能的时候,使用 import 关键字进行引用。它的好处是简单方便,我们直接使用官方提供的 go get 方法,就能将某个框架进行使用和升级。

但是如果希望对第三方库进行源码级别的修改,go mod 的方式就会受到限制了。比如我们使用了 Gin 项目,想扩展项目中的 Context 这个结构,为其增加一个函数方法 A,这个需求用 go mod 的方式是做不到的。

go mod 的方式提倡的是“使用第三方库”而不是“定制第三方库”。所以对于很强的定制第三方库的需求,我们只能选择复制源码的方式。

主要步骤为:

  • 在framework目录下创建gin目录,将gin@v1.7.3源码copy进来
  • 将gin的go.mod中require复制到项目的go.mod中,删除gin的go.mod和go.sum
  • gin中原有的引用地址github.com/gin-gonic/gin统一替换为本项目地址github.com/wojh217/hade/framework/gin

如何迁移

目前已实现的模块:

  • Context: 请求控制器,控制每个请求的超时等逻辑;
  • 路由: 让请求更快寻找目标函数,并且支持通配符、分组等方式制定路由规则;
  • 中间件:能将通用逻辑转化为中间件,并串联中间件实现业务逻辑;
  • 封装: 提供易用的逻辑,把 request 和 response 封装在 Context 结构中;
  • 重启: 实现优雅关闭机制,让服务可以重启。

在Gin的框架中,Context、路由、中间件,都已经有了 Gin 自己的实现。

Context 方面,Gin 的实现基本和我们之前的实现是一致的。之前实现的 Core 数据结构对应 Gin 中的 Engine,Group 数据结构对应 Gin 的 Group 结构,Context 数据结构对应 Gin 的 Context 数据结构。

路由方面,Gin 的路由实现得比我们要好。

中间件方面,Gin 的中间件实现与我们实现的差别不大,只是定义的Handler不同:

// 我们定义的
type ControllerHandler func(c *Context) error

// gin定义的
type HandlerFunc func(*Context)

Gin 的作者认为,中断一个请求返回一个 error 并没有什么用,他希望中断一个请求的时候直接操作 Response,比如设置返回状态码、设置返回错误信息,而不希望用 error 来进行返回,所以框架也不会用这个 error 来操作任何的返回信息。

Gin对Request和Response的封装较为简单,且没有链式调用,我们可以迁移这部分封装。

最后一个优雅关闭的逻辑,我们和 Gin 都是直接使用 HTTP 库的 server.Shutdown 实现的,不受 Gin 代码迁移的影响。

所以再明确下,context、路由、中间件、重启机制,我们都不需要迁移,唯一需要迁移的是对 Request 和 Response 的封装。

使用加法最小化和定制化我们的需求

对于request和response的封装,使用加法最小化的方式进行迁移,既能保持自己的定制化需求,又不影响Gin原有的代码逻辑。

创建两个新的文件,hade_request.go和hade_response.go,存放这两个接口。

现在要让Gin的Ctx也要实现这两个接口,那么:

  • Gin中已有接口中相同参数、返回值、函数名的函数,则不必再写
  • Gin中不存在相同函数名,则自己再实现一遍
  • Gin中存在相同函数名,但返回值不同,这时候为了影响,将接口里的函数名重命名,比如IRequest中QueryXXX系列和Gin中Query系列冲突,我们改为DefaultQueryXXX

因此,IRequest 的定义修改为:


// 代表请求包含的方法
type IRequest interface {

  // 请求地址url中带的参数
  // 形如: foo.com?a=1&b=bar&c[]=bar
  DefaultQueryInt(key string, def int) (int, bool)
  DefaultQueryInt64(key string, def int64) (int64, bool)
  DefaultQueryFloat64(key string, def float64) (float64, bool)
  DefaultQueryFloat32(key string, def float32) (float32, bool)
  DefaultQueryBool(key string, def bool) (bool, bool)
  DefaultQueryString(key string, def string) (string, bool)
  DefaultQueryStringSlice(key string, def []string) ([]string, bool)

  // 路由匹配中带的参数
  // 形如 /book/:id
  DefaultParamInt(key string, def int) (int, bool)
  DefaultParamInt64(key string, def int64) (int64, bool)
  DefaultParamFloat64(key string, def float64) (float64, bool)
  DefaultParamFloat32(key string, def float32) (float32, bool)
  DefaultParamBool(key string, def bool) (bool, bool)
  DefaultParamString(key string, def string) (string, bool)
  DefaultParam(key string) interface{}

  // form表单中带的参数
  DefaultFormInt(key string, def int) (int, bool)
  DefaultFormInt64(key string, def int64) (int64, bool)
  DefaultFormFloat64(key string, def float64) (float64, bool)
  DefaultFormFloat32(key string, def float32) (float32, bool)
  DefaultFormBool(key string, def bool) (bool, bool)
  DefaultFormString(key string, def string) (string, bool)
  DefaultFormStringSlice(key string, def []string) ([]string, bool)
  DefaultFormFile(key string) (*multipart.FileHeader, error)
  DefaultForm(key string) interface{}

  // json body
  BindJson(obj interface{}) error

  // xml body
  BindXml(obj interface{}) error

  // 其他格式
  GetRawData() ([]byte, error)

  // 基础信息
  Uri() string
  Method() string
  Host() string
  ClientIp() string

  // header
  Headers() map[string]string
  Header(key string) (string, bool)

  // cookie
  Cookies() map[string]string
  Cookie(key string) (string, bool)
}



// IResponse代表返回方法
type IResponse interface {
  // Json输出
  IJson(obj interface{}) IResponse

  // Jsonp输出
  IJsonp(obj interface{}) IResponse

  //xml输出
  IXml(obj interface{}) IResponse

  // html输出
  IHtml(template string, obj interface{}) IResponse

  // string
  IText(format string, values ...interface{}) IResponse

  // 重定向
  IRedirect(path string) IResponse

  // header
  ISetHeader(key string, val string) IResponse

  // Cookie
  ISetCookie(key string, val string, maxAge int, path, domain string, secure, httpOnly bool) IResponse

  // 设置状态码
  ISetStatus(code int) IResponse

  // 设置200状态
  ISetOkStatus() IResponse
}

接口和实现都修改了,最后肯定还要对应修改下业务代码中之前定义的一些控制器。

  • 控制器的参数,从 framework.Context 修改为 gin.Context,返回值从error改为无返回值
  • 修改Response 方法调用,签名都带上了一个前缀 I

【小结】
我们的框架和gin相比,具有类似的Ctx封装、group分组,路由分组、handler有所不同,本次改造针对Request和Response的再次封装。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值