package api import ( "encoding/json" "errors" "fmt" "net/http" "reflect" "runtime/debug" "strconv" "strings" "time" "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" "qoobing.com/gomod/log" "qoobing.com/gomod/str" ) 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 resultentry string httpstatus int starttime time.Time output interface{} errcode int } func New(c *gin.Context) *Context { return &Context{ Context: c, httpstatus: 200, starttime: time.Now(), AppId: "", UserId: 0, } } func (c *Context) TryRecover() { if err := recover(); err != nil { calldepth := 3 stack := strings.Replace(string(debug.Stack()), "\n", "\n== ", -1) stack = str.SkipLine(stack, calldepth*2+1) t := reflect.TypeOf(err) switch t.Kind() { case reflect.String: log.Fatalf("panic err:%v. Panic stack:\n%s", err, stack) c.RESULT_ERROR(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") } } } func (c *Context) BindInput(i interface{}) (err error) { logstr := fmt.Sprintf("input:[have not initialized]") defer func(plogstr *string) { log.DebugfWithDepth(2, "%s", *plogstr) }(&logstr) err = c.ShouldBind(i) if err != nil { b, _ := json.Marshal(i) logstr = fmt.Sprintf("input:%s binderror:%s", string(b), err.Error()) return err } err = validate.Struct(i) if err != nil { outputstr := "" for _, err := range err.(validator.ValidationErrors) { b, _ := json.Marshal(i) logstr = fmt.Sprintf("input:%s validateerror:%s", string(b), err.Error()) outputstr = fmt.Sprintf("invalide field: [%s(%s)]", err.Field(), err.Tag()) return errors.New(outputstr) } panic("Unreachable Code") } // recode input parameters. b, _ := json.Marshal(i) logstr = fmt.Sprintf("input:%s", string(b)) return nil } func (c *Context) BindOutput(o interface{}) (err error) { var v = reflect.ValueOf(o) if v.Kind() != reflect.Pointer { return fmt.Errorf("output is NOT a pointer") } 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("ErrMsg"); !ok { return fmt.Errorf("out have no field ErrMsg") } 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'") } c.output = o return nil } func (c *Context) SetCookie(cookie *http.Cookie) { if cookie.Path == "" { cookie.Path = "/" } http.SetCookie(c.Writer, cookie) } func (c *Context) RESULT(output interface{}) error { var t = reflect.TypeOf(output) defer func(o *interface{}) { b, err := json.Marshal(o) if err != nil { return } calldepth := 0 switch c.resultentry { case "RESULT": calldepth = 0 case "RESULT_ERROR": calldepth = 1 case "RESULT_PARAMETER_ERROR": calldepth = 2 } //// cut log if too long ////// begin ////////////// 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) if _, ok := t.FieldByName("ErrCode"); !ok { errstr := "Result MUST have 'ErrCode' field" output = BaseWithErrCodeOutput{errCodeUnknownError, errstr} c.shapeOutput(output) c.errcode = int(reflect.ValueOf(output).FieldByName("ErrCode").Int()) 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} c.shapeOutput(output) c.errcode = int(reflect.ValueOf(output).FieldByName("ErrCode").Int()) c.IndentedJSON(c.httpstatus, output) return errors.New(errstr) } c.shapeOutput(output) c.errcode = int(reflect.ValueOf(output).FieldByName("ErrCode").Int()) c.IndentedJSON(c.httpstatus, output) return nil } func (c *Context) RESULT_ERROR(eno int, err string) error { if c.resultentry == "" { c.resultentry = "RESULT_ERROR" } result := BaseWithErrCodeOutput{eno, err} c.errcode = eno return c.RESULT(result) } func (c *Context) RESULT_PARAMETER_ERROR(err string) error { if c.resultentry == "" { c.resultentry = "RESULT_PARAMETER_ERROR" } return c.RESULT_ERROR(errCodeParameterError, err) } func (c *Context) ApiHandler() Handler { return ApiHandler(c.Context) } func (c *Context) shapeOutput(o interface{}) { if c.output == nil { return } var out, ok = o.(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 } func (c *Context) recordMetrics() { if metricApiCounter == nil || metricApiSummary == nil { return } var h = c.ApiHandler() if h == nil { h = unimplementHandler{c.Context} } var ( api = h.ApiName() appid = c.AppId costus = time.Now().Sub(c.starttime).Microseconds() costms = float64(costus) / 1000.0 errcode = strconv.Itoa(c.errcode) ) RecordMetrics(api, errcode, appid, costms) }