go语言动态执行go代码
今天群里闲聊的时候,群友想实现动态插件效果 之前写插件,要么是js,要么是python这类动态语言 用go能实现吗? 答案是可以的 突然想到之前用到的一个库,可以实现动态执行go代码
go语言动态执行go代码
发布时间:2025-11-20 (2025-11-20)

今天群里闲聊的时候,群友想实现动态插件效果

之前写插件,要么是js,要么是python这类动态语言

用go能实现吗?

答案是可以的

突然想到之前用到的一个库,可以实现动态执行go代码

go get github.com/traefik/yaegi

简单分享一下这个库的用法,在应对一些简单场景有奇效

interp.New(options Options) *Interpreter:创建解释器实例

i.Use(values Exports):注入符号(包、函数、变量)到解释器,用于宿主程序向动态代码暴露能力

i.Eval(src string) (res reflect.Value, err error):执行动态代码,返回执行结果(如变量值、函数返回值)和错误

执行代码片段

package main

import (
  "github.com/traefik/yaegi/interp"
  "github.com/traefik/yaegi/stdlib"
)

func main() {
  i := interp.New(interp.Options{})
  // 加载标准库(必须加载才能使用 fmt、os 等包)
  i.Use(stdlib.Symbols)

  // 执行代码
  _, err := i.Eval(`
import "fmt"

func add(a, b int) int {
    return a + b
}

func main(){
  fmt.Println("方式 3 结果:", add(30, 40))
}
`)
  if err != nil {
    panic(err)
  }
}

打包之后也是可以执行的

获取动态代码的变量

通过 i.Eval 获取动态代码中定义的变量或函数,再通过类型断言使用。

package main

import (
  "fmt"
  "github.com/traefik/yaegi/interp"
  "github.com/traefik/yaegi/stdlib"
)

func main() {
  i := interp.New(interp.Options{})
  // 加载标准库(必须加载才能使用 fmt、os 等包)
  i.Use(stdlib.Symbols)

  // 执行代码
  _, err := i.Eval(`
var dynamicVar string = "fengfeng"

func dynamicAdd(a, b int) int {
  return a + b
}
`)
  if err != nil {
    panic(err)
  }

  // 1. 宿主读取动态变量
  dynamicVar, err := i.Eval(`dynamicVar`)
  if err != nil {
    panic(err)
  }
  fmt.Println("读取动态变量:", dynamicVar.Interface().(string)) // 输出:读取动态变量:我是动态变量

  // 2. 宿主调用动态函数(通过类型断言转为函数类型)
  dynamicAddVal, err := i.Eval(`dynamicAdd`)
  if err != nil {
    panic(err)
  }
  // 断言为函数类型:func(int, int) int
  dynamicAdd := dynamicAddVal.Interface().(func(int, int) int)
  res := dynamicAdd(5, 3)
  fmt.Println("调用动态函数:", res) // 输出:调用动态函数:8
}

宿主向动态代码交互

在动态代码里面获取宿主里面的变量和函数

package main

import (
  "fmt"
  "github.com/traefik/yaegi/interp"
  "github.com/traefik/yaegi/stdlib"
  "reflect"
)

func hostGreet(name string) string {
  return fmt.Sprintf("宿主程序问候:Hello, %s!", name)
}

var hostVersion = "v1.0.0"

func main() {
  // 创建解释器,指定 GoPath 避免警告
  i := interp.New(interp.Options{})

  // 加载标准库
  i.Use(stdlib.Symbols)

  i.Use(interp.Exports{
    "host/host": map[string]reflect.Value{
      "Greet":   reflect.ValueOf(hostGreet),
      "Version": reflect.ValueOf(hostVersion),
    },
  })

  _, err := i.Eval(`
import (
  "fmt"
  "host"
)

func main(){
  fmt.Println(host.Greet("动态代码用户"));
  fmt.Println("宿主版本:", host.Version);
}
`)
  if err != nil {
    fmt.Printf("执行失败:%v\n", err)
    panic(err)
  }
}

动态执行go文件

把上面的代码,放到一个文件里面,然后直接加载这个文件也是可以的

package main

import (
  "fmt"
  "github.com/traefik/yaegi/interp"
  "github.com/traefik/yaegi/stdlib"
)

func main() {
  // 创建解释器,指定 GoPath 避免警告
  i := interp.New(interp.Options{})

  // 加载标准库
  i.Use(stdlib.Symbols)

  val, err := i.EvalPath("xxx.go")
  fmt.Println(val, err)

}

需要注意的是,不是任何go代码都可以执行,有些go代码是执行不了的

  • .s不支持程序集文件( )。
  • 不支持调用 C 代码(没有虚拟“C”包)。
  • 不支持有关编译器、链接器或嵌入文件的指令。
  • 不能动态添加要从预编译代码中使用的接口,因为需要预编译接口包装器。
  • 使用 %T表示类型reflect和打印值在编译模式和解释模式下可能会产生不同的结果。
  • 解释执行计算密集型代码的速度可能比编译执行慢得多。

用之前做下测试,把自己要用的场景都覆盖完,看看能不能行

动态插件案例

其实就是封装api调用这个过程

我的想法就是每个模型创建一个插件

比如qianwen.go,chatgpt.go

在宿主程序里面写完整的逻辑

可以在插件里面重写几个重点方法

宿主程序

package main

import (
  "fmt"
  "github.com/traefik/yaegi/interp"
  "github.com/traefik/yaegi/stdlib"
  "io"
  "net/http"
  "reflect"
)

type Config struct {
  Url string
}
type Req struct {
  Content string
}

func main() {
  // 创建解释器,指定 GoPath 避免警告
  i := interp.New(interp.Options{})

  // 加载标准库
  i.Use(stdlib.Symbols)

  _, err := i.EvalPath("utils/qianwen.go")
  if err != nil {
    panic(err)
  }

  _setModel, err := i.Eval("utils.SetModel")
  if err != nil {
    // 没有重写utils.SetModel方法,用默认的
    return
  }
  // 重写了setModel,用重写的方法
  var config = Config{
    Url: "http://xxx.com",
  }
  _setModel.Call([]reflect.Value{
    reflect.ValueOf(&config),
  })
  fmt.Println("重写之后的配置对象", config)

  // 调用do方法
  _do, err := i.Eval("utils.Do")
  if err != nil {
    // 没有重写utils.SetModel方法,用默认的
    return
  }

  var req = Req{
    Content: "你好",
  }
  val := _do.Call([]reflect.Value{
    reflect.ValueOf(&req),
  })
  _response, _err := val[0], val[1]
  if !_err.IsNil() {
    err = _err.Interface().(error)
    if err != nil {
      fmt.Println("请求错误", err)
      return
    }
  }
  response := _response.Interface().(*http.Response)
  byteData, _ := io.ReadAll(response.Body)
  fmt.Println("请求响应", string(byteData))
}

插件程序

package utils

import (
  "fmt"
  "net/http"
  "reflect"
)

// SetModel 可以修改配置对象
func SetModel(config any) {
  c := reflect.ValueOf(config)
  c.Elem().FieldByName("Url").SetString("http://qianwen.com")
  fmt.Println("重写SetModel", config)
}

// Do 请求的钩子函数
func Do(req any) (response any, err error) {
  fmt.Println("开始请求", req)

  response, err = http.Get("https://baidu.com")
  //err = errors.New("出错了")
  return
}