Golang应用中添加脚本支持的方法探讨

Golang应用中添加脚本支持的方法探讨 我刚入职一家公司,他们的软件负责与不同系统进行集成。 目前一切顺利,但问题在于每个客户都有一个独立的代码版本。 我认为更好的做法是拥有一个通用的基础代码核心,适用于所有客户,而差异则通过核心外部的插件或代码扩展机制来处理。 这些差异将通过脚本在核心的某些预定义事件(例如OnLoad、onStart、OnNewFile等)中执行。 你知道有什么库或文档可以帮助我完成这项任务吗?

5 回复

非常感谢!!!

更多关于Golang应用中添加脚本支持的方法探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


非常感谢您的回答!!!

我曾经使用 GitHub - milochristiansen/lua: 一个用 Go 编写的 Lua 5.3 虚拟机和编译器 来为一些 Go 代码添加 Lua 脚本功能。当出现问题时,Milo 的响应非常及时。

你好,

我想到几个方案(排名不分先后):

  1. 将“插件”编写为独立的命令行应用,让核心程序通过标准输入/输出或gRPC与它们通信。
  2. 在核心程序中嵌入一个脚本解释器。我立刻能想到的有Lua和JavaScript解释器——你可以在GitHub上搜索 language:go scriptlanguage:go Lualanguage:go javascript 来寻找。
  3. 用Go语言编写插件,并使用像Yaegi这样的Go解释器来运行它们。

我并不特别偏爱其中任何一种。像很多情况一样,选择取决于具体的用例和需求。

在Golang应用中实现脚本支持,有几个成熟的方案可以考虑:

1. Go Plugin 原生方案

使用Go内置的plugin包,适合编译型插件:

// main.go
package main

import (
    "plugin"
)

type EventHandler interface {
    OnLoad() error
    OnStart() string
}

func main() {
    p, err := plugin.Open("plugin.so")
    if err != nil {
        panic(err)
    }
    
    sym, err := p.Lookup("Handler")
    if err != nil {
        panic(err)
    }
    
    handler := sym.(EventHandler)
    handler.OnLoad()
}

2. Lua 脚本集成

使用gopher-lua或go-lua库,适合动态脚本:

// 使用gopher-lua示例
package main

import (
    "github.com/yuin/gopher-lua"
)

func main() {
    L := lua.NewState()
    defer L.Close()
    
    // 注册事件回调
    L.SetGlobal("OnLoad", L.NewFunction(func(L *lua.LState) int {
        // 处理OnLoad事件
        return 0
    }))
    
    // 执行客户脚本
    if err := L.DoFile("customer_script.lua"); err != nil {
        panic(err)
    }
}

3. JavaScript/TypeScript 支持

使用goja或otto库:

// 使用goja示例
package main

import (
    "github.com/dop251/goja"
    "fmt"
)

func main() {
    vm := goja.New()
    
    // 暴露事件接口
    vm.Set("OnNewFile", func(call goja.FunctionCall) goja.Value {
        filename := call.Argument(0).String()
        fmt.Printf("New file: %s\n", filename)
        return nil
    })
    
    // 执行客户脚本
    script := `
    OnNewFile("customer_data.txt");
    function OnStart() {
        return "Customer specific logic";
    }
    `
    
    _, err := vm.RunString(script)
    if err != nil {
        panic(err)
    }
}

4. 通用插件框架

使用hashicorp/go-plugin或natefinch/pie:

// 使用go-plugin的RPC方案
package main

import (
    "github.com/hashicorp/go-plugin"
    "net/rpc"
)

type Plugin interface {
    OnLoad() error
    OnStart() string
}

type PluginRPC struct{ client *rpc.Client }

func (p *PluginRPC) OnLoad() error {
    var resp error
    err := p.client.Call("Plugin.OnLoad", new(interface{}), &resp)
    return err
}

func main() {
    client := plugin.NewClient(&plugin.ClientConfig{
        HandshakeConfig: plugin.HandshakeConfig{
            ProtocolVersion:  1,
            MagicCookieKey:   "CUSTOMER_PLUGIN",
            MagicCookieValue: "customer",
        },
        Plugins: map[string]plugin.Plugin{
            "customer": &CustomerPlugin{},
        },
    })
    
    defer client.Kill()
    rpcClient, _ := client.Client()
    raw, _ := rpcClient.Dispense("customer")
    plugin := raw.(Plugin)
    plugin.OnLoad()
}

5. 事件驱动架构实现

package main

import (
    "sync"
)

type Event string

const (
    EventOnLoad    Event = "OnLoad"
    EventOnStart   Event = "OnStart"
    EventOnNewFile Event = "OnNewFile"
)

type EventHandler func(params map[string]interface{}) error

type EventBus struct {
    handlers map[Event][]EventHandler
    mu       sync.RWMutex
}

func (eb *EventBus) Subscribe(event Event, handler EventHandler) {
    eb.mu.Lock()
    defer eb.mu.Unlock()
    eb.handlers[event] = append(eb.handlers[event], handler)
}

func (eb *EventBus) Emit(event Event, params map[string]interface{}) error {
    eb.mu.RLock()
    defer eb.mu.RUnlock()
    
    for _, handler := range eb.handlers[event] {
        if err := handler(params); err != nil {
            return err
        }
    }
    return nil
}

// 客户脚本通过配置文件注册事件处理器

选择方案时考虑:

  • 性能要求:Go Plugin性能最好,脚本语言需要解释执行
  • 部署复杂度:脚本方案更易热更新,Go Plugin需要重新编译
  • 客户技术栈:根据客户熟悉程度选择Lua、JS或Python

建议采用混合方案:核心事件总线 + 多种脚本引擎支持,通过配置文件决定使用哪种脚本引擎执行客户特定逻辑。

回到顶部