【Gin-API系列】實現動態路由分組(七)

RunningPower發表於2020-09-04

在之前的文章介紹中我們已經完成了一個API服務的全鏈路請求設計。呼叫方式可以看Test目錄的程式碼

// src/test/request_test.go
func TestAPI_Request(t *testing.T) {
	url := "127.0.0.1:8080"
	ak := "A00001"
	sk := "SECRET-A00001"
	api := NewAPI(url, ak, sk)
	params := map[string]interface{}{
		"ip": "10.1.162.18",
	}
	if result, err  := api.Request("/", "GET", params); err != nil {
		t.Fatal(err)
	} else {
		t.Log(result)
	}
}

重複的路由現象

截至目前我們只定義了一個路由(在main函式中),但現實中往往會定義多個路由實現多個API介面,而為了風格統一化(或是模組化、版本區分等原因),我們也往往會將多個路由歸為一類,這就會導致很多路由的字首相同。
所以,本文將介紹如何通過分組路由,減少路由重複程式碼並實現動態路由。

  • 路由定義
func main() {
	...	
	r.GET("/", v1_sdk_search_ip.SearchIpHandlerWithGet) 	
	...
}
  • 重複路由字首
/sdk/search_ip
/sdk/search_mac

/object/host
/object/switch

/v1/object/host
/v1/object/switch

/v2/object/host
/v2/object/switch

分組路由

通過Group方法來生成一個分組,然後使用這個分組來註冊多個統一分類的路由

/*
/sdk/search_ip
/sdk/search_mac

/object/host
/object/switch
*/
route := gin.Default()

// sdk 類別
sdkGroup := route.Group("/sdk")  
sdkGroup.GET("/search_mac", func(c *gin.Context) {
    c.String(200, "sdk search mac")
})
sdkGroup.GET("/search_ip", func(c *gin.Context) {
    c.String(200, "sdk search ip")
})

// object
objGroup := route.Group("/object")  
objGroup.GET("/host", func(c *gin.Context) {
    c.String(200, "get object host")
})
objGroup.POST("/switch", func(c *gin.Context) {
    c.String(200, "post object switch")
})

如果需要對sdk類別的路由進行統一呼叫某個中介軟體,也可以使用Group的第二個引數。則不管是search_ip還是search_mac都會呼叫這個中介軟體

sdkGroup := route.Group("/sdk", func(c *gin.Context) {
    fmt.Println("這是sdk中介軟體")
})

巢狀分組路由

分組路由也可以繼續分組,達到巢狀分組的目的

/*
/v1/object/host
/v1/object/switch

/v2/object/host
/v2/object/switch
*/

v1Group := route.Group("/v1")
v1ObjGroup := v1Group.Group("/object")
v1ObjGroup.GET("/host", func(c *gin.Context) {
    c.String(200, "get object host")
})
v1ObjGroup.POST("/switch", func(c *gin.Context) {
    c.String(200, "post object switch")
})

v2Group := route.Group("/v2")
v2ObjGroup := v2Group.Group("/object")
v2ObjGroup.GET("/host", func(c *gin.Context) {
    c.String(200, "get object host")
})
v2ObjGroup.POST("/switch", func(c *gin.Context) {
    c.String(200, "post object switch")
})

Gin-IPs動態路由

有了上面路由分組和巢狀分組的基礎後,我們就可以通過自定義函式來組裝路由,實現路由的動態分發了

  • 程式碼實現
type Route map[string]func(c *gin.Context) // key:uri路徑, value: 中介軟體函式
type Method int

const (
	GET Method = iota
	POST
	DELETE
	PUT
)

// devPkg 對應的路由
type DevPkgGroup struct {
	Name   configure.DevPkg
	Routes []map[Method]Route
}

// 版本對應的路由
type Group struct {
	Version string
	PkgList []DevPkgGroup
}

var RGroups []Group

func InitRouteGroups() {
	RGroups = []Group{
		{"v1", // RGroups[0] 表示 V1, RGroups[1] 表示 V2
			[]DevPkgGroup{},
		},
	}

	/*---------- 更新 V1 路由 ----------*/

	// Object 路由,根據oid遍歷多個
	var objectRoutes []map[Method]Route
	for _, oid := range configure.OidArray {  // 動態新增所有的oid路由
		uri, postFunc := v1_object.AllInstancesPostFunc(oid) // POST  /v1/object/$oid
		objectRoutes = append(objectRoutes, map[Method]Route{POST: {uri: postFunc}})

		uri, getFunc := v1_object.SingleInstanceGetFunc(oid) // GET /v1/object/$oid/$id
		objectRoutes = append(objectRoutes, map[Method]Route{GET: {uri: getFunc}})
	}
	RGroups[0].PkgList = append(RGroups[0].PkgList, DevPkgGroup{configure.ObjectPkg, objectRoutes})

	// Sdk 路由
	var sdkRoutes []map[Method]Route
	// Sdk Get 路由
	sdkGetFuncArr := []func() (string, func(c *gin.Context)){
		v1_sdk.SearchIpFunc, // Get /v1/sdk/search_ip?ip='xxx'
	}

	for _, sdkGetFunc := range sdkGetFuncArr {
		sdkGetUri, sdkGetFunc := sdkGetFunc()
		sdkRoutes = append(sdkRoutes, map[Method]Route{GET: {sdkGetUri: sdkGetFunc}})
	}
	RGroups[0].PkgList = append(RGroups[0].PkgList, DevPkgGroup{configure.SdkPkg, sdkRoutes})
}

func methodMapper(group *gin.RouterGroup, method Method) func(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
	if method == GET {
		return group.GET
	}
	if method == POST {
		return group.POST
	}
	if method == DELETE {
		return group.DELETE
	}
	if method == PUT {
		return group.PUT
	}
	return group.Any
}

// 路由解析
func AddRoute(app *gin.Engine) {
	cmdbGroup := app.Group("/")
	for _, group := range RGroups {
		versionGroup := cmdbGroup.Group(group.Version)
		for _, sdk := range group.PkgList {
			sdkGroup := versionGroup.Group(string(sdk.Name))
			for _, mapper := range sdk.Routes {
				for method, route := range mapper {
					for uri, handler := range route {
						methodMapper(sdkGroup, method)(uri, handler)
					}
				}
			}
		}
	}
}

  • 效果展示
[GIN-debug] POST   /v1/object/HOST           --> Gin-IPs/src/route/v1/object.glob..func1.1 (4 handlers)
[GIN-debug] GET    /v1/object/HOST/:id       --> Gin-IPs/src/route/v1/object.glob..func2.1 (4 handlers)
[GIN-debug] POST   /v1/object/SWITCH         --> Gin-IPs/src/route/v1/object.glob..func1.1 (4 handlers)
[GIN-debug] GET    /v1/object/SWITCH/:id     --> Gin-IPs/src/route/v1/object.glob..func2.1 (4 handlers)
[GIN-debug] GET    /v1/sdk/search_ip         --> Gin-IPs/src/route/v1/sdk.glob..func1.1 (4 handlers)

實現了動態路由之後我們的路由管理變得非常簡單,且很多程式碼可以重複利用。而且在此基礎上,我們還可以通過對不同的路由進行許可權控制,以實現許可權的精細管理。

Github 程式碼

請訪問 Gin-IPs 或者搜尋 Gin-IPs

相關文章