diff --git a/api.go b/api.go index b45546a..23b9116 100644 --- a/api.go +++ b/api.go @@ -1,8 +1,10 @@ package api import ( + "encoding/json" "fmt" "reflect" + "strings" "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" @@ -11,33 +13,24 @@ import ( ) var ( - validate = validator.New() - requestLogidGetter = defaultLogidGetter - requestHandlerMapper = map[string]gin.HandlerFunc{} - errCodeUnknownError = -10000 - errCodeParameterError = -20000 - errCodeUnimplementApi = -20401 + 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 } -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() @@ -85,7 +78,8 @@ func handlerWrapper(handler Handler) gin.HandlerFunc { return } } - hkey := fmt.Sprintf("%v", wrappedHandlerFunc) + + hkey := fmt.Sprintf("%p", wrappedHandlerFunc) requestHandlerMapper[hkey] = wrappedHandlerFunc return wrappedHandlerFunc } @@ -154,7 +148,7 @@ func CC(c *gin.Context) *Context { func ApiHandler(c *gin.Context) Handler { h := c.Handler() - hkey := fmt.Sprintf("%v", h) + hkey := fmt.Sprintf("%p", h) if _, ok := requestHandlerMapper[hkey]; !ok { return nil } @@ -180,7 +174,7 @@ func RecordMetrics(api, errcode, appid string, costms float64) { func DumpHandlerFunc(c *Context) error { errmsg := "api not implemented" - errcode := errCodeUnimplementApi + errcode := apicfg.ErrCodeUnimplementApi return c.RESULT_ERROR(errcode, errmsg) } @@ -191,14 +185,71 @@ func ErrorHandler(c *gin.Context, errcode int, errmsg string) { 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 - } +// 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 } diff --git a/context.go b/context.go index 5270c2e..853755a 100644 --- a/context.go +++ b/context.go @@ -19,7 +19,6 @@ import ( type Context struct { *gin.Context - e *gin.Engine AppId string //current request appid, must not be empty UserId uint64 //current request userid, maybe 0 if user unknown @@ -27,7 +26,8 @@ type Context struct { resultentry string httpstatus int starttime time.Time - output interface{} // case 1: for user get real errcode; case 2: for notice log + output interface{} // for notice log + outputBind interface{} // for user get real errcode noticedepth int errcode int } @@ -52,10 +52,10 @@ func (c *Context) TryRecover() { switch t.Kind() { case reflect.String: log.Fatalf("panic err:%v. Panic stack:\n%s", err, stack) - c.RESULT_ERROR(errCodeUnknownError, "unknown error") + c.RESULT_ERROR(apicfg.ErrCodeUnknownError, "unknown error") default: log.Fatalf("panic err type: %s:%s. Panic stack:\n%s", t.Name(), t.String(), stack) - c.RESULT_ERROR(errCodeUnknownError, "unknown error") + c.RESULT_ERROR(apicfg.ErrCodeUnknownError, "unknown error") } } } @@ -91,7 +91,7 @@ func (c *Context) BindInput(i interface{}) (err error) { return nil } -func (c *Context) BindOutput(o interface{}) (err error) { +func (c *Context) BindOutput(o any) (err error) { var v = reflect.ValueOf(o) if v.Kind() != reflect.Pointer { return fmt.Errorf("output is NOT a pointer") @@ -99,18 +99,18 @@ func (c *Context) BindOutput(o interface{}) (err error) { var e = v.Elem() var t = e.Type() - if _, ok := t.FieldByName("ErrCode"); !ok { - return fmt.Errorf("out have no field ErrCode") + if _, ok := t.FieldByName(apicfg.outputErrCodeFieldName); !ok { + return fmt.Errorf("out have no field '%s'", apicfg.outputErrCodeFieldName) } - if _, ok := t.FieldByName("ErrMsg"); !ok { - return fmt.Errorf("out have no field ErrMsg") + if _, ok := t.FieldByName(apicfg.outputErrMessageFiledName); !ok { + return fmt.Errorf("out have no field '%s'", apicfg.outputErrMessageFiledName) } - if code := e.FieldByName("ErrCode"); !code.CanSet() { - return fmt.Errorf("output.ErrCode can not be set, " + - "output is NOT a pointer or output have no field 'ErrCode'") + if code := e.FieldByName(apicfg.outputErrCodeFieldName); !code.CanSet() { + return fmt.Errorf("output.%s can not be set, output is NOT a pointer", apicfg.outputErrCodeFieldName) } c.output = o + c.outputBind = o return nil } @@ -125,13 +125,12 @@ func (c *Context) AlreadyHaveResult() (done bool, entry string) { return c.resultentry != "", c.resultentry } -func (c *Context) RESULT(output interface{}) error { +func (c *Context) RESULT(output any) error { if c.resultentry == "" { c.resultentry = "RESULT" } var t = reflect.TypeOf(output) defer func(o *interface{}) { - calldepth := 0 switch c.resultentry { case "RESULT": @@ -145,26 +144,33 @@ func (c *Context) RESULT(output interface{}) error { c.noticedepth = calldepth }(&output) - if _, ok := t.FieldByName("ErrCode"); !ok { - errstr := "Result MUST have 'ErrCode' field" - output = BaseWithErrCodeOutput{errCodeUnknownError, errstr} + if c.resultentry != "RESULT" { c.shapeOutput(output) - c.errcode = int(reflect.ValueOf(output).FieldByName("ErrCode").Int()) + c.errcode = output.(BaseWithErrCodeOutput).ErrCode + c.IndentedJSON(c.httpstatus, output) + return nil + } + + if _, ok := t.FieldByName(apicfg.outputErrCodeFieldName); !ok { + errstr := fmt.Sprintf("Result MUST have '%s' field", apicfg.outputErrCodeFieldName) + output = BaseWithErrCodeOutput{apicfg.ErrCodeUnknownError, errstr} + c.shapeOutput(output) + c.errcode = output.(BaseWithErrCodeOutput).ErrCode c.IndentedJSON(c.httpstatus, output) return errors.New(errstr) } - if _, ok := t.FieldByName("ErrMsg"); !ok { - errstr := "Result MUST have 'ErrMsg' field" - output = BaseWithErrCodeOutput{errCodeUnknownError, errstr} + if _, ok := t.FieldByName(apicfg.outputErrMessageFiledName); !ok { + errstr := fmt.Sprintf("Result MUST have '%s' field", apicfg.outputErrMessageFiledName) + output = BaseWithErrCodeOutput{apicfg.ErrCodeUnknownError, errstr} c.shapeOutput(output) - c.errcode = int(reflect.ValueOf(output).FieldByName("ErrCode").Int()) + c.errcode = output.(BaseWithErrCodeOutput).ErrCode c.IndentedJSON(c.httpstatus, output) return errors.New(errstr) } c.shapeOutput(output) - c.errcode = int(reflect.ValueOf(output).FieldByName("ErrCode").Int()) + c.errcode = int(reflect.ValueOf(output).FieldByName(apicfg.outputErrCodeFieldName).Int()) c.IndentedJSON(c.httpstatus, output) return nil } @@ -183,7 +189,7 @@ func (c *Context) RESULT_PARAMETER_ERROR(err string) error { if c.resultentry == "" { c.resultentry = "RESULT_PARAMETER_ERROR" } - return c.RESULT_ERROR(errCodeParameterError, err) + return c.RESULT_ERROR(apicfg.ErrCodeParameterError, err) } func (c *Context) ApiHandler() Handler { @@ -198,20 +204,19 @@ func (c *Context) ApiHandlerWithNoNil() Handler { return h } -func (c *Context) shapeOutput(o interface{}) { - if c.output == nil { +func (c *Context) shapeOutput(outputInnnerError any) { + if c.outputBind == nil { return } - var out, ok = o.(BaseWithErrCodeOutput) + var out, ok = outputInnnerError.(BaseWithErrCodeOutput) if !ok { return } - var e = reflect.ValueOf(c.output).Elem() - e.FieldByName("ErrMsg").SetString(out.ErrMsg) - e.FieldByName("ErrCode").SetInt(int64(out.ErrCode)) - return + var outputBind = reflect.ValueOf(c.outputBind).Elem() + outputBind.FieldByName(apicfg.outputErrCodeFieldName).SetInt(int64(out.ErrCode)) + outputBind.FieldByName(apicfg.outputErrMessageFiledName).SetString(out.ErrMessage) } func (c *Context) addApiCallNoticeLog() func() { @@ -221,16 +226,16 @@ func (c *Context) addApiCallNoticeLog() func() { var ( o = c.output b, err = json.Marshal(o) - costus = time.Now().Sub(c.starttime).Microseconds() + costus = time.Since(c.starttime).Microseconds() costms = float64(costus) / 1000.0 ) if err != nil { - b = []byte(fmt.Sprintf("marshal output error:%s", err)) + b = []byte(fmt.Sprintf("marshal output error: %s", err)) } if len(b) > 1024 { // cut log if too long b = append(b[0:1000], b[len(b)-24:]...) } - log.Noticef("[<<<<<<:%s,code:%d,cost:%dms] output: '%s", h.ApiName(), c.errcode, int64(costms), string(b)) + log.Noticef("[<<<<<<:%s,code:%d,cost:%dms] output: %s", h.ApiName(), c.errcode, int64(costms), string(b)) } } @@ -243,7 +248,7 @@ func (c *Context) recordMetrics() { var ( api = h.ApiName() appid = c.AppId - costus = time.Now().Sub(c.starttime).Microseconds() + costus = time.Since(c.starttime).Microseconds() costms = float64(costus) / 1000.0 errcode = strconv.Itoa(c.errcode) ) diff --git a/go.mod b/go.mod index 09d8802..b7c7a65 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,40 @@ module qoobing.com/gomod/api -go 1.19 +go 1.22 + +toolchain go1.23.6 require ( github.com/gin-gonic/gin v1.8.1 github.com/go-playground/validator/v10 v10.11.1 - github.com/tylerb/gls v0.0.0-20150407001822-e606233f194d // indirect - github.com/tylerb/is v2.1.4+incompatible // indirect - qoobing.com/gomod/log v1.1.0 - qoobing.com/gomod/str v1.0.3 + github.com/prometheus/client_golang v1.22.0 + qoobing.com/gomod/log v1.4.2 + qoobing.com/gomod/str v1.0.5 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/goccy/go-json v0.9.7 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pelletier/go-toml/v2 v2.0.1 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/tylerb/gls v0.0.0-20150407001822-e606233f194d // indirect + github.com/ugorji/go/codec v1.2.7 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect )