「go-zero 系列」gin 通用处理函数

2,179 阅读2分钟

💬

貌似又要一点了。

💻

  • 背景

最近使用 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 凌晨