美文网首页
基于gin的golang开发框架整理-01

基于gin的golang开发框架整理-01

作者: robertzhai | 来源:发表于2022-06-25 20:08 被阅读0次

背景

(1) 部门之前的框架是一个自研的mvc框架,依赖的旧lib库很多,没有迭代更新了,有一些历史的bug,暂时没精力维护,为了能提高开发效率,内部发起了重新封装一个新框架的想法,目前已经在新项目使用,运行1年+,比较稳定,还在持续迭代更新中

(2) 调研了目前的已有框架, 后来选择了gin 作为基础框架进行二次开发

新框架介绍

(1) 采用go 1.17 , 比现有 1.13 性能好

  - 提高了 defer 的性能,几乎是零开销

  -  库的升级和性能优化

  -  ......

(2)新框架组成部分

 路由(gin) 、redis、kafka、ucm配置、中间件、promethus监控、zap logger(日志滚动)、memcache、gorm 、graphql 等

(3) project layout


.
├── Makefile
├── README.md
├── app
│   ├── default
│   ├── example
├── cmd
│   └── main
├── config
│   ├── app_prod.yaml
│   └── app_test.yaml
├── docker
│   ├── Dockerfile
│   ├── docker-compose.yml
│   └── env
├── docs
├── go.mod
├── go.sum
├── library
├── routers
│   ├── monitor.go
│   └── routers.go
└── scripts
├── folder
└── mysql_tools
单个app下面的 pkg 目录结构
├── controller (CRSM分层) 控制层(c->r->s->m)
│   └── api
├── middleware
├── model  DAO层
│   └── xxxModel.go
├── repository 业务聚合层(可不用)
│   └── echo_repository
├── router
│   └── router.go
├── service 业务逻辑(服务)层
│   └── echo_service.go
├── util
└── worker

(4) 路由


type Option func(*gin.Engine)
 
var options = []Option{}
 
// 注册app的路由配置
func Include(opts ...Option) {
   options = append(options, opts...)
}
 
// 初始化
func Init() *gin.Engine {
   // 加载多个APP的路由配置
   //Include(example_router.Routers, Health, gql_router.GplRouters, default_router.Routers)
   Include(Health, default_router.Routers)
   r := gin.New()
   for _, opt := range options {
      opt(r)
   }
   r.SetTrustedProxies(nil)
   return r
}
 
 
// 路由组
func Routers(engine *gin.Engine) {
   // 外部api
   apiRouters(engine)
}
 
// 外部api
func apiRouters(engine *gin.Engine) {
   apiGroup := engine.Group("/go_preg_user/api", gin.Recovery(), middleware.IpMiddleware(), middleware.RequestIdMiddleware(), api_auth.ApiAuth(), middleware.FormatJsonMiddleware())
   {
      apiGroup.GET("/cookie/check_login", api.LoginController.CheckLoginAction)
     
   }
}

gin 路由结构Radix Tree(压缩前缀数, 空间换时间,效率比hash 高,可以支持正则匹配)


image.png

在gin的路由中,每一个Http Method(GET, PUT, POST…)都对应了一棵 radix tree

func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
    // ...
     
    // 获取method对应的树,如果没有就创建
    root := engine.trees.get(method)
    if root == nil {
        // 创建radix tree,只有根节点
        root = new(node)
        engine.trees = append(engine.trees, methodTree{method: method, root: root})
    }
    root.addRoute(path, handlers)
}
type node struct {
    // 节点路径,比如上面的s,earch,和upport
    path      string
    // 节点是否是参数节点,比如上面的:post
    wildChild bool
    // 节点类型,包括static, root, param, catchAll
    // static: 静态节点,比如上面的s,earch等节点
    // root: 树的根节点
    // catchAll: 有*匹配的节点
    // param: 参数节点
    nType     nodeType
    // 路径上最大参数个数
    maxParams uint8
    // 和children字段对应, 保存的是分裂的分支的第一个字符
    // 例如search和support, 那么s节点的indices对应的"eu"
    // 代表有两个分支, 分支的首字母分别是e和u
    indices   string
    // 儿子节点
    children  []*node
    // 处理函数
    handlers  HandlersChain
    // 优先级,子节点注册的handler数量
    priority  uint32
}
 
 
radix tree可以被认为是一棵简洁版的前缀树。拥有共同前缀的节点也共享同一个父节点。下面是一个GET方法对应的路由树的结构:
Priority   Path             Handle
9          \                *<1>
3          ├s               nil
2          |├earch\         *<2>
1          |└upport\        *<3>
2          ├blog\           *<4>
1          |    └:post      nil
1          |         └\     *<5>
2          ├about-us\       *<6>
1          |        └team\  *<7>
1          └contact\        *<8>
 
*<num>是方法(handler)对应的指针,从根节点遍历到叶子节点我们就能得到完整的路由表,图中的示例实现了以下路由:
 
 
GET("/", func1)
GET("/search/", func2)
GET("/support/", func3)
GET("/blog/", func4)
GET("/blog/:post/", func5)
GET("/about-us/", func6)
GET("/about-us/team/", func7)
GET("/contact/", func8)
 
:post是真实的post name的一个占位符(就是一个参数)。这里体现了radix tree相较于hash-map的一个优点,树结构允许我们的路径中存在动态的部分(参数),因为我们匹配的是路由的模式而不是hash值

(5) 拦截器

可以对每个路由group 进行单独设置,按顺序进行执行

 apiGroup := engine.Group("/go_user/api", gin.Recovery(), middleware.IpMiddleware(), middleware.RequestIdMiddleware(), api_auth.ApiAuth(), middleware.FormatJsonMiddleware())

(6) 控制器

type cookieController struct {
   *controller.BaseController
}
 
var CookieController = new(cookieController)
 
// http://localhost:900/go_user/api/user/check_login
func (ctr cookieController) CheckLoginAction(ctx *gin.Context) {
 
   params := &common.CheckLoginParams{}
   if ctx.ShouldBind(params) != nil || (!params.Validate(ctx)) {
      ctr.Fail(ctx, nil, "参数错误")
      return
   }
 
   if user_repository.GetRepository().IsLogin(ctx, params) {
      result := common.CheckLoginResponse{
         xx: params.xx,
      }
      zap_logger.Infof(ctx, common.TAG_NAME, "check succ params:%+v", params)
      ctr.Success(ctx, result)
   } else {
      
      zap_logger.Warnf(ctx, common.TAG_NAME, "check fail params:%+v", params)
      ctr.Fail(ctx, nil, "不正确")
   }
 
}

(7) model 对应表生成 struct 工具

go install github.com/Shelnutt2/db2struct/cmd/db2struct@latest
~/gopath/bin/db2struct --host 172.20.11.25 -d Message -t UserMessage --package model --struct UserMessageModel -p xx --user xx  --json  --gorm --target=./model/UserMessageModel.go

package model
 
import "database/sql"
 
type UserMessageModel struct {
   UserID int            `gorm:"column:user_id;primary_key" json:"user_id"` //
   ViewTs sql.NullString `gorm:"column:view_ts" json:"view_ts"`             //
}
 
// TableName sets the insert table name for this struct type
func (u *UserMessageModel) TableName() string {
   return "UserMessage"
}
 
func (u *UserMessageModel) DbName() string {
   return "Message"
}

(8) 新框架运行流程

main => 解析配置文件 =》 初始化 ucm 、redis 、mysql 、memcache => 注册路由 和  promethus metrics => 监听端口,等待客户端连接

参考

相关文章

网友评论

      本文标题:基于gin的golang开发框架整理-01

      本文链接:https://www.haomeiwen.com/subject/crgrvrtx.html