基于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 ginimport "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 errortype 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 mainimport (    "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 框架再封装了一层,将申请绑定、参数查看、异样解决、后果返回都对立封装了起来,由框架做对立流程解决。你也能够实现相应的接口,实现本人的一些业务逻辑。