第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的validateSingleAction()(计数字段)与templatePaths()(模板存在性校验)中处理新类型(isEmpty()已自动派生自ActionType(),无需改动); - 在
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 ""
}
再更新 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 是泛型解析器,需要传入「模板构造闭包」「坐标构造闭包」与动作名(这也是 click 家族支持 {template: 路径} 与 {x, y} 两种映射写法的由来):
func FromConfig(cfg config.Action, defaultTimeout int) (Runner, error) {
switch {
// ... 已有分支 ...
case cfg.Hover != nil:
return parseTargetedAction(cfg.Hover,
func(tpl string) *HoverAction { return &HoverAction{targetedAction: targetedAction{template: tpl}} },
func(pt geom.Point) *HoverAction { return &HoverAction{targetedAction: targetedAction{coord: &pt}} },
"hover",
)
}
return nil, fmt.Errorf("未知的动作类型")
}
这样,当 YAML 中出现 hover 字段时,工厂就能创建出 HoverAction。
17.5 第四步:加入验证
打开 internal/config/loader.go,让验证逻辑认识新动作:
isEmpty()现在直接由ActionType()派生(return a.ActionType() == ""),只要第一步给ActionType()加了Hover分支,无需再改isEmpty();- 在
validateSingleAction()中,把Hover计入「已设置字段计数」,否则click与hover同时出现时只计到 1 个,「一个动作项只能有一个动作字段」的约束对新动作失效; - 若 hover 用模板,还应确保
templatePaths()能收集到它的模板路径(字符串或{template}写法经appendTargetPath提取),从而参与「模板文件存在性」校验。
// 现状:isEmpty 无需改动
func (a *Action) isEmpty() bool { return a.ActionType() == "" }
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:配置加载与验证测试;config/validation_test.go:加载期校验规则测试(on_error枚举、max_retries、inputs唯一性等);cmd/robotgo-flow/main_test.go:CLI 参数重排(reorderFlags)等入口测试;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,否则编译不过; - 五步要齐全:漏了第一步(
ActionType()没有新分支)会导致新动作被当成空动作;漏掉验证(第四步)会破坏「单动作字段」约束;漏掉工厂(第三步)会导致 YAML 里写了却不执行; - 复用已有解析器:
parseTargetedAction、parseWait等能省很多事,优先复用; - 补测试:新动作至少补一个基于 Mock 的执行测试;
- 注意编码:涉及文件路径时记得走
encoding转换。
17.10 小结
本章我们完整走了一遍二次开发流程——以新增 hover 动作为例,演示了官方规定的五个步骤:在 config 定义字段并更新 ActionType/Label、在 action 实现 Runner(必要时扩展 Engine 接口与 Mock)、在 factory 注册分支、在 loader 加入验证、在 recorder 支持录制。我们还讲了如何用 Mock 引擎测试动作、其它扩展维度以及注意事项。清晰的分层与接口设计,让扩展 robotgo-flow 变得有章可循。下一章,我们通过一个综合实战项目把所学融会贯通。