go基于gin框架后端服务的插件化开发(附带xml解析实操)

Go基于GIN框架的插件化开发

简言

上期实现了用户的注册登录效果,这期则是实现插件化开发,可以让你的作品更“灵活”。还有xml的相关配置
和上期用户注册登录相比,就多了个prepare.go,主要负责项目启动后的初始化,读取配置文件啊什么的
请添加图片描述
还有就是server.go增加了一个新的路由组(Extensiongroup)

var Extensiongroup = engine.Group("/extensions/")

用到的包仍然是老三样(这里不够加密,就只用两个)

github.com/gin-gonic/gin
github.com/oswaldoooo/octools@v1.1

首先是需要用到的全局变量

//这里可以换成os.Getenv(变量名),然后提前将你项目根目录设置为变量名,这里为了方便直接上具体路径了
var ROOTPATH = "/Users/oswaldo/dev/golang/newstart"
//默认监听端口,如果配置文件中的端口有问题,就用默认端口
var port = 8001
//插件匹配字符串表,避免用相同的pattern,导致冲突,后面用空struct是为了省空间
var ExtensionPatterMap = make(map[string]struct{})
//octools/toolsbox初始化日志文件,第一个参数为prefix,后面为日志文件的地址,请确保日志文件的目录存在,日志文件若没有会自己创建,但目录不存在就会报错(但不会退出程序)
var errorlog = toolsbox.LogInit("error", ROOTPATH+"/logs/error.log")

xml的struct和xml的格式

type cnfinfo struct {
	XMLName xml.Name    `xml:"studentmanager"`
	Port    int         `xml:"port"`
	Plugins plugin_info `xml:"plugin"`
}
type plugin_info struct {
	XMLName     xml.Name `xml:"plugin"`
	Plugin_Info []struct {
		XMLName     xml.Name `xml:"plugin_info"`
		ClassName   string   `xml:"classname"`
		Plugin_Name string   `xml:"plugin_name"`
	} `xml:"plugin_info"`
}

这里缩进了就是儿子,没缩进就是兄弟,前提是要被包进去,port, plugin是studentmanager的儿子,两个plugin_info是plugin的儿子,以此类推。这里从上面struct可以看出plugin_info那里是数组,所以xml这里的plugin_info可以有多个

<?xml version="1.0" encoding="UTF-8"?>
<studentmanager>
    <port>9001</port>
    <plugin>
            <!-- 仅做展示,不要复制粘贴后直接跑 -->
        <plugin_info>
            <classname>extension</classname>
            <plugin_name>pluginname</plugin_name>
        </plugin_info>
        <plugin_info>
            <classname>extension</classname>
            <plugin_name>pluginname</plugin_name>
        </plugin_info>
    </plugin>
</studentmanager>

初始化

func init() {
	content, err := ioutil.ReadFile(ROOTPATH + "/conf/site.xml")
	if err == nil {
		cnf := new(cnfinfo)//保险起见,一定new,来的快,避免使用 var cnf cnfinfo,搞不好就是空指针报错 :-/
		err = xml.Unmarshal(content, cnf)
		if err == nil {
		//端口合理就设置配置文件端口为监听端口,不合理就继续用默认
			if cnf.Port > 0 {
				port = cnf.Port
			}
			bad := 0//配置文件中的无效插件配置数量
			var pluginer *plugin.Plugin
			fmt.Println("=====start read plugin info=====")
			for _, ve := range cnf.Plugins.Plugin_Info {
			//把classname全部转为小写可以为你以后是否大小写classname解忧
				switch strings.ToLower(ve.ClassName) {
				//这里是匹配支持的插件接口的插件类,这里后面就会用特制的解析器去解析这类插件
				case "extension": //octools/toolsbox插件扫描业务,插件名可以带后缀.so也可以不带
					pluginer, err = toolsbox.ScanPluginByName(ve.Plugin_Name, ROOTPATH+"/plugins/")
					if err == nil {
					//使用1v1设计的解析器来解析符合条件的插件
						err = lookupextension(pluginer)
					}
					if err != nil {
					//解析插件不符合要求,将具体错误写入开局初始化好的errorlog中
						errorlog.Println(err.Error())
						bad++
					}
				default:
					bad++
				}
			}
			fmt.Printf("read %v plugin info,%v bad info\n", len(cnf.Plugins.Plugin_Info), bad)
		} else {
			fmt.Println("unmarshal site.xml failed >>", err)
		}
	} else {
		fmt.Println("read site.xml failed,error>>", err)
		os.Exit(1)
	}
}

extension解析器,和下文试例插件对比对比,就知道其中奥秘了

func lookupextension(pluginer *plugin.Plugin) (err error) {
	srm, err := pluginer.Lookup("Pattern")
	if err == nil {
		pattern := *srm.(*string)
		//pattern存在就说明其他插件已经注册了这个路由了,所以你就该换一个pattern了
		if _, ok := ExtensionPatterMap[pattern]; ok {
			err = errors.New(pattern + " is exist")
			return
		}
		srm, err = pluginer.Lookup("Method")
		if err == nil {
			method := *srm.(*string)
			srm, err = pluginer.Lookup("ExtensionFunc")
			if err == nil {
				resfunc := srm.(func(*gin.Context))
				//根据匹配到的方法进行请求方式设置
				switch strings.ToLower(method) {
				case "get":
					Extensiongroup.GET(pattern, resfunc)
				case "post":
					Extensiongroup.POST(pattern, resfunc)
				case "put":
					Extensiongroup.PUT(pattern, resfunc)
				case "delete":
					Extensiongroup.DELETE(pattern, resfunc)
				default:
					err = errors.New("unkonwn request method")
				}
				if err == nil {
					ExtensionPatterMap[pattern] = struct{}{}
				}
			}
		}
	}
	return
}

试例插件

package main
//package 必须main!!!
import "github.com/gin-gonic/gin"

var Pattern = "greet"
var Method = "get"

func ExtensionFunc(ctx *gin.Context) {
	ctx.JSON(200, gin.H{"msg": "hello,im a extension :-)"})
}

插件编译(如果你记不住,或时间久了容易忘记,也可用我自制shell脚本)

go build --buildmode=plugin -o pluginname.so pluginname.go
#拉取脚本,此脚本是编译当前目录下所有.go文件为.so插件
curl https://brotherhoodhk.org/products/shells/build-all.sh -O
#此脚本是编译当前目录下指定.go文件为.so插件(使用时只需输入文件名但别带文件后缀名!!!,例如,greet.go输入文件名就是greet)
curl https://brotherhoodhk.org/products/shells/build.sh -O

现在,我们通过外挂插件的方式,就可以日后在本体基础上新增加其他玩法,无需动本体自身,只需要单独的插件开发编译后,修改修改配置文件就ok了

用试例插件演示演示

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值