package api import ( "encoding/json" "fmt" "reflect" "strings" "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{} apicfg = Config{ OutputErrCodeName: "errCode", outputErrCodeFieldName: "ErrCode", OutputErrMessageName: "errMessage", outputErrMessageFiledName: "ErrMessage", ErrCodeUnknownError: -10000, ErrCodeParameterError: -20000, ErrCodeUnimplementApi: -20401, } ) type Engine struct { *gin.Engine } 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("%p", 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 cc := New(c) cc.Set("cc", cc) defer cc.addApiCallNoticeLog()() // Step 3. do request cc.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("%p", 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 := apicfg.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) } // deprecated, use "BaseWithErrCodeOutput + api.Init" instead type BaseWithEnoOutput = BaseWithErrCodeOutput // deprecated, use "BaseWithErrCodeOutput + api.Init" instead type BaseWithCodeOutput = BaseWithErrCodeOutput type BaseWithErrCodeOutput struct { ErrCode int `json:"errCode"` ErrMessage string `json:"errMessage"` } func (out BaseWithErrCodeOutput) MarshalJSON() ([]byte, error) { var o = map[string]any{ apicfg.OutputErrCodeName: out.ErrCode, apicfg.OutputErrMessageName: out.ErrMessage, } return json.Marshal(o) } type Config struct { OutputErrCodeName string outputErrCodeFieldName string OutputErrMessageName string outputErrMessageFiledName string ErrCodeUnknownError int ErrCodeParameterError int ErrCodeUnimplementApi int } func Init(cfg Config) error { if cfg.OutputErrCodeName == apicfg.OutputErrCodeName { apicfg.OutputErrCodeName = cfg.OutputErrCodeName } else if cfg.OutputErrCodeName != apicfg.OutputErrCodeName && cfg.OutputErrCodeName == "errCode" { apicfg.OutputErrCodeName = cfg.OutputErrCodeName } else if cfg.OutputErrCodeName != apicfg.OutputErrCodeName && cfg.OutputErrCodeName != "errCode" { log.Warningf("!!!!!!!!!!!!! api OutputErrCodeName "+ "have been setted to: %s, overwrite it by %s", apicfg.OutputErrCodeName, cfg.OutputErrCodeName) } if apicfg.OutputErrCodeName == "" { return fmt.Errorf("OutputErrCodeName must not be empty") } if cfg.OutputErrMessageName == apicfg.OutputErrMessageName { apicfg.OutputErrMessageName = cfg.OutputErrMessageName } else if cfg.OutputErrMessageName != apicfg.OutputErrMessageName && cfg.OutputErrMessageName == "errMessage" { apicfg.OutputErrMessageName = cfg.OutputErrMessageName } else if cfg.OutputErrMessageName != apicfg.OutputErrMessageName && cfg.OutputErrMessageName != "errMessage" { log.Warningf("!!!!!!!!!!!!! api OutputErrMessageName "+ "have been setted to: %s, overwrite it by %s", apicfg.OutputErrMessageName, cfg.OutputErrMessageName) } if apicfg.OutputErrMessageName == "" { return fmt.Errorf("OutputErrMessageName must not be empty") } if cfg.ErrCodeUnknownError != 0 { apicfg.ErrCodeUnknownError = cfg.ErrCodeUnknownError } if cfg.ErrCodeParameterError != 0 { apicfg.ErrCodeParameterError = cfg.ErrCodeParameterError } if cfg.ErrCodeUnimplementApi != 0 { apicfg.ErrCodeUnimplementApi = cfg.ErrCodeUnimplementApi } apicfg.outputErrCodeFieldName = strings.ToUpper(apicfg.OutputErrCodeName[0:1]) + apicfg.OutputErrCodeName[1:] apicfg.outputErrMessageFiledName = strings.ToUpper(apicfg.OutputErrMessageName[0:1]) + apicfg.OutputErrMessageName[1:] return nil }