initial version
This commit is contained in:
		
						commit
						8489fc1556
					
				
							
								
								
									
										82
									
								
								api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								api.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,82 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"github.com/go-playground/validator/v10"
 | 
			
		||||
	"qoobing.com/gomod/log"
 | 
			
		||||
	"qoobing.com/gomod/str"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	validate              = validator.New()
 | 
			
		||||
	requestLogidGetter    = defaultLogidGetter
 | 
			
		||||
	errCodeUnknownError   = -10000
 | 
			
		||||
	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() *Engine {
 | 
			
		||||
	gin.SetMode(gin.ReleaseMode)
 | 
			
		||||
	e := gin.New()
 | 
			
		||||
	e.Use(middlewareMyApiEngine())
 | 
			
		||||
	return &Engine{e}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *Engine) POST(uri string, handler HandlerFunc) {
 | 
			
		||||
	e.Engine.POST(uri, handlerWrapper(handler))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// middlewareMyApiEngine myapi engine middleware
 | 
			
		||||
func middlewareMyApiEngine() gin.HandlerFunc {
 | 
			
		||||
	return func(c *gin.Context) {
 | 
			
		||||
		// Step 1. set logid
 | 
			
		||||
		id := requestLogidGetter(c)
 | 
			
		||||
		log.SetLogid(id)
 | 
			
		||||
		defer log.Cleanup()
 | 
			
		||||
 | 
			
		||||
		// Step 2. init *api.Context
 | 
			
		||||
		cc := New(c)
 | 
			
		||||
		c.Set("cc", cc)
 | 
			
		||||
 | 
			
		||||
		// Step 3. do request
 | 
			
		||||
		c.Next()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// defaultLogidGetter get logid from "X-Request-Id" or create a new one.
 | 
			
		||||
func defaultLogidGetter(c *gin.Context) (logid string) {
 | 
			
		||||
	if logid = c.Request.Header.Get("X-Request-Id"); logid != "" {
 | 
			
		||||
		return logid
 | 
			
		||||
	}
 | 
			
		||||
	if logid = str.GetRandomNumString(13); logid != "" {
 | 
			
		||||
		return logid
 | 
			
		||||
	}
 | 
			
		||||
	panic("unreachable code")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func InitErrCodes(errcodes map[string]int) {
 | 
			
		||||
	if code, ok := errcodes["ErrCodeUnknownError"]; ok {
 | 
			
		||||
		errCodeUnknownError = code
 | 
			
		||||
	}
 | 
			
		||||
	if code, ok := errcodes["ErrCodeUnimplementApi"]; ok {
 | 
			
		||||
		errCodeUnknownError = code
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										160
									
								
								context.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								context.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,160 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"runtime/debug"
 | 
			
		||||
	"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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
	req := c.Request
 | 
			
		||||
	log.DebugfWithDepth(1, "[%s|%s|%s]", req.Method, req.Host, req.RequestURI)
 | 
			
		||||
 | 
			
		||||
	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) 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.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.IndentedJSON(c.httpstatus, output)
 | 
			
		||||
		return errors.New(errstr)
 | 
			
		||||
	}
 | 
			
		||||
	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}
 | 
			
		||||
	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(errCodeUnknownError, err)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
			
		||||
module qoobing.com/gomod/api
 | 
			
		||||
 | 
			
		||||
go 1.16
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										49
									
								
								routes.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								routes.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"qoobing.com/gomod/log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type routes struct {
 | 
			
		||||
	gin.IRoutes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type HandlerFunc func(*Context) error
 | 
			
		||||
 | 
			
		||||
type IRoutes interface {
 | 
			
		||||
	POST(uri string, handler HandlerFunc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r routes) POST(uri string, handler HandlerFunc) {
 | 
			
		||||
	r.IRoutes.POST(uri, handlerWrapper(handler))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func DumpHandler(c *Context) error {
 | 
			
		||||
	return c.RESULT_ERROR(errCodeUnimplementApi, "api not implemented")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ErrorHandler(c *gin.Context, errcode int, errmsg string) {
 | 
			
		||||
	handler := handlerWrapper(func(c *Context) error {
 | 
			
		||||
		return c.RESULT_ERROR(errcode, errmsg)
 | 
			
		||||
	})
 | 
			
		||||
	handler(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handlerWrapper(handler HandlerFunc) gin.HandlerFunc {
 | 
			
		||||
	return func(c *gin.Context) {
 | 
			
		||||
		if icc, ok := c.Get("cc"); !ok {
 | 
			
		||||
			log.Errorf("Unreachable Code: can not get cc(*api.Context) from *gin.Context")
 | 
			
		||||
		} else if cc, ok := icc.(*Context); !ok {
 | 
			
		||||
			log.Debugf("Unreachable Code: cc from *gin.Context is type of:[%s]", reflect.TypeOf(icc).String())
 | 
			
		||||
			log.Errorf("Unreachable Code: cc from *gin.Context is not type of *api.Context")
 | 
			
		||||
		} else {
 | 
			
		||||
			defer cc.TryRecover()
 | 
			
		||||
			cc.AppId = c.GetString("appid")
 | 
			
		||||
			cc.UserId = c.GetUint64("userid")
 | 
			
		||||
			handler(cc)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user