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