插件接口速查¶
最后更新: 2026-08-04
核心描述符¶
所有插件通过 Descriptor 定义,无需继承任何基类。
&plugin.Descriptor{
Name: "myplugin", // 必填,全局唯一
Version: "1.0.0", // 建议填写(semver)
Deps: []string{"storage"}, // 前置依赖
// 元数据(影响 /help 显示)
Meta: &plugin.Metadata{
Author: "Team",
Description: "我的插件",
HelpText: "/hello - 打招呼",
Category: "工具",
Tags: []string{"示例"},
},
// 初始化(必填)
Setup: func(ctx *plugin.SetupContext) (any, error) {
p := &MyPlugin{}
ctx.Reg.RegisterCommand(eventctx.EventGroup, "/hello").Handle(p.handle)
return p, nil // 导出到容器(nil 也合法)
},
// 清理(可选)
Teardown: func(ctx *plugin.TeardownContext) error {
ctx.Log.Info("stopping")
return nil
},
}
SetupContext — Setup 阶段的全部 API¶
| 字段 | 类型 | 说明 |
|---|---|---|
ctx.Reg |
RegistryWriter |
注册 Matcher / Command |
ctx.Log |
PluginLogger |
带插件名前缀的结构化日志 |
ctx.Info |
PluginInfo |
插件系统只读视图 |
ctx.Admin |
ManagerWriter |
管理写视图(仅 Privileged:true 时非 nil) |
ctx.Config |
plugin.Config |
插件配置(来自 config.yaml plugins 节) |
ctx.EventBus |
EventBus |
插件间事件总线 |
ctx.DryRun |
bool |
Smart 注册依赖推断阶段为 true |
ctx.Spawn(fn) |
- | 启动生命周期绑定的后台 goroutine |
ctx.SpawnNamed(name, fn) |
- | 有名称的后台 goroutine |
ctx.NewTaskGroup() |
*TaskGroup |
创建并发任务组(短生命周期,可等待结果) |
ctx.Batch(fns...).Wait() |
error |
并发执行一批函数并等待全部完成 |
注册 Matcher¶
// 事件匹配器
ctx.Reg.RegisterCommand(eventctx.EventGroup, "/ping").
Handle(func(c *eventctx.Context) error {
c.Reply(platform.TextMessage("Pong!"))
return nil
})
// 命令匹配器(自动 O(1) 索引)
ctx.Reg.RegisterCommand(eventctx.EventGroup, "/status").
Handle(p.handleStatus)
依赖获取¶
// Service[T]:获取必要依赖(返回 ServiceProxy,在依赖热重载后仍有效)
store := plugin.Service[storage.Plugin](ctx, "storage")
store.Get() // 运行时获取实际值
// TryService[T]:获取可选依赖,找不到返回 nil
cache, ok := plugin.TryService[cache.Plugin](ctx, "cache")
// ExportIface[T]:以接口类型额外导出,消费方通过 Service[T] 按接口获取
// 生产者:
plugin.ExportIface[io.Writer](ctx, "log-writer", impl)
// 消费者:
writer := plugin.Service[io.Writer](ctx, "log-writer")
PluginInfo — 只读查询接口¶
通过 ctx.Info 访问,也可在插件之间传递。
// 状态查询
ctx.Info.IsLoaded("storage") // bool
ctx.Info.IsDisabled("debug") // bool
ctx.Info.GetStatus("weather") // *plugin.Status, nil if not found
ctx.Info.List() // []string — 所有已注册插件名
ctx.Info.Count() // int
ctx.Info.GetMetadata("weather") // *plugin.Metadata, bool
ctx.Info.ListWithMetadata() // map[string]*plugin.Metadata
ctx.Info.GetLoadOrder() // []string
ctx.Info.Get("storage") // *plugin.Instance, bool
// Engine 只读视图(不能调用任何写操作)
reader := ctx.Info.Coordinator() // engine.Reader
cmds := reader.GetAllCommands() // []engine.CommandInfo
ManagerWriter — 管理写视图(Privileged 插件)¶
声明 Privileged: true 后,ctx.Admin 为非 nil,可调用写操作:
&plugin.Descriptor{
Name: "admin",
Privileged: true, // ← 声明需要管理权限
Setup: func(ctx *plugin.SetupContext) (any, error) {
// 只读:通过 ctx.Info
plugins := ctx.Info.List()
// 写操作:通过 ctx.Admin
if err := ctx.Admin.Reload("weather"); err != nil { ... }
if err := ctx.Admin.Disable("debug"); err != nil { ... }
if err := ctx.Admin.Enable("debug"); err != nil { ... }
if err := ctx.Admin.Unregister("old"); err != nil { ... }
return &AdminPlugin{admin: ctx.Admin, info: ctx.Info}, nil
},
}
| 方法 | 说明 |
|---|---|
Reload(name) |
热重载插件 |
Disable(name) |
禁用(暂停 Matcher,保留容器条目) |
Enable(name) |
启用已禁用的插件 |
Unregister(name) |
注销插件(完全卸载) |
ForceUnregister(name) |
强制注销(忽略 Unload 错误) |
TeardownContext — Teardown 阶段¶
Teardown: func(ctx *plugin.TeardownContext) error {
ctx.Log.Info("plugin stopping")
ctx.API // Setup 返回的 API 对象
ctx.Config // 插件配置
return nil
},
Advanced 高级选项¶
Advanced: &plugin.Advanced{
// 热重载策略
Strategy: plugin.ReloadInPlace, // 默认:原地重载
// Strategy: plugin.ReloadBlueGreen // 蓝绿重载(先启动新实例再停旧实例)
// 原地重载时调用(Strategy == ReloadInPlace 时有效)
Reload: func(ctx *plugin.SetupContext) error {
// 重新注册 Matcher 等
return nil
},
// 热重载状态保存/恢复
SaveState: func() (any, error) { return myState, nil },
RestoreState: func(state any) error { myState = state.(MyState); return nil },
// 依赖重载通知
OnDependencyReloaded: func(depName string) {
// 某依赖插件被重载时调用
},
},
后台 goroutine(生命周期绑定)¶
Setup: func(ctx *plugin.SetupContext) (any, error) {
ctx.Spawn(func(runCtx context.Context) {
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for {
select {
case <-ticker.C:
cleanup()
case <-runCtx.Done():
return
}
}
})
return p, nil
},
框架在 Teardown 前自动 cancel 所有 goroutine 并等待退出,无需手动管理。
何时用 Spawn vs NewTaskGroup?¶
ctx.Spawn/ctx.SpawnNamed— 长驻后台 goroutine,生命周期=插件本身,fire-and-forget(如定时清理、自动保存)ctx.NewTaskGroup— 短生命周期并发任务,需要等待结果和收集错误(如并发网络请求后聚合)
// Spawn:长驻 daemon
ctx.Spawn(func(runCtx context.Context) {
for { /* 定时任务 */ }
})
// TaskGroup:短任务并发 + 等待结果
g := ctx.NewTaskGroup()
for _, url := range urls {
g.Go(func(taskCtx context.Context) error { ... })
}
if err := g.Wait(); err != nil {
ctx.Log.Warnf("部分请求失败: %v", err)
}
DryRun 保护¶
Smart 注册模式会多次执行 Setup 进行依赖推断,此时 ctx.DryRun == true。
ctx.Reg、ctx.EventBus、ctx.Spawn 已自动替换为 no-op,大多数插件无需判断。
仅当 Setup 中有网络 I/O、进程级全局变量写入等副作用时才需要:
Setup: func(ctx *plugin.SetupContext) (any, error) {
if !ctx.DryRun {
p.metrics = initPrometheusMetrics() // 全局注册,只做一次
}
ctx.Reg.RegisterCommand(...)
return p, nil
},
插件配置¶
Setup: func(ctx *plugin.SetupContext) (any, error) {
cfg := ctx.Config
if cfg != nil {
apiKey := cfg.GetString("api_key", "")
timeout := cfg.GetDuration("timeout", 10*time.Second)
retries := cfg.GetInt("max_retries", 3)
enabled := cfg.GetBool("enabled", true)
cfg.OnChange(func(key string, oldVal, newVal any) {
// 配置变更回调
})
}
return p, nil
},
对应 config.yaml:
plugins:
myplugin:
api_key: "your-key"
timeout: "10s"
max_retries: 3
enabled: true