api/context.go
2022-12-21 16:01:53 +08:00

230 lines
5.4 KiB
Go

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) recordMetric() {
if metricApiCounter == nil ||
metricApiSummary == nil {
return
}
var (
h = c.ApiHandler()
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)
}