第17章 - 二次开发:扩展新动作类型
robotgo-flow 内置的 19 种动作已能覆盖大多数场景,但总有特殊需求需要自定义。得益于清晰的分层与接口设计,为框架添加一种新动作是一件有章可循的事。本章以「添加一个新动作类型」为主线,完整走一遍二次开发流程。
本章面向有 Go 基础、希望修改/扩展框架源码的读者。若你只是使用者,可略读了解扩展点即可。
17.1 扩展新动作的五个步骤
官方文档明确给出了添加新动作类型的标准流程,共五步,分布在不同的包中:
- 在
internal/config/workflow.go的Action结构体中添加新字段,并更新ActionType()与Label()方法; - 在
internal/action/下新建文件,实现Runner接口(Execute方法); - 在
internal/action/factory.go的FromConfig()中添加对应分支; - 在
internal/config/loader.go的isEmpty()与validateSingleAction()中处理新类型; - 在
internal/recorder/recorder.go中添加录制支持。
下面我们以一个假想的新动作 hover(鼠标悬停) 为例,逐步实现。
17.2 第一步:定义配置字段
打开 internal/config/workflow.go,在 Action 结构体中新增字段:
type Action struct {
// ... 已有字段 ...
Hover any `yaml:"hover,omitempty"` // string(模板) 或 {x, y}
}
hover 与 click 类似,支持模板或坐标,故用 any 类型。
接着更新 ActionType() 方法,让它能识别新动作:
func (a Action) ActionType() string {
switch {
// ... 已有分支 ...
case a.Hover != nil:
return "hover"
}
return "unknown"
}
再更新 Label(),为进度/日志显示提供可读标签:
func (a Action) Label() string {
switch {
// ... 已有分支 ...
case a.Hover != nil:
return "悬停"
}
return "未知动作"
}
17.3 第二步:实现 Runner
在 internal/action/ 下新建 hover.go,实现动作逻辑。参考 click.go 的 targetedAction 复用「模板或坐标」的定位逻辑:
package action
type HoverAction struct {
targetedAction // 复用模板/坐标解析
}
func (a *HoverAction) Execute(eng Engine) error {
x, y, err := a.resolveTarget(eng)
if err != nil {
return err
}
return eng.Hover(x, y) // 调用引擎的悬停能力
}
这里假设我们在 Engine 接口与引擎实现中新增了 Hover(x, y int) error 方法(内部可用 robotgo.Move 或 robotgo.MoveSmooth 把鼠标移到目标而不点击)。因此扩展新动作有时也要扩展 Engine 接口:
- 在
action/action.go的Engine接口中加Hover(x, y int) error; - 在
engine/engine.go中实现Hover; - 在
action/engine_mock.go的 Mock 中也补上Hover,以便测试。
17.4 第三步:注册到工厂
打开 internal/action/factory.go,在 FromConfig 中添加对新字段的分支。可复用已有的 parseTargetedAction 泛型解析器:
func FromConfig(cfg config.Action, defaultTimeout int) (Runner, error) {
switch {
// ... 已有分支 ...
case cfg.Hover != nil:
base, err := parseTargetedAction(cfg.Hover)
if err != nil {
return nil, err
}
return &HoverAction{targetedAction: base}, nil
}
return nil, fmt.Errorf("未知动作类型")
}
这样,当 YAML 中出现 hover 字段时,工厂就能创建出 HoverAction。
17.5 第四步:加入验证
打开 internal/config/loader.go,让验证逻辑认识新动作:
- 在
isEmpty()中,把Hover纳入「非空」判断,否则含 hover 的动作会被误判为空动作; - 在
validateSingleAction()中,把Hover计入「已设置字段计数」,以确保「一个动作项只有一个动作字段」的规则对 hover 也生效; - 若 hover 用模板,还应确保
templatePaths()能收集到它的模板路径,从而参与「模板文件存在性」校验。
func (a Action) isEmpty() bool {
return a.Click == nil && /* ... */ && a.Hover == nil
}
17.6 第五步:支持录制
最后,在 internal/recorder/recorder.go 的动作菜单中加入新动作,让用户能在 record 交互录制时选择它:
- 在
askAction()的菜单里增加一个选项(如「16. Hover」); - 对该选项调用
askTemplateOrCoord(复用模板/坐标询问逻辑),把结果写入Action.Hover。
至此,hover 动作就从「配置定义 → 执行实现 → 工厂注册 → 验证 → 录制」全链路打通,可以在 YAML 中这样使用:
- hover: "templates/menu_trigger.png"
- hover: {x: 400, y: 200}
17.7 测试你的新动作
框架自带完善的测试。二次开发后,应补充并运行测试:
cd src\go
# 运行所有测试
go test ./...
# 运行相关包测试
go test ./internal/config/ -v
go test ./internal/action/ -v
go test ./internal/executor/ -v
# 带覆盖率
go test ./... -cover
参考已有的测试文件:
config/loader_test.go、config/loader_ext_test.go:配置加载与验证测试;action/factory_test.go:工厂创建测试;action/execute_test.go:动作执行集成测试(用engine_mock.go的 Mock 引擎);action/interact_test.go:交互动作测试。
用 Mock 引擎测试动作是关键技巧:由于动作只依赖 Engine 接口,你可以在测试中传入 engine_mock.go 的 Mock,断言动作调用了哪些引擎方法、传了什么参数,而无需真的操作鼠标。为 HoverAction 写一个测试,验证它正确调用了 eng.Hover(x, y) 即可。
17.8 扩展其它维度
除了新增动作,你还可以在其它维度扩展框架:
- 新增引擎能力:在
Engine接口与实现中增加方法(如剪贴板高级操作、特定应用适配),供动作调用; - 新增交互方式:扩展
notify包,接入更丰富的对话框或通知渠道; - 增强图像匹配:在
matcher.go中引入容差匹配、多尺度匹配(应对 DPI 差异)等; - 新增前端:只要遵循 JSON-Line 协议,就能开发 Web、Electron 等其它前端来驱动
serve。
17.9 二次开发注意事项
- 保持接口契约:改动
Engine接口时,务必同步更新真实实现与 Mock,否则编译不过; - 五步要齐全:漏掉验证(第四步)会导致新动作被当成空动作;漏掉工厂(第三步)会导致 YAML 里写了却不执行;
- 复用已有解析器:
parseTargetedAction、parseWait等能省很多事,优先复用; - 补测试:新动作至少补一个基于 Mock 的执行测试;
- 注意编码:涉及文件路径时记得走
encoding转换。
17.10 小结
本章我们完整走了一遍二次开发流程——以新增 hover 动作为例,演示了官方规定的五个步骤:在 config 定义字段并更新 ActionType/Label、在 action 实现 Runner(必要时扩展 Engine 接口与 Mock)、在 factory 注册分支、在 loader 加入验证、在 recorder 支持录制。我们还讲了如何用 Mock 引擎测试动作、其它扩展维度以及注意事项。清晰的分层与接口设计,让扩展 robotgo-flow 变得有章可循。下一章,我们通过一个综合实战项目把所学融会贯通。