256 lines
7.4 KiB
Go
256 lines
7.4 KiB
Go
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
|
|
}
|