# 按键精灵(anjianjingling)· 技术方案 v2.1 > 版本:v2.3(全 C# + 四区界面 + 榫卯积木 + **脚本 ⇄ 积木 双向转换**) > 日期:2026-10-23 > 状态:已实现并可编译运行 --- ## 0. 技术栈(全部 C# / .NET) | 项 | 选型 | 说明 | |---|---|---| | 语言 / 框架 | **C# + WinForms + .NET 10**(`net10.0-windows`) | 单一技术栈,无前端工程 | | 脚本引擎 | **MoonSharp 2.0.0**(纯托管 Lua 5.2) | 无 native 依赖,`dotnet build` 即可 | | 积木编辑器 | **自绘 WinForms 控件 `BlockCanvas`** | GDI+ 圆角积木、拖拽排序、容器嵌套,**非网页** | | 输入模拟 | `SendInput`(user32) | 系统级键鼠,坐标为屏幕物理像素 | | 找图 | OpenCvSharp4 `MatchTemplate` | 相似度阈值 0~1 | | 找色 | `LockBits` 逐像素扫描 | 支持容差 | | 操作录制 | `WH_KEYBOARD_LL` / `WH_MOUSE_LL` 低级钩子 | 录制 → 智能聚合 → 生成 Lua | | 屏幕录像 | GDI 抓帧 + JPEG + 自写 AVI(MJPG) 封装 | 不依赖 ffmpeg,输出可播放 avi | | 内嵌浏览器 | WebView2(仅浏览器标签页用) | 与积木无关,可选功能 | --- ## 0.5 界面布局(四区) ``` ┌──────────────────────────────────────────────────────────────┐ │ 菜单栏 文件 / 积木 / 工具 / 帮助 │ ├──────────────────────────────────────────────────────────────┤ │ 工具栏 ▶启动 F8 ‖暂停 F9 ■停止 F10 │ +新建 复制 删除 │ 录制 截图 │ ├──────────┬───────────────────────────────────┬───────────────┤ │ 任务列表 │ 任务详情 │ 插件 │ │ (左) │ ┌ 任务名 / 启用 / 循环 / 间隔 ─┐ │ 屏幕与定位 │ │ ·任务1 │ │ 脚本代码 积木 录制 浏览器 │ │ 坐标取色器 │ │ ·任务2 │ │ │ │ 框选截图 │ │ ·任务3 │ │ 编辑区 │ │ 找图测试 │ │ │ └───────────────────────────────┘ │ 录制 │ │ 共 N 个 │ │ 系统与帮助 │ ├──────────┴───────────────────────────────────┴───────────────┤ │ 运行日志(跨全宽,可拖动高度) │ ├──────────────────────────────────────────────────────────────┤ │ 状态栏 状态:空闲 F8 启动 · F9 暂停 … 行1,列1 │ └──────────────────────────────────────────────────────────────┘ ``` | 区域 | 内容 | 行为 | |---|---|---| | 左:任务列表 | 自绘列表,每项显示任务名 + 循环次数/间隔 + 状态色条 + **启用勾选框** | 单击切换任务;点勾选框直接启/停;右键新建/复制/删除/上下移/立即运行 | | 中:任务详情 | 顶部属性条(名称/启用/无限/次数/间隔/**启动热键 F1~F7**)+ 四个页签 | 改动实时回写任务,无需保存按钮 | | 右:插件 | 分组卡片按钮:屏幕与定位 / 录制 / 脚本 / 系统与帮助 | 一键调用工具,不占主编辑区 | | 下:日志 | 深色日志区,错误红、成功绿 | 跨全宽,分割条可拖 | --- ## 1. 功能清单 ### 1.1 Lua 脚本 - 完整 Lua 语法(`if / for / while / function / table / string.format`…) - 双写法并存:全局简写 `moveTo(100,200)` + 命名空间 `mouse.moveTo(100,200)` - 语法着色 + 行号栏 + 中文错误提示 - F8 队列运行、F9 暂停、F10 停止、左上角 (0,0) 急停 ### 1.2 积木编程(插片耦合 · 与脚本双向转换) - **单一数据源**:任务只保存 Lua 脚本。积木页是脚本的另一种视图——切到积木页自动「脚本 → 积木」;在积木里增删改(含改参数)自动「积木 → 脚本」回写,两边永远一致,不存在两份代码 - 旧版任务里只有积木没有脚本的,打开时自动把积木转成脚本 - 解析覆盖目录全部命令:流程容器(for/while/if/elseif/else)、找图(ifExists 还原自 do+local __ix 结构)、赋值/自增、mouse.down 等;认不出的行进「自定义代码」块,原样保留不丢失 - 往返稳定:脚本 → 积木 → 脚本 内容一致(二次往返验证通过) #### 插片耦合造型 - **榫卯咬合**:每块顶部有凹槽、底部有凸榫(7px),相邻块互相插接,视觉上是一条拼好的积木链 - **C 形容器**:`循环 N 次`、`当…时循环`、`无限循环`、`如果…否则` —— 左壁 + 内部插槽底 + 底部封口;空容器显示「把积木拖到这里」 - 「否则」是容器内腔里的横向分隔条,把插槽分成上下两半 - **参数 = 白色插槽**:标题里的参数值画成可嵌入的白色槽(Blockly 风格),选中块后右侧改参数即时刷新 - 双击或拖拽添加;拖动改序 / 拖进容器嵌套;黄色描边 = 选中 - 一键「生成 Lua →」「运行积木」,存为 `.ajb`(JSON) ### 1.3 操作录制(F12) - 录制真实的键盘、鼠标点击、滚轮、拖动 - 连续鼠标移动合并为一次 `moveTo`(15ms/3px 采样节流) - 连续可打印字符聚合为 `typeText("...")` - `Ctrl/Alt/Win + X` 自动合成 `hotkey("Ctrl+X")` - 时间差 → `delay(ms)`,可设最大延时截断 - 结果直接填入脚本页或直接运行 ### 1.4 屏幕录像 - 全屏或框选区域录制,帧率 1~30,可含鼠标指针 - 输出 `recordings/rec_yyyyMMdd_HHmmss.avi`(MJPEG),任意播放器可播 - 固定帧率策略:抓不过来就丢帧,保证视频时长 = 真实时长 ### 1.5 任务列表(左侧栏) - 每个任务独立持有:脚本、积木、名称、启用、次数/无限、轮次间隔 - 单击任务 → 中间详情立即载入该任务的脚本与积木(互不干扰) - 右键菜单:新建 / 复制 / 删除 / 上移 / 下移 / 立即运行此任务 - F8 依次执行所有「已启用」任务,每项可设次数 / 无限 / 轮次间隔 - 整个列表保存为 `.ajt`(JSON) ### 1.6 插件面板(右侧栏) | 分组 | 插件 | |---|---| | 屏幕与定位 | 坐标取色器、框选截图、全屏截图、找图测试、打开图库目录 | | 录制 | 键鼠录制、全屏录屏、区域录屏、停止录屏、打开录像目录 | | 脚本 | 运行当前任务、生成 Lua、打开脚本、载入积木 | | 系统与帮助 | 窗口列表、Lua 语法速查、关于 | --- ## 2. 目录结构 ``` anjianjingling/ ├── anjianjingling.csproj ├── app.manifest # requireAdministrator(控制管理员程序必需) ├── Program.cs ├── Forms/ │ ├── MainForm.cs # 主窗体:任务列表 / 详情 / 插件 / 日志 四区布局 │ ├── PickerForm.cs # ★ 坐标取色器(放大镜 + 实时坐标/颜色) │ ├── BlockCanvas.cs # ★ 自绘积木画布(拖拽/嵌套/插入指示线) │ ├── LuaEditor.cs # ★ Lua 编辑器(行号 + 语法着色) │ └── OverlayForm.cs # 框选截图 / 取录屏区域 ├── Lua/ │ ├── LuaEngine.cs # ★ MoonSharp 宿主 + 暂停/停止/急停 + 队列 │ ├── ScriptApi.cs # ★ Lua API 注册(mouse/key/screen/win/browser/clip) │ ├── LuaBlocks.cs # ★ 积木定义表 + Lua 生成器 + JSON │ ├── LuaTask.cs # 任务模型 + .ajt 读写 │ └── ILuaHost.cs # 能力接口(主窗体实现) ├── Capture/ │ ├── InputRecorder.cs # ★ 低级键鼠钩子录制 → Lua │ └── ScreenRecorder.cs # ★ 屏幕录像 + AVI(MJPG) 封装 ├── Input/ MouseInput.cs KeyboardInput.cs ├── Vision/ ScreenCapture.cs ImageFinder.cs ColorFinder.cs ├── Window/ WindowManager.cs ├── Browser/ WebViewBridge.cs ├── Native/ Win32.cs ├── Common/ LogBus.cs VirtualKeys.cs ├── Samples/ demo_loop.lua demo_text.lua demo_record.lua └── images/ # 模板图目录(F11 框选截图默认存这里) ``` --- ## 3. Lua API 参考 ### 3.1 基础 ```lua delay(ms) -- 等待(可被 F9/F10 打断) delayRandom(min, max) -- 随机等待 log("文本") -- 写运行日志 stop() -- 主动结束脚本 random(1, 10) now() -- 随机数 / 时间戳 ``` ### 3.2 鼠标 ```lua moveTo(x, y) moveR(dx, dy) leftClick() / leftClick(x, y) rightClick() / rightClick(x, y) middleClick() doubleClick(x, y) wheel(-1) -- 正数向上,负数向下 drag(x1, y1, x2, y2, ms) mouse.down("left") mouse.up("left") local x, y = mouse.pos() ``` ### 3.3 键盘 ```lua keyPress("ENTER") -- A~Z 0~9 F1~F12 ENTER SPACE TAB ESC LEFT UP RIGHT DOWN … hotkey("Ctrl+Shift+Esc") typeText("中文文本") -- Unicode 注入,不受输入法影响 keyDown("SHIFT") keyUp("SHIFT") ``` ### 3.4 视觉 ```lua local x, y = findImage("images/btn.png", 0.85) -- 找不到 x = -1 local x, y = findColor("FF0000", 10) local x, y = waitImage("images/btn.png", 10000) -- 轮询等待 if exists("images/btn.png") then … end screenshot("shot.png") screen.pick("images/btn.png") -- 运行时框选截图存模板 screen.color(x, y) -- "RRGGBB" ``` ### 3.5 系统 / 剪贴板 / 浏览器 ```lua run("notepad.exe") activate("记事本") win.list() clipSet("内容") local s = clipGet() browser.open("https://…") browser.click("#btn") browser.type("#input", "内容") browser.wait(".loaded", 10000) local t = browser.text(".result") browser.js("document.title") browser.back() browser.forward() browser.reload() browser.shot("web.png") ``` --- ## 4. 关键实现说明 ### 4.1 积木 → Lua `BlockCatalog` 中每个积木含 `Title`(显示模板)与 `Template`(Lua 模板)。 渲染与生成共用同一套 `{0} {1}` 占位,参数按 `ArgKind` 决定是否加引号: | ArgKind | 用途 | 生成 | |---|---|---| | Number | 坐标、毫秒、相似度 | 原样 | | Text | 文件路径、文本 | `"…"` 转义 | | Key | 键名、鼠标键 | `"…"` | | Expr | 条件表达式 | 原样 | | Raw | 变量名、注释 | 原样 | 容器块(LoopN / LoopWhile / If / IfExists)在生成器里递归,缩进 4 空格。 ### 4.2 中断模型 每条 Lua API 回调首先调用 `LuaEngine.Tick()`: - `CancellationToken` 已取消 → 抛 `AbortSignal`(MoonSharp 会原样透传,不被 `pcall` 吞掉) - 暂停 → 阻塞在 `ManualResetEventSlim` - 急停 → 每 100ms 节流检测鼠标是否在 (0,0),停留 120ms 即中止 `delay` 按 20ms 分片,期间同样响应暂停/停止。 ### 4.3 录制聚合 原始事件(带时间戳)→ 单遍扫描: 1. 连续 `Move` → 只保留落点,生成一次 `moveTo` 2. `Down` + 500ms 内同名 `Up` → 合并成一次点击 3. 修饰键状态栈 + 主键 → `hotkey("Ctrl+X")` 4. 可打印字符进缓冲区,遇到非字符事件 flush 成 `typeText` 5. 事件间隔 ≥30ms → `delay(间隔)`,超过上限则截断 ### 4.4 AVI 封装 ``` RIFF/AVI ├ LIST hdrl → avih(56) + LIST strl(strh 56 + strf 40) ├ LIST movi → 00dc 帧 chunk(JPEG 数据,奇数长度补 0) └ idx1 → 每帧 16 字节索引 ``` 写完回填 RIFF/movi 长度、总帧数、最大帧字节数。MJPEG 无需关键帧,所有帧都是关键帧(flag `0x10`)。 --- ## 5. 热键 | 键 | 功能 | |---|---| | F8 | 启动任务队列 | | F9 | 暂停 / 继续 | | F10 | 停止 | | F11 | 框选截图(直接存 `images/`,可当模板图) | | F12 | 开始 / 停止操作录制 | | 鼠标到 (0,0) | 紧急停止 | --- ## 6. 已验证项 | 项 | 结果 | |---|---| | `dotnet build` | 0 错误(仅 WebView2 自带的 WindowsBase 版本警告) | | 积木 → Lua 生成 | 循环/条件/否则/找图/键盘/拖拽/注释全部正确 | | Lua 执行 | 找图多返回值、`for`、`table`、剪贴板、`stop()` 正常 | | 语法错误提示 | 中文提示并带位置 | | 屏幕录像 | 1.5s @10fps → 16 帧,RIFF/idx1 结构正确 | | 主窗体构造 | 四区布局全部正常渲染(1360×860) | | 脚本⇄积木 | 演示脚本 581 字符 → 18 个积木(注释/循环/ifExists/赋值全命中);改积木参数 delay(1234) 后脚本自动回写;往返一致 | | 新任务 | 模板脚本 4 行 → 4 个积木自动转换 | | 任务联动 | 改任务名即时同步左侧;切换任务脚本/积木各自恢复(积木 JSON 1345 字节往返正常) | | 任务增删 | 新建→选中→载入模板脚本;删除后自动选中相邻任务 | | 插件面板 | 17 个插件项,内容超高时垂直滚动条正常 | --- ## 7. 已知限制 | 限制 | 说明 | |---|---| | 管理员权限 | manifest 为 `requireAdministrator`,需以管理员身份运行才能控制系统级窗口 | | 纯计算死循环 | 不调用任何 API 的 `while true do end` 无法被 F10 中断(无指令级钩子) | | 中文输入法录制 | 低级钩子只能拿虚拟键码,输入法上屏的中文需改用 `typeText` | | DirectX 独占全屏 | 抓图/后台消息无效,属系统限制 | | 游戏内核反作弊 | 应用层模拟无法驱动,明确不支持 | --- ## 8. 后续可扩展 - OCR 文字识别(接 PaddleOCR / Windows.Media.Ocr) - 录制时自动补找图锚点(点击位置周边取 32×32 模板) - 积木 → 脚本 双向(Lua 子集解析回积木) - 脚本加密打包(导出 exe / 加密 lua) - 多坐标点绑定窗口(窗口相对坐标)