257 lines
6.5 KiB
Go
257 lines
6.5 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
|
|
|
|
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{} // for notice log
|
|
outputBind interface{} // for user get real errcode
|
|
noticedepth int
|
|
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.Errorf("panic err:%v. Panic stack:\n%s", err, stack)
|
|
c.RESULT_ERROR(apicfg.ErrCodeUnknownError, "unknown error")
|
|
default:
|
|
log.Errorf("panic err type: %s:%s. Panic stack:\n%s", t.Name(), t.String(), stack)
|
|
c.RESULT_ERROR(apicfg.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 any) (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(apicfg.outputErrCodeFieldName); !ok {
|
|
return fmt.Errorf("out have no field '%s'", apicfg.outputErrCodeFieldName)
|
|
}
|
|
if _, ok := t.FieldByName(apicfg.outputErrMessageFiledName); !ok {
|
|
return fmt.Errorf("out have no field '%s'", apicfg.outputErrMessageFiledName)
|
|
}
|
|
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
|
|
}
|
|
|
|
func (c *Context) SetCookie(cookie *http.Cookie) {
|
|
if cookie.Path == "" {
|
|
cookie.Path = "/"
|
|
}
|
|
http.SetCookie(c.Writer, cookie)
|
|
}
|
|
|
|
func (c *Context) AlreadyHaveResult() (done bool, entry string) {
|
|
return c.resultentry != "", c.resultentry
|
|
}
|
|
|
|
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":
|
|
calldepth = 0
|
|
case "RESULT_ERROR":
|
|
calldepth = 1
|
|
case "RESULT_PARAMETER_ERROR":
|
|
calldepth = 2
|
|
}
|
|
c.output = o
|
|
c.noticedepth = calldepth
|
|
}(&output)
|
|
|
|
if c.resultentry != "RESULT" {
|
|
c.shapeOutput(output)
|
|
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(apicfg.outputErrMessageFiledName); !ok {
|
|
errstr := fmt.Sprintf("Result MUST have '%s' field", apicfg.outputErrMessageFiledName)
|
|
output = BaseWithErrCodeOutput{apicfg.ErrCodeUnknownError, errstr}
|
|
c.shapeOutput(output)
|
|
c.errcode = output.(BaseWithErrCodeOutput).ErrCode
|
|
c.IndentedJSON(c.httpstatus, output)
|
|
return errors.New(errstr)
|
|
}
|
|
|
|
c.shapeOutput(output)
|
|
c.errcode = int(reflect.ValueOf(output).FieldByName(apicfg.outputErrCodeFieldName).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(apicfg.ErrCodeParameterError, err)
|
|
}
|
|
|
|
func (c *Context) ApiHandler() Handler {
|
|
return ApiHandler(c.Context)
|
|
}
|
|
|
|
func (c *Context) ApiHandlerWithNoNil() Handler {
|
|
h := c.ApiHandler()
|
|
if h == nil {
|
|
h = unimplementHandler{c.Context}
|
|
}
|
|
return h
|
|
}
|
|
|
|
func (c *Context) shapeOutput(outputInnnerError any) {
|
|
if c.outputBind == nil {
|
|
return
|
|
}
|
|
|
|
var out, ok = outputInnnerError.(BaseWithErrCodeOutput)
|
|
if !ok {
|
|
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() {
|
|
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() {
|
|
if metricApiCounter == nil ||
|
|
metricApiSummary == nil {
|
|
return
|
|
}
|
|
var h = c.ApiHandlerWithNoNil()
|
|
var (
|
|
api = h.ApiName()
|
|
appid = c.AppId
|
|
costus = time.Since(c.starttime).Microseconds()
|
|
costms = float64(costus) / 1000.0
|
|
errcode = strconv.Itoa(c.errcode)
|
|
)
|
|
RecordMetrics(api, errcode, appid, costms)
|
|
}
|