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