使用Golang reflect 对 gin handler 进行简单封装

项目中大量使用 gin 作为service API的 http framework. 大部分时候我们的代码结构类似这样:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func main() {
 
    engine := gin.New()
    engine.GET("/hi", hiHandler)
    engine.Run(":8083")
}
 
func hiHandler(c *gin.Context) {
    rsp, err := businessLogicProcess()
    if err != nil {
        c.AbortWithStatus(http.StatusBadRequest)
    } else {
        c.JSON(http.StatusOK, rsp)
    }
}
 
func businessLogicProcess() {
    ...
}
 
hiHandler -> businessLogicProcess -> hiHandlerhiHandler胶水层handler
hiHandlerwrite reusable code
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
func main() {
 
    engine := gin.New()
    engine.GET("/time", WrapHandler(GetTime))
    engine.Run(":8083")
}
 
 
type GetTimeReq struct {
    Nation string `form:"nation" binding:"required" json:"nation"`
}
 
func GetTime(req *GetTimeReq) (*OKRsp, *ErrRsp) {
    ...
}
 
 
// WrapHandler wrap gin handler, the handler signature should be
// func(req *ReqStruct) (*OKRspStruct, *ErrorRspStruct) or
// func(c *gin.Context, req *ReqStruct) (*OKRspStruct, *ErrorRspStruct)
func WrapHandler(f interface{}) gin.HandlerFunc {
    t := reflect.TypeOf(f)
    if t.Kind() != reflect.Func {
        panic("handdler should be function type")
    }
 
    fnumIn := t.NumIn()
    if fnumIn == 0 || fnumIn > 2 {
        panic("handler function require 1 or 2 input parameters")
    }
 
    if fnumIn == 2 {
        tc := reflect.TypeOf(&gin.Context{})
        if t.In(0) != tc {
            panic("handler function first paramter should by type of *gin.Context if you have 2 input parameters")
        }
    }
 
    if t.NumOut() != 2 {
        panic("handler function return values should contain response data & error")
    }
 
    // errorInterface := reflect.TypeOf((*error)(nil)).Elem()
    // if !t.Out(1).Implements(errorInterface) {
    //  panic("handler function second return value should by type of error")
 
    // }
 
    return func(c *gin.Context) {
        var req interface{}
        if fnumIn == 1 {
            req = newReqInstance(t.In(0))
        } else {
            req = newReqInstance(t.In(1))
        }
 
        if !c.Bind(req) {
            err := c.LastError()
            log.Warning("bind parameter error:%v", err)
            GinErrRsp(c, ErrCodeParamInvalid, err.Error())
            return
        }
 
        var inValues []reflect.Value
        if fnumIn == 1 {
            inValues = []reflect.Value{reflect.ValueOf(req)}
        } else {
            inValues = []reflect.Value{reflect.ValueOf(c), reflect.ValueOf(req)}
        }
 
        ret := reflect.ValueOf(f).Call(inValues)
        if ret[1].IsNil() {
            GinRsp(c, http.StatusOK, ret[0].Interface())
        } else {
            GinRsp(c, http.StatusOK, ret[1].Interface())
        }
    }
}
 
func newReqInstance(t reflect.Type) interface{} {
    switch t.Kind() {
    case reflect.Ptr, reflect.Interface:
        return newReqInstance(t.Elem())
    default:
        return reflect.New(t).Interface()
    }
}
 
 

这样做还有一个额外的好处:实现了业务处理函数 (GetTime) 与gin的解耦,使得业务处理函数复用性更强。

–EOF–

版权声明
转载请注明出处,本文原始链接