Compare commits

..

No commits in common. "v1.3.0" and "master" have entirely different histories.

3 changed files with 94 additions and 189 deletions

120
api.go
View File

@ -1,10 +1,8 @@
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"
@ -16,21 +14,30 @@ var (
validate = validator.New() validate = validator.New()
requestLogidGetter = defaultLogidGetter requestLogidGetter = defaultLogidGetter
requestHandlerMapper = map[string]gin.HandlerFunc{} requestHandlerMapper = map[string]gin.HandlerFunc{}
apicfg = Config{ errCodeUnknownError = -10000
OutputErrCodeName: "errCode", errCodeParameterError = -20000
outputErrCodeFieldName: "ErrCode", errCodeUnimplementApi = -20401
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()
@ -78,8 +85,7 @@ 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
} }
@ -111,12 +117,13 @@ func middlewareMyApiEngine() gin.HandlerFunc {
defer log.Cleanup() defer log.Cleanup()
// Step 2. init *api.Context // Step 2. init *api.Context
req := c.Request
log.DebugfWithDepth(1, "[%s|%s|%s]", req.Method, req.Host, req.RequestURI)
cc := New(c) cc := New(c)
cc.Set("cc", cc) c.Set("cc", cc)
defer cc.addApiCallNoticeLog()()
// Step 3. do request // Step 3. do request
cc.Next() c.Next()
// Step 4. recorde metrics // Step 4. recorde metrics
cc.recordMetrics() cc.recordMetrics()
@ -148,7 +155,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("%p", h) hkey := fmt.Sprintf("%v", h)
if _, ok := requestHandlerMapper[hkey]; !ok { if _, ok := requestHandlerMapper[hkey]; !ok {
return nil return nil
} }
@ -174,7 +181,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 := apicfg.ErrCodeUnimplementApi errcode := errCodeUnimplementApi
return c.RESULT_ERROR(errcode, errmsg) return c.RESULT_ERROR(errcode, errmsg)
} }
@ -185,71 +192,14 @@ func ErrorHandler(c *gin.Context, errcode int, errmsg string) {
handler(c) handler(c)
} }
// deprecated, use "BaseWithErrCodeOutput + api.Init" instead func InitErrCodes(errcodes map[string]int) {
type BaseWithEnoOutput = BaseWithErrCodeOutput if code, ok := errcodes["ErrCodeUnknownError"]; ok {
errCodeUnknownError = code
// deprecated, use "BaseWithErrCodeOutput + api.Init" instead }
type BaseWithCodeOutput = BaseWithErrCodeOutput if code, ok := errcodes["ErrCodeUnimplementApi"]; ok {
errCodeUnknownError = code
type BaseWithErrCodeOutput struct { }
ErrCode int `json:"errCode"` if code, ok := errcodes["errCodeParameterError"]; ok {
ErrMessage string `json:"errMessage"` errCodeParameterError = code
} }
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,6 +19,7 @@ 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
@ -26,9 +27,7 @@ type Context struct {
resultentry string resultentry string
httpstatus int httpstatus int
starttime time.Time starttime time.Time
output interface{} // for notice log output interface{}
outputBind interface{} // for user get real errcode
noticedepth int
errcode int errcode int
} }
@ -52,10 +51,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(apicfg.ErrCodeUnknownError, "unknown error") c.RESULT_ERROR(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(apicfg.ErrCodeUnknownError, "unknown error") c.RESULT_ERROR(errCodeUnknownError, "unknown error")
} }
} }
} }
@ -91,7 +90,7 @@ func (c *Context) BindInput(i interface{}) (err error) {
return nil return nil
} }
func (c *Context) BindOutput(o any) (err error) { func (c *Context) BindOutput(o interface{}) (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 +98,18 @@ func (c *Context) BindOutput(o any) (err error) {
var e = v.Elem() var e = v.Elem()
var t = e.Type() var t = e.Type()
if _, ok := t.FieldByName(apicfg.outputErrCodeFieldName); !ok { if _, ok := t.FieldByName("ErrCode"); !ok {
return fmt.Errorf("out have no field '%s'", apicfg.outputErrCodeFieldName) return fmt.Errorf("out have no field ErrCode")
} }
if _, ok := t.FieldByName(apicfg.outputErrMessageFiledName); !ok { if _, ok := t.FieldByName("ErrMsg"); !ok {
return fmt.Errorf("out have no field '%s'", apicfg.outputErrMessageFiledName) return fmt.Errorf("out have no field ErrMsg")
} }
if code := e.FieldByName(apicfg.outputErrCodeFieldName); !code.CanSet() { if code := e.FieldByName("ErrCode"); !code.CanSet() {
return fmt.Errorf("output.%s can not be set, output is NOT a pointer", apicfg.outputErrCodeFieldName) return fmt.Errorf("output.ErrCode can not be set, " +
"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,12 +124,16 @@ func (c *Context) AlreadyHaveResult() (done bool, entry string) {
return c.resultentry != "", c.resultentry return c.resultentry != "", c.resultentry
} }
func (c *Context) RESULT(output any) error { func (c *Context) RESULT(output interface{}) 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{}) {
b, err := json.Marshal(o)
if err != nil {
return
}
calldepth := 0 calldepth := 0
switch c.resultentry { switch c.resultentry {
case "RESULT": case "RESULT":
@ -140,37 +143,41 @@ func (c *Context) RESULT(output any) error {
case "RESULT_PARAMETER_ERROR": case "RESULT_PARAMETER_ERROR":
calldepth = 2 calldepth = 2
} }
c.output = o //// cut log if too long ////// begin //////////////
c.noticedepth = calldepth if len(b) > 1024 {
//no := retag.Convert(*o, retag.NewView("json", "!logignore"))
//if nb, err := json.Marshal(no); err != nil {
// return
//} else {
// b = nb
//}
}
//// cut log if too long ////// end ////////////////
cost := time.Now().Sub(c.starttime).Milliseconds()
noticeStr := fmt.Sprintf("cost: %dms, output: '%s'", cost, string(b))
log.NoticefWithDepth(calldepth, noticeStr)
}(&output) }(&output)
if c.resultentry != "RESULT" { if _, ok := t.FieldByName("ErrCode"); !ok {
errstr := "Result MUST have 'ErrCode' field"
output = BaseWithErrCodeOutput{errCodeUnknownError, errstr}
c.shapeOutput(output) c.shapeOutput(output)
c.errcode = output.(BaseWithErrCodeOutput).ErrCode c.errcode = int(reflect.ValueOf(output).FieldByName("ErrCode").Int())
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(apicfg.outputErrMessageFiledName); !ok { if _, ok := t.FieldByName("ErrMsg"); !ok {
errstr := fmt.Sprintf("Result MUST have '%s' field", apicfg.outputErrMessageFiledName) errstr := "Result MUST have 'ErrMsg' field"
output = BaseWithErrCodeOutput{apicfg.ErrCodeUnknownError, errstr} output = BaseWithErrCodeOutput{errCodeUnknownError, errstr}
c.shapeOutput(output) c.shapeOutput(output)
c.errcode = output.(BaseWithErrCodeOutput).ErrCode c.errcode = int(reflect.ValueOf(output).FieldByName("ErrCode").Int())
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(apicfg.outputErrCodeFieldName).Int()) c.errcode = int(reflect.ValueOf(output).FieldByName("ErrCode").Int())
c.IndentedJSON(c.httpstatus, output) c.IndentedJSON(c.httpstatus, output)
return nil return nil
} }
@ -189,54 +196,27 @@ 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(apicfg.ErrCodeParameterError, err) return c.RESULT_ERROR(errCodeParameterError, err)
} }
func (c *Context) ApiHandler() Handler { func (c *Context) ApiHandler() Handler {
return ApiHandler(c.Context) return ApiHandler(c.Context)
} }
func (c *Context) ApiHandlerWithNoNil() Handler { func (c *Context) shapeOutput(o interface{}) {
h := c.ApiHandler() if c.output == nil {
if h == nil {
h = unimplementHandler{c.Context}
}
return h
}
func (c *Context) shapeOutput(outputInnnerError any) {
if c.outputBind == nil {
return return
} }
var out, ok = outputInnnerError.(BaseWithErrCodeOutput) var out, ok = o.(BaseWithErrCodeOutput)
if !ok { if !ok {
return return
} }
var outputBind = reflect.ValueOf(c.outputBind).Elem() var e = reflect.ValueOf(c.output).Elem()
outputBind.FieldByName(apicfg.outputErrCodeFieldName).SetInt(int64(out.ErrCode)) e.FieldByName("ErrMsg").SetString(out.ErrMsg)
outputBind.FieldByName(apicfg.outputErrMessageFiledName).SetString(out.ErrMessage) e.FieldByName("ErrCode").SetInt(int64(out.ErrCode))
} return
func (c *Context) addApiCallNoticeLog() func() {
h, req := c.ApiHandlerWithNoNil(), c.Request
log.DebugfWithDepth(1, "[>>>>>>:%s,%s,%s]", h.ApiName(), req.Method, req.RequestURI)
return func() {
var (
o = c.output
b, err = json.Marshal(o)
costus = time.Since(c.starttime).Microseconds()
costms = float64(costus) / 1000.0
)
if err != nil {
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))
}
} }
func (c *Context) recordMetrics() { func (c *Context) recordMetrics() {
@ -244,11 +224,14 @@ func (c *Context) recordMetrics() {
metricApiSummary == nil { metricApiSummary == nil {
return return
} }
var h = c.ApiHandlerWithNoNil() var h = c.ApiHandler()
if h == nil {
h = unimplementHandler{c.Context}
}
var ( var (
api = h.ApiName() api = h.ApiName()
appid = c.AppId appid = c.AppId
costus = time.Since(c.starttime).Microseconds() costus = time.Now().Sub(c.starttime).Microseconds()
costms = float64(costus) / 1000.0 costms = float64(costus) / 1000.0
errcode = strconv.Itoa(c.errcode) errcode = strconv.Itoa(c.errcode)
) )

36
go.mod
View File

@ -1,40 +1,12 @@
module qoobing.com/gomod/api module qoobing.com/gomod/api
go 1.22 go 1.19
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/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/tylerb/gls v0.0.0-20150407001822-e606233f194d // indirect
github.com/ugorji/go/codec v1.2.7 // indirect github.com/tylerb/is v2.1.4+incompatible // indirect
golang.org/x/crypto v0.31.0 // indirect qoobing.com/gomod/log v1.1.0
golang.org/x/net v0.33.0 // indirect qoobing.com/gomod/str v1.0.3
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
) )