package api import ( "fmt" "reflect" "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" "qoobing.com/gomod/log" "qoobing.com/gomod/str" ) var ( validate = validator.New() requestLogidGetter = defaultLogidGetter requestHandlerMapper = map[string]gin.HandlerFunc{} errCodeUnknownError = -10000 errCodeParameterError = -20000 errCodeUnimplementApi = -20401 ) type Engine struct { *gin.Engine } type BaseWithEnoOutput struct { Eno int `json:"eno"` Err string `json:"err"` } type BaseWithCodeOutput struct { Code int `json:"code"` Msg string `json:"msg"` } type BaseWithErrCodeOutput struct { ErrCode int `json:"errCode"` ErrMsg string `json:"errMsg"` } func NewEngine(pre ...gin.HandlerFunc) *Engine { gin.SetMode(gin.ReleaseMode) e := gin.New() for _, m := range pre { e.Use(m) } e.Use(middlewareMyApiEngine()) return &Engine{e} } func (e *Engine) POST(uri string, handler Handler) { h := handlerWrapper(handler) log.Infof("api handler [%v] registor success", handler) e.Engine.POST(uri, h) } func (e *Engine) GET(uri string, handler Handler) { h := handlerWrapper(handler) log.Infof("api handler [%v] registor success", handler) e.Engine.GET(uri, h) } // handlerWrapper func handlerWrapper(handler Handler) gin.HandlerFunc { handlerFunc := handler.HandlerFunc() wrappedHandlerFunc := func(c *gin.Context) { // Case 1. get request handler if c.Request == nil && c.Keys != nil { c.Keys["handler"] = handler return } // Case 2. normal case, user request will go this way if icc, ok := c.Get("cc"); !ok { log.Errorf("Unreachable Code: can not get cc(*api.Context) from *gin.Context") } else if cc, ok := icc.(*Context); !ok { log.Debugf("Unreachable Code: cc from *gin.Context is type of:[%s]", reflect.TypeOf(icc).String()) log.Errorf("Unreachable Code: cc from *gin.Context is not type of *api.Context") } else { // user request should go this way defer cc.TryRecover() cc.AppId = c.GetString("appid") cc.UserId = c.GetUint64("userid") handlerFunc(cc) return } } hkey := fmt.Sprintf("%v", wrappedHandlerFunc) requestHandlerMapper[hkey] = wrappedHandlerFunc return wrappedHandlerFunc } // handlerWrapper func handlerFuncWrapper(handler HandlerFunc) gin.HandlerFunc { return func(c *gin.Context) { if icc, ok := c.Get("cc"); !ok { log.Errorf("Unreachable Code: can not get cc(*api.Context) from *gin.Context") } else if cc, ok := icc.(*Context); !ok { log.Debugf("Unreachable Code: cc from *gin.Context is type of:[%s]", reflect.TypeOf(icc).String()) log.Errorf("Unreachable Code: cc from *gin.Context is not type of *api.Context") } else { defer cc.TryRecover() cc.AppId = c.GetString("appid") cc.UserId = c.GetUint64("userid") handler(cc) return } } } // middlewareMyApiEngine myapi engine middleware func middlewareMyApiEngine() gin.HandlerFunc { return func(c *gin.Context) { // Step 1. set logid id := requestLogidGetter(c) log.SetLogid(id) defer log.Cleanup() // Step 2. init *api.Context req := c.Request log.DebugfWithDepth(1, "[%s|%s|%s]", req.Method, req.Host, req.RequestURI) cc := New(c) c.Set("cc", cc) // Step 3. do request c.Next() // Step 4. recorde metrics cc.recordMetrics() } } // defaultLogidGetter get logid from "X-Request-Id" or create a new one. func defaultLogidGetter(c *gin.Context) (logid string) { if logid = c.Request.Header.Get("X-Request-Id"); logid != "" { return logid } if logid = str.GetRandomNumString(13); logid != "" { return logid } panic("unreachable code") } func CC(c *gin.Context) *Context { if icc, ok := c.Get("cc"); !ok { log.Errorf("Unreachable Code: can not get cc(*api.Context) from *gin.Context") } else if cc, ok := icc.(*Context); !ok { log.Debugf("Unreachable Code: cc from *gin.Context is type of:[%s]", reflect.TypeOf(icc).String()) log.Errorf("Unreachable Code: cc from *gin.Context is not type of *api.Context") } else { return cc } return nil } func ApiHandler(c *gin.Context) Handler { h := c.Handler() hkey := fmt.Sprintf("%v", h) if _, ok := requestHandlerMapper[hkey]; !ok { return nil } newc := &gin.Context{ Request: nil, Keys: map[string]any{}, } h(newc) return newc.Keys["handler"].(Handler) } func RecordMetrics(api, errcode, appid string, costms float64) { var ( counter = metricApiCounter.WithLabelValues(api, errcode, appid) summary = metricApiSummary.WithLabelValues(api, errcode, appid) ) // Metric 1. api request counter counter.Inc() // Metric 2. api request cost/latency summary.Observe(costms) } func DumpHandlerFunc(c *Context) error { errmsg := "api not implemented" errcode := errCodeUnimplementApi return c.RESULT_ERROR(errcode, errmsg) } func ErrorHandler(c *gin.Context, errcode int, errmsg string) { handler := handlerFuncWrapper(func(c *Context) error { return c.RESULT_ERROR(errcode, errmsg) }) handler(c) } func InitErrCodes(errcodes map[string]int) { if code, ok := errcodes["ErrCodeUnknownError"]; ok { errCodeUnknownError = code } if code, ok := errcodes["ErrCodeUnimplementApi"]; ok { errCodeUnknownError = code } if code, ok := errcodes["errCodeParameterError"]; ok { errCodeParameterError = code } }