[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

119
api.go
View File

@ -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
}

View File

@ -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)
)

38
go.mod
View File

@ -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
)