💬
貌似又要一点了。
💻
- 背景
最近使用 gin 写了一些 api,我们知道每次要暴露一个服务/接口,需要为其编写一个 HanlderFunc,类似这样
// 伪代码
func handler(c *gin.Context) {
var req Req
if err := c.ShouldBindJSON(&req); err != nil{
c.JSON(xxx)
return
}
// logic init
l := logic.Newxx(c)
data, err := l.Getxx(&req)
if err != nil{
c.JSON(xx)
return
}
c.JSON(data)
}
几乎所有的 handler 都是这种形式,除了 req,logic 的初始化不同之外。像我们的某些服务 api 接口可能是几十上百个。之前是重复 copy 了几十遍。有没有相对较好的通用处理方式?答案是考虑使用反射
代码示例:(有注释)
package handler
import (
"fmt"
"gin-reflect-handler/internal/svc"
"go.mongodb.org/mongo-driver/bson"
"log"
"net/http"
"reflect"
"github.com/gin-gonic/gin"
)
type Handler struct {
svcCtx *svc.ServiceContext
}
//ReflectHandler 反射通用 handler
//fn -> NewTargetLogic 实例化 logic 的函数签名
//req -> 请求体 需要传指针类型 .Elem 需要 Kind 为 Ptr
//callFn -> 调用的函数名称
func (h *Handler) ReflectHandler(fn, req interface{}, callFn string) gin.HandlerFunc {
return func(c *gin.Context) {
// bind req
reqType := reflect.TypeOf(req).Elem()
request := reflect.New(reqType)
if err := c.ShouldBind(request.Interface()); err != nil {
c.JSON(http.StatusOK, fmt.Sprintf("反序列化失败: %v", err))
return
}
log.Printf("%+v\n", request.Interface())
// 实例化 logic
logicParams := []reflect.Value{reflect.ValueOf(c), reflect.ValueOf(h.svcCtx)}
rets := reflect.ValueOf(fn).Call(logicParams)
if len(rets) != 1 {
c.JSON(http.StatusOK, fmt.Sprintf("实例化 logic 失败"))
return
}
log.Printf("rets[0] is %+v\n", rets[0])
// Logic.Call func by method name
reqParams := []reflect.Value{reflect.ValueOf(request.Interface())}
res := rets[0].MethodByName(callFn).Call(reqParams)
if len(res) != 2 {
c.JSON(http.StatusOK, fmt.Sprintf("调用 %s func 失败", callFn))
return
}
if !res[1].IsNil() { // 第二个返回参数是 error
c.JSON(http.StatusOK, fmt.Sprintf(
"fn: %+v, req: %+v, callFn: %s, err: %+v",
fn,
request.Interface(),
callFn,
res[1].Interface()))
return
}
c.JSON(http.StatusOK, bson.M{"data": res[0].Interface()}) // 第一个返回参数是 data
return
}
}
- 如何使用?
h := Handler{svcCtx: serverCtx}
targetRouter := v1.Group("/target")
{
targetHandler := TargetHandler{H: &h}
targetRouter.GET("/attr", targetHandler.H.ReflectHandler(
logic.NewTargetLogic, // 指定初始化 logic func
&types.AttrListReq{}, // 指定请求体
"GetAttrList", // 指定调用的对应 func name
))
- 测试
# 终端 terminal
# ashing @ ashings-m1-mac-mini in ~/Documents/gin-reflect-handler on git:master x [0:56:42]
$ curl http://127.0.0.1:9096/v1/api/cmdb/target/attr\?objectId\=HOST
{"data":{"objectId":"HOST"}}%
# 控制台输出
GOROOT=/usr/local/go #gosetup
GOPATH=/Users/ashing/go #gosetup
/usr/local/go/bin/go build -o /private/var/folders/1n/3blyz5ys7z73gsr9bzz46pq40000gn/T/GoLand/___go_build_gin_reflect_handler gin-reflect-handler #gosetup
/private/var/folders/1n/3blyz5ys7z73gsr9bzz46pq40000gn/T/GoLand/___go_build_gin_reflect_handler
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /v1/api/cmdb/target/attr --> gin-reflect-handler/internal/handler.(*Handler).ReflectHandler.func1 (3 handlers)
2021/11/26 00:56:52 &{ObjectId:HOST}
2021/11/26 00:56:52 rets[0] is &{svcCtx:0x1400012c1a0 ctx:0x140000ae400}
{"@timestamp":"2021-11-26T00:56:52.405+08","level":"info","content":"objectId: HOST"}
- 后续改造
目前暂时打算在新的服务中进行使用,旧的服务因为 logic func 的某些入参不太规范,有一定的改动成本。代码已经托管 github gin-reflect-handler 可直接运行。
💽
朋友送了一张 不是台版也不是复刻的再版《范特西》
🌞
嗯 生活总是这样。
写于 2021-11-26 凌晨