api/api.go

200 lines
5.0 KiB
Go

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)
}
// 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
}
}