2

基于Gin Web 框架封住自己的业务框架

在使用Gin开发过程中遇到的问题

  • 繁琐的请求绑定、参数检查、异常处理、Error日志等,太多重复低效的代码,淹没了真正的业务逻辑
    在这里插入图片描述

基于Gin Web 框架封装需要支持的地方

  • 原则:尽量保持和Gin 的API 一致

    聚焦痛点问题

  • 自动请求绑定:框架自动绑定请求到具体结构,开发同学可以直接处理业务
  • 参数校验:抽离校验过程,框架自动调用,开发同学只需要定义具体校验内容
  • 错误处理方案:设计清晰的错误处理流程
  • 更多的中间件支持

提供的其他功能

优雅重启: 监听信号量
func (h *hook) WithSignals(signals ...syscall.Signal) Hook {
    for _, s := range signals {
        signal.Notify(h.ctx, s)
    }

    return h
}

func (h *hook) Close(funcs ...func()) {
    select {
    case <-h.ctx:
    }
    signal.Stop(h.ctx)

    for _, f := range funcs {
        f()
    }
}

func (d *DefaultServerPlugin) AfterStop(ctx context.Context) error {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    err := d.engine.server.Shutdown(ctx)
    if err != nil {
        debugPrint("server shutdown error: %v", err.Error())
    }
    fmt.Print("DefaultServerPlugin AfterStop\n")
    return nil
}
统一的错误处理,定义接口
type Decode interface {
    DecodeErr(err error) (int, string)
}

func (d *DefaultResponses) Response(ctx *gin.Context, result interface{}, err error, DecodeImp Decode){
    // 可以在这里实现错误err处理
    code, message := DecodeImp.DecodeErr(err)
    if out, ok := result.(Output);ok{
        out.Output() // 在输出的时候自己做转换
    }
    ctx.JSON(http.StatusOK, response{
        Code: uint64(code),
        Message: message,
        Data: result,
    })
}
请求和响应

##### 请求:提供 接口做参数校验和格式转化

type Request interface {
    Validator(ctx context.Context) error
}

##### 响应:提供 接口最对外输出的格式转化

type Responses interface {
    Response(*gin.Context, interface{}, error, Decode)
}
错误处理方案

package gin

import "fmt"

type Decode interface {
    DecodeErr(err error) (int, string)
}

type DecodeImp struct {
}


var (
    // Common errors
    OK                  = &Errno{Code: 0, Message: "OK"}
    InternalServerError = &Errno{Code: 10001, Message: "Internal server error"}
)

// Errno ...
type Errno struct {
    Code    int
    Message string
}

func (err Errno) Error() string {
    return err.Message
}

// Err represents an error
type Err struct {
    Code    int
    Message string
    Err     error
}

func (err *Err) Error() string {
    return fmt.Sprintf("Err - code: %d, message: %s, error: %s", err.Code, err.Message, err.Err)
}


func newDefaultDecode() Decode {
    return &DecodeImp{}
}

// DecodeErr ...
func (decodeImp *DecodeImp) DecodeErr(err error) (int, string) {
    if err == nil {
        return OK.Code, OK.Message
    }

    switch typed := err.(type) {
    case *Err:
        return typed.Code, typed.Message
    case *Errno:
        return typed.Code, typed.Message
    default:
    }

    return InternalServerError.Code, err.Error()
}

func(engine *Engine) SetDecode(decodeImp Decode){
    engine.errDecode = decodeImp
}
中间键支持
限流插件
func RateLimitMiddleware(fillInterval time.Duration, cap int64) func(c *rgin.Context) {
    bucket := ratelimit.NewBucket(fillInterval, cap)
    return func(c *rgin.Context) {
        // 如果取不到令牌就中断本次请求返回 rate limit...
        if bucket.TakeAvailable(1) < 1 {
            c.String(http.StatusOK, "rate limit...")
            c.Abort()
            return
        }
        c.Next()
    }
}
pprof

func (engine *Engine) registerAPI(){
    if engine.enableHealthCheck{
        engine.Engine.GET("/health", Health)
    }
    if engine.enablePprof{
        pprof.Register(engine.Engine)
    }
    // 自动注册路由
    if err := engine.registerModelToRouter();err!=nil{
        panic(err)
    }

}

整体框架运行流程

在这里插入图片描述

Demo 例子


package main

import (
    "context"
    
    serverGin "github.com/colinrs/pkgx/server/gin"
)


type User struct {

}

type UseReq struct {
    Message string `form:"message" json:"message"`
}

type UseResp struct {
    Message string `form:"message" json:"message"`
}

func (useReq *UseReq) Validator(ctx context.Context) error{
    return nil
}


func UserHello(ctx context.Context, req *UseReq) (*UseResp, error){
 resp := &UseResp{}
 resp.Message = req.Message
 return resp,nil
}



func (u *User) Init(engine *serverGin.Engine) error{
    engine.GET("/user", UserHello)
    return nil
}

func main(){
    engine := serverGin.Default()
    models := []serverGin.Model{new(User)}
    engine.RegisterModel(models)
    engine.Run(":6969")
}

运行结果

在这里插入图片描述
在这里插入图片描述
curl --location --request GET 'http://127.0.0.1:6969/user?message=hello,world'

帮你自动注册好了,/health 接口和 pprof 接口
而且,业务逻辑的处理边的简单很多,只需要关注req 输入,直接将输出 return 就可以了。

Default 做了什么

func Default() *Engine {
    debugPrintWARNINGDefault()
    reneging := rgin.Default() // 原生 gin的 Default
    engine := &Engine{
        Engine:reneging,
        enablePprof:true, // 是否开启 pprof
        enableHealthCheck:true, // 是否自动注册 health 接口
        enablePrometheus: true, // 是否注册Prometheus,这部分现在未支持
        models: []Model{}, // api 注册
        hook: shutdown.NewHook(), // 监听信号量
        errChan: make(chan error), // 未使用
    }
    engine.serverPlugin = newDefaultServerPlugin(engine) // 默认的服务插件
    engine.responses = newDefaultResponses() // 默认的返回格式
    engine.errDecode = newDefaultDecode() // 默认的错误解析
    return engine
}

Run 做了什么


func (engine *Engine) Run(addr ...string) (err error) {
    defer func() { debugPrintError(err) }()
    engine.registerAPI() // 注册API,包括基本的和业务上的
    address := resolveAddress(addr)
    server := &http.Server{Addr: address, Handler: engine.Engine}
    engine.server = server
    engine.serverPlugin.BeforeRun(context.Background()) // 在服务启动前做的操作。你也可以自己实现 ServerPlugin 接口,然后使用 SetserverPlugin 注册进来
    go func() {
        debugPrint("Listening and serving HTTP on %s\n", address)
        debugPrint("Listening and serving HTTP err:%s\n", server.ListenAndServe().Error())
    }()
    resourceCleanups := []func(){
        func() {
            engine.serverPlugin.AfterStop(context.Background()) // 关闭之后可以做什么
        },
    }
    resourceCleanups = append(resourceCleanups, engine.resourceCleanup...)
    engine.hook.Close(resourceCleanups...) // 资源清理, 关闭服务器
    return
}

总结

serverGin 是在原生的 Gin Web 框架再封装了一层,将请求绑定、参数检查、异常处理、结果返回都统一封装了起来,由框架做统一流程处理。你也可以实现相应的接口,实现自己的一些业务逻辑。


djjk
20 声望2 粉丝

2014~2018年就读河北农业大学、本科、网络工程。