package utils import ( "context" "errors" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" "net/http" "reflect" ) var ( ErrMustHasTwoParam = errors.New("method must has two input") ErrMustHasTwoOut = errors.New("method must has two out") ErrMustValid = errors.New("method must valid") ErrMustFunc = errors.New("method must func") ErrMustPtr = errors.New("method must ptr") ErrMustPointToStruct = errors.New("method must point to struct") ) func checkMethod(method interface{}) (mV reflect.Value, reqT, replyT reflect.Type, err error) { mV = reflect.ValueOf(method) if !mV.IsValid() { err = ErrMustValid return } mT := mV.Type() if mT.Kind() != reflect.Func { err = ErrMustFunc return } if mT.NumIn() != 2 { err = ErrMustHasTwoParam return } reqT = mT.In(1) if reqT.Kind() != reflect.Ptr { err = ErrMustPtr return } if reqT.Elem().Kind() != reflect.Struct { err = ErrMustPointToStruct return } reqT = reqT.Elem() if mT.NumOut() != 2 { err = ErrMustHasTwoOut return } replyT = mT.Out(0) if replyT.Kind() != reflect.Ptr { err = ErrMustPtr return } replyT = replyT.Elem() return mV, reqT, replyT, err } var ( RequestKey struct{} ResponseKey struct{} ) type Option struct { logger *logrus.Logger } func CreateHandlerFunc(method interface{}, opts ...Option) gin.HandlerFunc { option := Option{logger: logrus.StandardLogger()} if len(opts) > 0 { option = opts[0] } mV, reqT, _, err := checkMethod(method) if err != nil { panic(err) } handler := func(ctx context.Context, req interface{}) (interface{}, error) { results := mV.Call([]reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(req)}) if results[1].Interface() != nil { return results[0].Interface(), results[1].Interface().(error) } return results[0].Interface(), nil } l := option.logger return func(c *gin.Context) { ctx := c.Request.Context() req := reflect.New(reqT) if err := DecodeRequestWithHeader(c, req.Interface()); err != nil { l.WithFields(logrus.Fields{ "req": c.Request.URL, "err": err, }).Warn(ctx, "bind param failed") WriteResponseWithCode(c, nil, err) return } ctx = context.WithValue(ctx, RequestKey, c.Request) ctx = context.WithValue(ctx, ResponseKey, c.Writer) // 有些请求没有带shopid和platform 需要写到context里面,后面规范后可以去掉 l.WithFields(logrus.Fields{ "req": req.Interface(), "func": mV.Type().String(), }).Info(ctx, "invoke handler") rsp, err := handler(ctx, req.Interface()) if err != nil { l.WithFields(logrus.Fields{ "req": c.Request.URL, "err": err, }).Warn(ctx, "handler err") WriteResponseWithCode(c, nil, err) return } WriteResponseWithCode(c, rsp, nil) } } func StatCode(err error) int { if err == nil { return 0 } return -1 } func WriteErrResponse(c *gin.Context, err error) { status := http.StatusOK code := int64(-1) var message string message = err.Error() c.JSON(status, &RespStruct{ Code: code, Message: message, }) } func WriteResponse(c *gin.Context, rsp interface{}, err error) { if err != nil { WriteErrResponse(c, err) return } c.PureJSON(http.StatusOK, rsp) } func WriteResponseWithCode(c *gin.Context, rsp interface{}, err error) { if err != nil { WriteErrResponse(c, err) return } c.PureJSON(http.StatusOK, &RespStruct{ Data: rsp, }) } type RespStruct struct { Code int64 `json:"code"` Message string `json:"message"` Data interface{} `json:"data"` } func DecodeRequestWithHeader(c *gin.Context, req interface{}) error { if err := c.ShouldBind(req); err != nil { // 有些 POST请求,但参数写在query,兼容一下吧 if err = c.BindQuery(req); err != nil { return err } } return c.BindHeader(req) }