[FEATURE] rename errCode and errMessage

This commit is contained in:
bryan 2025-05-26 16:56:26 +08:00
parent b3b2a1c13b
commit d545612482
3 changed files with 158 additions and 74 deletions

113
api.go
View File

@ -1,8 +1,10 @@
package api package api
import ( import (
"encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
@ -14,30 +16,21 @@ var (
validate = validator.New() validate = validator.New()
requestLogidGetter = defaultLogidGetter requestLogidGetter = defaultLogidGetter
requestHandlerMapper = map[string]gin.HandlerFunc{} requestHandlerMapper = map[string]gin.HandlerFunc{}
errCodeUnknownError = -10000 apicfg = Config{
errCodeParameterError = -20000 OutputErrCodeName: "errCode",
errCodeUnimplementApi = -20401 outputErrCodeFieldName: "ErrCode",
OutputErrMessageName: "errMessage",
outputErrMessageFiledName: "ErrMessage",
ErrCodeUnknownError: -10000,
ErrCodeParameterError: -20000,
ErrCodeUnimplementApi: -20401,
}
) )
type Engine struct { type Engine struct {
*gin.Engine *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 { func NewEngine(pre ...gin.HandlerFunc) *Engine {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
e := gin.New() e := gin.New()
@ -85,7 +78,8 @@ func handlerWrapper(handler Handler) gin.HandlerFunc {
return return
} }
} }
hkey := fmt.Sprintf("%v", wrappedHandlerFunc)
hkey := fmt.Sprintf("%p", wrappedHandlerFunc)
requestHandlerMapper[hkey] = wrappedHandlerFunc requestHandlerMapper[hkey] = wrappedHandlerFunc
return wrappedHandlerFunc return wrappedHandlerFunc
} }
@ -154,7 +148,7 @@ func CC(c *gin.Context) *Context {
func ApiHandler(c *gin.Context) Handler { func ApiHandler(c *gin.Context) Handler {
h := c.Handler() h := c.Handler()
hkey := fmt.Sprintf("%v", h) hkey := fmt.Sprintf("%p", h)
if _, ok := requestHandlerMapper[hkey]; !ok { if _, ok := requestHandlerMapper[hkey]; !ok {
return nil return nil
} }
@ -180,7 +174,7 @@ func RecordMetrics(api, errcode, appid string, costms float64) {
func DumpHandlerFunc(c *Context) error { func DumpHandlerFunc(c *Context) error {
errmsg := "api not implemented" errmsg := "api not implemented"
errcode := errCodeUnimplementApi errcode := apicfg.ErrCodeUnimplementApi
return c.RESULT_ERROR(errcode, errmsg) return c.RESULT_ERROR(errcode, errmsg)
} }
@ -191,14 +185,71 @@ func ErrorHandler(c *gin.Context, errcode int, errmsg string) {
handler(c) handler(c)
} }
func InitErrCodes(errcodes map[string]int) { // deprecated, use "BaseWithErrCodeOutput + api.Init" instead
if code, ok := errcodes["ErrCodeUnknownError"]; ok { type BaseWithEnoOutput = BaseWithErrCodeOutput
errCodeUnknownError = code
} // deprecated, use "BaseWithErrCodeOutput + api.Init" instead
if code, ok := errcodes["ErrCodeUnimplementApi"]; ok { type BaseWithCodeOutput = BaseWithErrCodeOutput
errCodeUnknownError = code
} type BaseWithErrCodeOutput struct {
if code, ok := errcodes["errCodeParameterError"]; ok { ErrCode int `json:"errCode"`
errCodeParameterError = code 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
} }

View File

@ -19,7 +19,6 @@ import (
type Context struct { type Context struct {
*gin.Context *gin.Context
e *gin.Engine
AppId string //current request appid, must not be empty AppId string //current request appid, must not be empty
UserId uint64 //current request userid, maybe 0 if user unknown UserId uint64 //current request userid, maybe 0 if user unknown
@ -27,7 +26,8 @@ type Context struct {
resultentry string resultentry string
httpstatus int httpstatus int
starttime time.Time 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 noticedepth int
errcode int errcode int
} }
@ -52,10 +52,10 @@ func (c *Context) TryRecover() {
switch t.Kind() { switch t.Kind() {
case reflect.String: case reflect.String:
log.Fatalf("panic err:%v. Panic stack:\n%s", err, stack) 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: default:
log.Fatalf("panic err type: %s:%s. Panic stack:\n%s", t.Name(), t.String(), stack) 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 return nil
} }
func (c *Context) BindOutput(o interface{}) (err error) { func (c *Context) BindOutput(o any) (err error) {
var v = reflect.ValueOf(o) var v = reflect.ValueOf(o)
if v.Kind() != reflect.Pointer { if v.Kind() != reflect.Pointer {
return fmt.Errorf("output is NOT a 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 e = v.Elem()
var t = e.Type() var t = e.Type()
if _, ok := t.FieldByName("ErrCode"); !ok { if _, ok := t.FieldByName(apicfg.outputErrCodeFieldName); !ok {
return fmt.Errorf("out have no field ErrCode") return fmt.Errorf("out have no field '%s'", apicfg.outputErrCodeFieldName)
} }
if _, ok := t.FieldByName("ErrMsg"); !ok { if _, ok := t.FieldByName(apicfg.outputErrMessageFiledName); !ok {
return fmt.Errorf("out have no field ErrMsg") return fmt.Errorf("out have no field '%s'", apicfg.outputErrMessageFiledName)
} }
if code := e.FieldByName("ErrCode"); !code.CanSet() { if code := e.FieldByName(apicfg.outputErrCodeFieldName); !code.CanSet() {
return fmt.Errorf("output.ErrCode can not be set, " + return fmt.Errorf("output.%s can not be set, output is NOT a pointer", apicfg.outputErrCodeFieldName)
"output is NOT a pointer or output have no field 'ErrCode'")
} }
c.output = o c.output = o
c.outputBind = o
return nil return nil
} }
@ -125,13 +125,12 @@ func (c *Context) AlreadyHaveResult() (done bool, entry string) {
return c.resultentry != "", c.resultentry return c.resultentry != "", c.resultentry
} }
func (c *Context) RESULT(output interface{}) error { func (c *Context) RESULT(output any) error {
if c.resultentry == "" { if c.resultentry == "" {
c.resultentry = "RESULT" c.resultentry = "RESULT"
} }
var t = reflect.TypeOf(output) var t = reflect.TypeOf(output)
defer func(o *interface{}) { defer func(o *interface{}) {
calldepth := 0 calldepth := 0
switch c.resultentry { switch c.resultentry {
case "RESULT": case "RESULT":
@ -145,26 +144,33 @@ func (c *Context) RESULT(output interface{}) error {
c.noticedepth = calldepth c.noticedepth = calldepth
}(&output) }(&output)
if _, ok := t.FieldByName("ErrCode"); !ok { if c.resultentry != "RESULT" {
errstr := "Result MUST have 'ErrCode' field"
output = BaseWithErrCodeOutput{errCodeUnknownError, errstr}
c.shapeOutput(output) 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) c.IndentedJSON(c.httpstatus, output)
return errors.New(errstr) return errors.New(errstr)
} }
if _, ok := t.FieldByName("ErrMsg"); !ok { if _, ok := t.FieldByName(apicfg.outputErrMessageFiledName); !ok {
errstr := "Result MUST have 'ErrMsg' field" errstr := fmt.Sprintf("Result MUST have '%s' field", apicfg.outputErrMessageFiledName)
output = BaseWithErrCodeOutput{errCodeUnknownError, errstr} output = BaseWithErrCodeOutput{apicfg.ErrCodeUnknownError, errstr}
c.shapeOutput(output) c.shapeOutput(output)
c.errcode = int(reflect.ValueOf(output).FieldByName("ErrCode").Int()) c.errcode = output.(BaseWithErrCodeOutput).ErrCode
c.IndentedJSON(c.httpstatus, output) c.IndentedJSON(c.httpstatus, output)
return errors.New(errstr) return errors.New(errstr)
} }
c.shapeOutput(output) 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) c.IndentedJSON(c.httpstatus, output)
return nil return nil
} }
@ -183,7 +189,7 @@ func (c *Context) RESULT_PARAMETER_ERROR(err string) error {
if c.resultentry == "" { if c.resultentry == "" {
c.resultentry = "RESULT_PARAMETER_ERROR" c.resultentry = "RESULT_PARAMETER_ERROR"
} }
return c.RESULT_ERROR(errCodeParameterError, err) return c.RESULT_ERROR(apicfg.ErrCodeParameterError, err)
} }
func (c *Context) ApiHandler() Handler { func (c *Context) ApiHandler() Handler {
@ -198,20 +204,19 @@ func (c *Context) ApiHandlerWithNoNil() Handler {
return h return h
} }
func (c *Context) shapeOutput(o interface{}) { func (c *Context) shapeOutput(outputInnnerError any) {
if c.output == nil { if c.outputBind == nil {
return return
} }
var out, ok = o.(BaseWithErrCodeOutput) var out, ok = outputInnnerError.(BaseWithErrCodeOutput)
if !ok { if !ok {
return return
} }
var e = reflect.ValueOf(c.output).Elem() var outputBind = reflect.ValueOf(c.outputBind).Elem()
e.FieldByName("ErrMsg").SetString(out.ErrMsg) outputBind.FieldByName(apicfg.outputErrCodeFieldName).SetInt(int64(out.ErrCode))
e.FieldByName("ErrCode").SetInt(int64(out.ErrCode)) outputBind.FieldByName(apicfg.outputErrMessageFiledName).SetString(out.ErrMessage)
return
} }
func (c *Context) addApiCallNoticeLog() func() { func (c *Context) addApiCallNoticeLog() func() {
@ -221,16 +226,16 @@ func (c *Context) addApiCallNoticeLog() func() {
var ( var (
o = c.output o = c.output
b, err = json.Marshal(o) b, err = json.Marshal(o)
costus = time.Now().Sub(c.starttime).Microseconds() costus = time.Since(c.starttime).Microseconds()
costms = float64(costus) / 1000.0 costms = float64(costus) / 1000.0
) )
if err != nil { 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 if len(b) > 1024 { // cut log if too long
b = append(b[0:1000], b[len(b)-24:]...) 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 ( var (
api = h.ApiName() api = h.ApiName()
appid = c.AppId appid = c.AppId
costus = time.Now().Sub(c.starttime).Microseconds() costus = time.Since(c.starttime).Microseconds()
costms = float64(costus) / 1000.0 costms = float64(costus) / 1000.0
errcode = strconv.Itoa(c.errcode) errcode = strconv.Itoa(c.errcode)
) )

38
go.mod
View File

@ -1,12 +1,40 @@
module qoobing.com/gomod/api module qoobing.com/gomod/api
go 1.19 go 1.22
toolchain go1.23.6
require ( require (
github.com/gin-gonic/gin v1.8.1 github.com/gin-gonic/gin v1.8.1
github.com/go-playground/validator/v10 v10.11.1 github.com/go-playground/validator/v10 v10.11.1
github.com/tylerb/gls v0.0.0-20150407001822-e606233f194d // indirect github.com/prometheus/client_golang v1.22.0
github.com/tylerb/is v2.1.4+incompatible // indirect qoobing.com/gomod/log v1.4.2
qoobing.com/gomod/log v1.1.0 qoobing.com/gomod/str v1.0.5
qoobing.com/gomod/str v1.0.3 )
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
) )