原因
近期,我正在筹备一个开源项目,该项目是一个能快速生成后台代码的工具,采用了Hertz+Gorm+Gen+Vben Admin技术栈。在开发过程中,我始终在思考如何实现框架与后续业务的分离,以使快速生成的业务代码更加清晰,且业务的修改不会对框架产生影响。这样也能更好地迭代这个开源项目,并在使用过程中不断完善。
在凝视着vscode,陷入沉思时,我突然灵光一闪,想到了插件化。目前许多工具都正在转变为插件化,它们在提供最基础的功能的同时,也允许用户以自己的方式增强这个工具。
开始
Go语言在插件化方面非常的方便,只需要将插件路由注册到主路由上就可以使用了。接下来我将实现过程记录一下:
一、在框架中加入插件功能
1、先创建一个Plugin 插件模式接口化文件
package plugin
import "github.com/cloudwego/hertz/pkg/route"
const (
OnlyFuncName = "Plugin"
)
// Plugin 插件模式接口化
type Plugin interface {
// Register 注册路由
Register(group *route.RouterGroup)
// RouterPath 用户返回注册路由
RouterPath() string
}
2、然后批量初始化插件路由
package initialize
import (
"fmt"
……
)
//插件初始化,注册上面创建的接口文件
func PluginInit(group *route.RouterGroup, Plugin ...plugin.Plugin) {
for i := range Plugin {
PluginGroup := group.Group(Plugin[i].RouterPath())
Plugin[i].Register(PluginGroup)
}
}
// 安装插件
func InstallPlugin(Router *server.Hertz) {
PublicGroup := Router.Group("")
fmt.Println("无鉴权插件安装==》", PublicGroup)
PrivateGroup := Router.Group("",middleware.JwtMiddleware.MiddlewareFunc())
fmt.Println("鉴权插件安装==》", PrivateGroup)
……
}
3、在主路由上调用上面的函数
package initialize
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
)
func Routers() *server.Hertz {
Router := server.Default()
InstallPlugin(Router) // 安装插件
//下面继续写主框架中的路由地址
……
return Router
}
4、在man中调用路由
package main
import (
……
)
func main() {
initialize.Routers()
}
二、插件实现方法
如果我要实现CMS的功能,
我在根目录创建了一个名字为plugint的文件夹,在文件夹中创建目录:
plugint
└── cms
├── README.MD
├── api
│ └── cms_api.go
├── global
│ └── global.go
├── main.go
├── model
│ └── res
│ └── article.go
├── router
│ ├── cms_router.go
│ └── enter.go
└── service
└── post.go
1、api:直接写接口方法
//cms_api.go 文件
package api
import(
……
)
type CMSApi struct {
}
//提交文章
func (b *CMSApi) PostArticle(c context.Context, cx *app.RequestContext) {
article := &res.ArticleRes{}
if err := cx.BindJSON(&article); err != nil {
hlog.Error("参数错误!", zap.Error(err))
response.FailWithMessage(err.Error(), cx)
return
}
response.OkWithData(article, cx)
}
2、model:写一些结构体
3、global:在插件中的全局变量,在这里只写了一个例子,全局保存Title的内容
package global
var GlobalConfig = new(res.ArticleRes) //GlobalConfig为model中的结构体
4、router路由
//cms_router.go文件
package router
import (
……
)
type CMSRouter struct {
}
//初始化路由
func (s *CMSRouter) InitCMSRouter(group *route.RouterGroup) {
genRouterWithoutRecord := group.Use(middleware.Cors())
cmsApi := api.CMSApi{}
{
genRouterWithoutRecord.POST("post", cmsApi.PostArticle)
}
}
5、main.go:很重要的文件,是插件的进出口:
package cms
import (
……
)
type cmsPlugin struct {
}
//初始化插件,可以设置接受参数,这里只接受了一个Title,并将其保存到当前插件的全局变量中
func CreateCMSPlugin(Title string) *cmsPlugin {
//这里可以传递配置参数,将参数放到当前插件的global中,作为插件全局变量使用
global.GlobalConfig.Title = Title
return &cmsPlugin{}
}
//注册路由
func (*cmsPlugin) Register(group *route.RouterGroup) {
router.RouterGroupApp.InitCMSRouter(group)
}
//设置路由路径
func (*cmsPlugin) RouterPath() string {
return "cms"
}
6、service:自己写逻辑吧
三、结果
在写好插件后,将插件的man.go中的初始化插件方法,注册到initialize/InstallPlugin中,就是第一步骤中的第2个中:
package initialize
……
// 安装插件
func InstallPlugin(Router *server.Hertz) {
PublicGroup := Router.Group("")
fmt.Println("无鉴权插件安装==》", PublicGroup)
PrivateGroup := Router.Group("",middleware.JwtMiddleware.MiddlewareFunc())
fmt.Println("鉴权插件安装==》", PrivateGroup)
//添加到这里,如果无需jwt权限则使用PublicGroup
PluginInit(PrivateGroup, cms.CreateCMSPlugin("传递参数"))
}
可以启动项目调用插件了,地址为:http://localhost:8888/cms/post
总结
这种方法的应用十分简单,且代码分离非常清晰。唯一的问题在于,插件的注册还需要手动添加到文件中。接下来,我将通过前端配置或寻找其他方式,使功能实现更加优雅。