﻿# VMware 自动化 API 开发文档

## 1. 项目目标

本服务用于在宿主机上通过 HTTP API 控制 VMware Workstation，方便上层控制程序统一调用。

当前适用场景：

- 批量复制虚拟机
- 按名称控制开机和关机
- 删除虚拟机
- 获取指定虚拟机屏幕内容
- 按客户区坐标点击
- 通过 OCR 查找屏幕上的“文字节点”
- 向虚拟机发送键盘输入
- 查找到文字后直接点击
- 用可视化执行器批量跑“复制 -> 开机 -> 打开夸克 -> 登录/发送验证码 -> 领取 -> 删除”
- 直接对接 `uc_exchange_ip/device_operator_api_examples.py`
- 自动拉取设备任务、回传资格结果、回传领取结果、释放任务
- 槽位空闲时自动继续拉单，尽量减少空闲

### 新架构链路

当前推荐链路已经调整为：

- `Lua 手机脚本`
- `Win 中转 API`
- `虚拟机内 Guest Agent`

其中：

- 手机 Lua 只负责调宿主机 API
- 宿主机 API 负责找虚拟机、取客机 IP、做 OCR、转发命令
- 真正的截图、点击、输入、按键改为在虚拟机内部执行

这样就不再依赖宿主机前台 VMware 窗口，更适合静默运行。

## 2. 目录结构

- 宿主机安装入口: `..\一键部署_新电脑Server.cmd`
- API 启动入口: `..\一键打开_API.cmd`
- 自动化 GUI: `..\一键打开_PY自动化.cmd`
- 自动化 GUI 六开: `..\一键打开_PY自动化_六开.cmd`
- 截图调试助手: `..\一键打开_截图调试助手.cmd`
- 宿主机脚本目录: `..\scripts`
- 宿主机程序目录: `..\app`
- 源码参考目录: `..\..\03_PY源码参考\vmware_api`
- Guest Agent 交付目录: `..\vm_guest_agent_package`
- Guest Agent 独立目录: `..\..\01_虚拟机内Agent服务`

## 3. 启动方式

### 3.1 启动 API

```bat
cd /d C:\QuarkVmwareClient
一键打开_API.cmd
```

默认监听地址：

```text
http://127.0.0.1:18765
```

在线 Swagger 文档：

- [http://127.0.0.1:18765/docs](http://127.0.0.1:18765/docs)

OpenAPI JSON：

- [http://127.0.0.1:18765/openapi.json](http://127.0.0.1:18765/openapi.json)

### 3.2 启动默认自动拉单执行器

先启动 API，再启动默认执行器：

```bat
cd /d C:\QuarkVmwareClient
一键打开_PY自动化.cmd
```

默认执行器会直接对接 `uc_exchange_ip` 的设备端 API。

### 3.3 启动旧版手工执行器

如果你想手工填 `手机号,短信` 跑单，再用这个：

```bat
cd /d C:\QuarkVmwareClient
一键打开_截图调试助手.cmd
```

## 4. 默认配置

- VMware 根目录: 安装时输入或自动探测，不再固定要求同一台电脑必须是同一个路径
- VMware 命令行工具: `C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe`
- VMware GUI: `C:\Program Files (x86)\VMware\VMware Workstation\vmware.exe`
- 默认主机: `0.0.0.0`
- 默认端口: `18765`
- 默认执行器源虚拟机: `Quark-Win10-Base`
- 默认远程站点: `https://rt.viphc.cn`
- 默认远程 API 示例文件: `C:\Users\daifei\Desktop\夸克项目\uc_exchange_ip\device_operator_api_examples.py`
- 热更新清单默认地址: `http://47.109.27.133:8010/manifest.json`
- 本机宿主机配置文件: `C:\QuarkVmwareClient\config\vmware_api_host_config.json`

可通过环境变量覆盖：

- `VMWARE_API_VMROOT`
- `VMWARE_API_DISCOVERY_ROOTS`
- `VMWARE_RUN_PATH`
- `VMWARE_GUI_PATH`
- `VMWARE_API_HOST`
- `VMWARE_API_PORT`

## 5. 坐标体系

所有和屏幕相关的坐标都使用同一套坐标系：

- 原点：虚拟机客户区左上角
- `x`：从左往右递增
- `y`：从上往下递增

以下接口共享这套坐标系：

- `POST /vm/screenshot`
- `POST /vm/click`
- `POST /vm/find-node`
- `POST /vm/find-and-click`

## 6. 节点查找说明

### 6.1 当前“节点”定义

当前接口里的“节点”不是客机内部真实的控件树节点，不是 Win32 UIA、不是浏览器 DOM，也不是 Appium 元素。

当前实现是：

- 先截取指定虚拟机当前屏幕
- 再用 OCR 识别出屏幕上的文字块
- 把每个文字块视为一个“文字节点”
- 根据匹配规则查找目标文字
- 返回是否存在、边界框和中心坐标

### 6.2 适用情况

适合这些场景：

- 找“登录”
- 找“下一步”
- 找“确定”
- 找“领取奖励”
- 找业务页面里的按钮标题、菜单名、标签文本

### 6.3 不适用情况

不适合这些场景：

- 读取客机内部真实控件 ID
- 读取浏览器 DOM 节点
- 获取不可见控件
- 获取没有文字的纯图标控件

如果后面你需要“真正的控件树”，那就不是宿主机 OCR 路线了，需要在客机内部再部署代理程序或 UIA 服务。

## 7. 并发与前台窗口说明

### 7.1 可以多开

API 和任务执行器都支持多任务并发。

例如：

- 同时复制多台虚拟机
- 同时启动多台虚拟机
- 多个任务并发排队执行

### 7.2 为什么前台 UI 动作要串行

这些动作依赖 VMware 图形窗口成为前台窗口：

- 截图
- OCR 查找
- 鼠标点击
- 文本输入
- 按键发送

如果多台虚拟机同时抢前台，容易出现：

- 点错窗口
- 输入串到别的虚拟机
- 截图和当前任务不匹配

所以服务内部已经对这类动作加了全局 UI 锁，自动串行执行。

这意味着：

- 多任务可以同时跑
- 但真正依赖前台窗口的步骤会排队依次执行
- 这样更稳，不容易把流程点乱

## 8. 无密码假设

当前任务执行器默认按以下前提设计：

- 虚拟机开机后可直接使用
- 不需要输入系统登录密码
- 不包含任何密码填写步骤

如果后面你的模板虚拟机改成需要密码，那就要在工作流里单独补“等待登录页 + 输入密码”的步骤。

### 基础机 Guest Agent 安装

当前宿主机已经清理为只保留基础机：

- `Quark-Win10-Base`

接下来要在基础机里安装 Guest Agent：

1. 启动 `Quark-Win10-Base`
2. 把 `..\vm_guest_agent_package` 或 `..\..\01_虚拟机内Agent服务` 整个目录复制进虚拟机
3. 在虚拟机里管理员运行 `install_guest_agent.ps1` 或 `install_guest_agent.cmd`
4. 重启基础机
5. 回到宿主机调用 `POST /vm/status`，确认 `guest_agent_ok = true`

## 9. 可靠性链路

为了解决“点击复制没反应”“开机后断开”“步骤没衔接上”的问题，当前默认自动拉单执行器已经改成逐步确认：

- 复制阶段：
  - 先调用 `POST /vm/clone`
  - 再循环读取 `GET /vms`
  - 直到新虚拟机真实出现在列表里，才进入下一步
- 开机阶段：
  - 先调用 `POST /vm/power-on`
  - 再循环检查虚拟机 `running=true`
  - 然后继续截图，连续成功后才认定屏幕就绪
- 打开夸克阶段：
  - 点击桌面图标或开始菜单启动
  - 等待夸克相关界面信号出现
  - 没识别到会自动重试，而不是盲等
- `submit_exchange` 登录阶段：
  - 不再只刷新一次远程任务
  - 会循环 `device_task_status`
  - 直到拿到验证码或明确超时/远程状态变化
- 登录提交后：
  - 不是固定 `sleep`
  - 而是等待“领取奖励/已领取/明日再来”等登录后页面信号
  - 只有确认进入下一页面后，才回传“登录成功”
- 结果识别阶段：
  - 支持多次连续确认
  - 避免 OCR 一闪而过导致误判

这套逻辑的目标是：每个步骤必须严丝合缝，前一个步骤没有确认完成，就不会跳到下一步。

## 10. 接口总览

### 10.1 系统类

- `GET /health`
- `GET /config`

### 10.2 虚拟机类

- `GET /vms`
- `GET /vms/count`
- `POST /vm/status`
- `POST /vm/clone`
- `POST /vm/clone-batch`
- `POST /vm/power-on`
- `POST /vm/power-off`
- `POST /vm/delete`

### 10.3 屏幕自动化类

- `POST /vm/screenshot`
- `POST /vm/click`
- `POST /vm/find-node`
- `POST /vm/type-text`
- `POST /vm/send-keys`
- `POST /vm/find-and-click`

## 11. 接口详情

### 11.1 健康检查

```http
GET /health
```

用途：

- 检查服务是否在线
- 查看当前使用的 VMware 路径和默认配置

### 11.2 获取服务配置

```http
GET /config
```

返回：

- VMware 根目录
- `vmrun.exe` 路径
- `vmware.exe` 路径
- 默认监听地址
- OCR 是否可用
- Guest Agent 是否启用
- Guest Agent 监听端口
- Guest Agent 超时配置

### 11.3 获取虚拟机列表

```http
GET /vms
```

返回每台虚拟机：

- 名称
- 显示名称
- `.vmx` 路径
- 是否运行中
- BIOS UUID
- 网卡 MAC

### 11.4 获取虚拟机数量

```http
GET /vms/count
```

返回：

- `count`
- `running_count`

### 11.5 查询单台虚拟机状态

```http
POST /vm/status
Content-Type: application/json
```

请求示例：

```json
{
  "vm_ref": "Quark-Win10-Base"
}
```

返回重点字段：

- `exists`：虚拟机是否存在
- `registered`：是否已注册到当前 VMware 列表
- `running`：是否已开机运行
- `power_state`：`running` / `stopped` / `not_found`
- `display_name`
- `vmx_path`
- `guest_ip`：客机当前 IP
- `guest_agent_ok`：虚拟机内部 Agent 是否已就绪
- `control_mode`：当前控制模式，默认是 `guest-agent`

### 11.6 复制单台虚拟机

```http
POST /vm/clone
Content-Type: application/json
```

请求示例：

```json
{
  "source_vm_ref": "Quark-Win10-Base",
  "new_name": "Quark-04",
  "clone_type": "full"
}
```

### 11.7 批量复制虚拟机

```http
POST /vm/clone-batch
Content-Type: application/json
```

请求示例：

```json
{
  "source_vm_ref": "Quark-Win10-Base",
  "name_prefix": "Quark",
  "count": 3,
  "start_index": 4,
  "digits": 2,
  "clone_type": "full"
}
```

### 11.8 开机

```http
POST /vm/power-on
Content-Type: application/json
```

请求示例：

```json
{
  "vm_ref": "Quark-01",
  "gui": true
}
```

### 11.9 关机

```http
POST /vm/power-off
Content-Type: application/json
```

请求示例：

```json
{
  "vm_ref": "Quark-01",
  "soft": false
}
```

### 11.10 删除虚拟机

```http
POST /vm/delete
Content-Type: application/json
```

请求示例：

```json
{
  "vm_ref": "Quark-01",
  "force": true,
  "delete_disk": true
}
```

### 11.11 获取屏幕截图

```http
POST /vm/screenshot
Content-Type: application/json
```

说明：

- 当前版本默认优先走虚拟机内部 Guest Agent 截图
- 不是再去截宿主机 VMware 窗口画面
- 这更适合后台静默运行

请求示例：

```json
{
  "vm_ref": "Quark-01",
  "with_ocr": true
}
```

返回重点字段：

- `width`
- `height`
- `image_base64`
- `ocr_items`

### 11.12 按坐标点击

```http
POST /vm/click
Content-Type: application/json
```

说明：

- 当前版本默认优先走虚拟机内部 Guest Agent 点击
- 不再要求 VMware 窗口在宿主机前台

请求示例：

```json
{
  "vm_ref": "Quark-01",
  "x": 640,
  "y": 360,
  "button": "left",
  "double_click": false
}
```

### 11.13 查找文字节点

```http
POST /vm/find-node
Content-Type: application/json
```

请求示例：

```json
{
  "vm_ref": "Quark-01",
  "text": "登录",
  "match_mode": "contains",
  "ignore_case": true,
  "min_score": 0.5,
  "index": 0,
  "return_image": false
}
```

返回重点字段：

- `exists`
- `match_count`
- `matches`
- `node`
- `center_x`
- `center_y`

### 11.14 输入文本

```http
POST /vm/type-text
Content-Type: application/json
```

说明：

- 当前版本默认优先走虚拟机内部 Guest Agent 输入

请求示例：

```json
{
  "vm_ref": "Quark-01",
  "text": "13800138000",
  "interval_ms": 20,
  "press_enter": false
}
```

说明：

- 会先激活虚拟机窗口
- 会自动切到客户机输入模式
- 文本会发到当前焦点控件

### 11.15 发送按键

```http
POST /vm/send-keys
Content-Type: application/json
```

说明：

- 当前版本默认优先走虚拟机内部 Guest Agent 按键

请求示例一：连续按 3 次 Tab

```json
{
  "vm_ref": "Quark-01",
  "keys": ["tab"],
  "mode": "press",
  "repeat": 3,
  "delay_ms": 120
}
```

请求示例二：发送组合键

```json
{
  "vm_ref": "Quark-01",
  "keys": ["ctrl", "l"],
  "mode": "hotkey",
  "repeat": 1,
  "delay_ms": 80
}
```

### 11.16 查找后点击

```http
POST /vm/find-and-click
Content-Type: application/json
```

请求示例：

```json
{
  "vm_ref": "Quark-01",
  "text": "领取奖励",
  "match_mode": "contains",
  "ignore_case": true,
  "min_score": 0.5,
  "index": 0,
  "button": "left",
  "double_click": false
}
```

适合上层少调一次接口的场景：

- 找到“登录”后直接点
- 找到“领取奖励”后直接点
- 找到桌面“夸克”图标后直接双击

## 12. 推荐控制流程

如果上层程序要做“找节点再点击”，建议按这个顺序：

1. 调 `POST /vm/find-node`
2. 判断 `exists`
3. 读取 `center_x`、`center_y`
4. 调 `POST /vm/click`

如果你想少调一次接口，可以直接调：

1. `POST /vm/find-and-click`

## 13. Python 对接示例

```python
import requests

base = "http://127.0.0.1:18765"

print(requests.get(f"{base}/health").json())
print(requests.get(f"{base}/vms").json())

requests.post(
    f"{base}/vm/power-on",
    json={"vm_ref": "Quark-01", "gui": True}
).json()

requests.post(
    f"{base}/vm/type-text",
    json={"vm_ref": "Quark-01", "text": "13800138000"}
).json()

requests.post(
    f"{base}/vm/send-keys",
    json={"vm_ref": "Quark-01", "keys": ["tab"], "mode": "press", "repeat": 1}
).json()

requests.post(
    f"{base}/vm/find-and-click",
    json={"vm_ref": "Quark-01", "text": "领取奖励", "double_click": False}
).json()
```

## 14. 执行器说明

### 14.1 默认自动拉单执行器

默认执行器文件：

- `..\..\03_PY源码参考\vmware_api\device_task_runner.py`

默认配置文件：

- `..\..\03_PY源码参考\vmware_api\device_runner_config.json`

默认启动脚本：

- 安装后入口：`..\一键打开_PY自动化.cmd`

这个执行器直接复用了：

- `C:\Users\daifei\Desktop\夸克项目\uc_exchange_ip\device_operator_api_examples.py`

主要能力：

- 自动按槽位发送 `device_heartbeat`
- 槽位空闲时自动调用 `device_pull_task`
- 自动处理 `check_qualification`
- 自动处理 `submit_exchange`
- 自动调用 `device_submit_qualification`
- 自动调用 `device_submit_result`
- 停止或异常时尽量 `device_release_task`
- 本地虚拟机清理完就继续拉下一单，尽量避免空闲

### 14.2 自动拉单执行流程

当远程接口拉到 `check_qualification` 时：

1. 复制源虚拟机
2. 轮询确认新虚拟机已经注册到 `/vms`
3. 开机
4. 轮询确认 `running=true`
5. 连续截图确认屏幕已就绪
6. 打开夸克并等待界面信号
7. 进入手机号登录页
8. 输入手机号
9. 触发发送验证码
10. OCR 判断是否进入验证码输入阶段
11. 调 `device_submit_qualification`
12. 删除虚拟机
13. 槽位继续拉下一单

当远程接口拉到 `submit_exchange` 时：

1. 复制源虚拟机
2. 轮询确认新虚拟机已经注册到 `/vms`
3. 开机
4. 轮询确认 `running=true`
5. 连续截图确认屏幕已就绪
6. 打开夸克并等待界面信号
7. 等待远程接口下发验证码
8. 进入手机号登录页
9. 输入手机号和验证码
10. 登录并等待登录后页面信号
11. 可选先回传一次 `登录成功`
12. 点击领取奖励
13. OCR 连续确认最终结果
14. 调 `device_submit_result`
15. 删除虚拟机
16. 槽位继续拉下一单

### 14.3 自动拉单配置

自动拉单主配置在：

- `..\..\03_PY源码参考\vmware_api\device_runner_config.json`

最常调的通常是这些字段：

- `ui.concurrency`
- `ui.boot_wait_seconds`
- `ui.idle_poll_seconds`
- `ui.heartbeat_interval_seconds`
- `remote_api.base_url`
- `remote_api.examples_path`
- `remote_api.device_no_prefix`
- `remote_api.device_name_prefix`
- `remote_api.capabilities`

夸克页面相关的高级流程参数在：

- `workflow.clone_register_timeout_seconds`
- `workflow.clone_register_poll_seconds`
- `workflow.power_on_timeout_seconds`
- `workflow.power_on_poll_seconds`
- `workflow.screen_ready_timeout_seconds`
- `workflow.screen_ready_poll_seconds`
- `workflow.screen_ready_confirmations`
- `workflow.app_launch_attempts`
- `workflow.app_ready_timeout_seconds`
- `workflow.launch_icon_texts`
- `workflow.popup_watch_texts`
- `workflow.popup_click_texts`
- `workflow.login_entry_texts`
- `workflow.remote_sms_wait_seconds`
- `workflow.remote_sms_poll_seconds`
- `workflow.login_ready_timeout_seconds`
- `workflow.login_ready_confirmations`
- `workflow.login_success_texts`
- `workflow.login_failure_texts`
- `workflow.tab_to_phone_input`
- `workflow.tab_to_request_sms_after_phone`
- `workflow.tab_between_phone_sms`
- `workflow.tab_to_submit_after_sms`
- `workflow.request_sms_button_texts`
- `workflow.qualification_success_texts`
- `workflow.qualification_failure_texts`
- `workflow.reward_button_texts`
- `workflow.success_texts`
- `workflow.already_done_texts`
- `workflow.failure_texts`
- `workflow.result_confirmations`

### 14.4 旧版手工执行器

旧版手工执行器仍然保留：

- `..\..\03_PY源码参考\vmware_api\task_runner.py`
- 调试入口：`..\一键打开_截图调试助手.cmd`
- `..\..\03_PY源码参考\vmware_api\workflow_config.json`

它适合这些场景：

- 你手工准备好 `手机号,短信`
- 你不想接远程设备 API
- 你只想本地调试页面流程

## 15. 当前限制

- 不是客机内部真实控件树
- 查找节点依赖 OCR，会受字体、分辨率、遮挡、模糊影响
- 没文字的纯图标控件无法稳定识别
- 执行器里的登录流程目前主要靠“文字查找 + Tab 导航 + 输入文本”
- 如果夸克页面结构变化较大，需要调整对应配置文件
- 自动拉单执行器虽然已经对接远程 API，但我没有在你的线上站点直接创建测试设备去跑真实订单，避免污染正式环境

## 16. 后续建议扩展

后面如果你继续做控制程序，我建议优先扩这些：

- `POST /vm/power-on-batch`
- `POST /vm/power-off-batch`
- `POST /vm/drag`
- `POST /vm/screenshot-region`
- `POST /vm/wait-text`
- 自动拉单执行器的批量健康巡检
- 自动拉单执行器的任务重试策略
- 自动拉单执行器的失败截图归档
- 客机内代理方案，用来做真实控件树自动化
