目录
[1. 新增工具类 CoordinateExtractUtil](#1. 新增工具类 CoordinateExtractUtil)
[1.1 核心方法说明](#1.1 核心方法说明)
[2. DesktopRobotUtil 修改](#2. DesktopRobotUtil 修改)
[2.1 功能概述](#2.1 功能概述)
[2.2 核心方法解析](#2.2 核心方法解析)
[3. OperationController 接口](#3. OperationController 接口)
一、引言
在前文 基于GUI-PLUS 的桌面GUI交互全流程总结-CSDN博客 中一个最最简单的,能把桌面上各种软件位置定位的办法被我搞出来了,接下来,我会用Java 的 Robot 方法来实现将这个定位好的坐标用到实际操作上。
二、代码实现
1. 新增工具类 CoordinateExtractUtil
java
package gzj.spring.ai.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* 从JSON坐标字符串中提取x/y值的工具方法
* @author DELL
*/
public class CoordinateExtractUtil {
// 复用单例ObjectMapper
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
/**
* 提取JSON字符串中的x和y坐标
* @param jsonStr 格式如 {"x": 1860, "y": 237} 的字符串
* @return CoordinateMappingUtil.Point 对象(包含x/y值)
* @throws Exception 解析失败时抛出异常
*/
public static CoordinateMappingUtil.Point extractXY(String jsonStr) throws Exception {
// 1. 校验入参
if (jsonStr == null || jsonStr.trim().isEmpty()) {
throw new IllegalArgumentException("JSON坐标字符串不能为空");
}
// 2. 解析JSON字符串为JsonNode
JsonNode jsonNode = OBJECT_MAPPER.readTree(jsonStr);
// 3. 提取x/y值(path()方法容错:字段不存在时返回0,避免空指针)
int x = jsonNode.path("x").asInt();
int y = jsonNode.path("y").asInt();
// 4. 校验提取结果(避免x/y均为0的无效情况)
if (x == 0 && y == 0) {
throw new RuntimeException("未从JSON字符串中提取到有效坐标,JSON:" + jsonStr);
}
// 5. 返回Point对象
return new CoordinateMappingUtil.Point(x, y);
}
// 测试示例
public static void main(String[] args) {
try {
// 你的目标JSON字符串
String jsonStr = "{\"x\": 1860, \"y\": 237}";
// 提取坐标
CoordinateMappingUtil.Point point = extractXY(jsonStr);
// 输出结果
System.out.println("提取的x值:" + point.getX()); // 输出 1860
System.out.println("提取的y值:" + point.getY()); // 输出 237
} catch (Exception e) {
e.printStackTrace();
}
}
}
该代码是一个Java工具类,专门用于从JSON格式的坐标字符串中提取x/y数值并封装为Point对象。
1.1 核心方法说明
extractXY(String jsonStr)
输入参数为JSON格式字符串(如{"x":1860,"y":237}),输出为包含x/y坐标的Point对象。方法执行流程如下:
- 参数非空校验:若输入为空字符串或null,抛出
IllegalArgumentException - JSON解析:使用Jackson库的
ObjectMapper将字符串转为JsonNode对象 - 坐标提取:通过
path()方法安全获取x/y字段值(字段不存在时默认返回0) - 有效性校验:当x/y同时为0时抛出运行时异常(可能JSON格式错误或字段缺失)
- 结果封装:返回新建的
Point对象
2. DesktopRobotUtil 修改
java
package gzj.spring.ai.util;
import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
/**
* 桌面操作工具类(基于java.awt.Robot)
* 支持鼠标移动、点击、滚轮滑动、键盘输入,兼容屏幕缩放后的精准坐标
*/
public class DesktopRobotUtil {
private final Robot robot;
// 屏幕分辨率(初始化时自动获取)
private final int screenWidth;
private final int screenHeight;
// 系统显示缩放比例(如Windows 125%=1.25,需手动传入或自动识别)
private final double scaleRatio;
/**
* 构造器(初始化Robot+屏幕参数)
* @param scaleRatio 系统显示缩放比例(如1.25=125%)
* @throws AWTException Robot初始化失败(如无AWT环境)
*/
public DesktopRobotUtil(double scaleRatio) throws AWTException {
this.robot = new Robot();
// 获取系统屏幕的物理分辨率
this.screenWidth = Toolkit.getDefaultToolkit().getScreenSize().width;
this.screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
this.scaleRatio = scaleRatio;
// 设置自动等待(操作间的默认间隔)
robot.setAutoWaitForIdle(true);
}
// ====================== 鼠标基础操作 ======================
/**
* 移动鼠标到指定坐标(已校准的原始屏幕物理像素)
* @param x 校准后的X坐标
* @param y 校准后的Y坐标
*/
public void moveMouse(int x, int y) {
// 若系统有缩放,转换为Robot识别的逻辑像素(可选,根据实际场景)
int logicX = (int) Math.round(x / scaleRatio);
int logicY = (int) Math.round(y / scaleRatio);
// 校验坐标在屏幕范围内
logicX = Math.max(0, Math.min(screenWidth - 1, logicX));
logicY = Math.max(0, Math.min(screenHeight - 1, logicY));
// 移动鼠标
robot.mouseMove(logicX, logicY);
// 短暂延迟,确保鼠标移动到位
robot.delay(300);
}
/**
* 鼠标左键单击(先移动到坐标,再点击)
* @param x 校准后的X坐标
* @param y 校准后的Y坐标
*/
public void leftClick(int x, int y) {
moveMouse(x, y);
// 按下左键
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
// 释放左键
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
robot.delay(200);
}
/**
* 鼠标右键单击
* @param x 校准后的X坐标
* @param y 校准后的Y坐标
*/
public void rightClick(int x, int y) {
moveMouse(x, y);
robot.mousePress(InputEvent.BUTTON3_DOWN_MASK);
robot.mouseRelease(InputEvent.BUTTON3_DOWN_MASK);
robot.delay(200);
}
/**
* 鼠标左键双击
* @param x 校准后的X坐标
* @param y 校准后的Y坐标
*/
public void doubleClick(int x, int y) {
// 1. 先移动到目标坐标,确保位置准确
moveMouse(x, y);
// 2. 第一次单击(增加按压延迟,模拟人类点击力度)
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
robot.delay(120); // 按压后停留200ms,避免"轻触"
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
robot.delay(200); // 双击间隔设为300ms(匹配Windows默认双击阈值)
// 3. 第二次单击(同样增加按压延迟)
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
robot.delay(120);
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
robot.delay(200); // 双击后等待1秒,给软件启动的时间
}
// ====================== 滚轮滑动操作 ======================
/**
* 鼠标滚轮滑动(上下)
* @param direction 方向:正数=向上,负数=向下
* @param amount 滑动幅度(推荐1-10)
*/
public void scrollWheel(int direction, int amount) {
// Robot的scroll方法:正数向上,负数向下,amount为滑动格数
robot.mouseWheel(direction * amount);
robot.delay(500);
}
// ====================== 键盘操作 ======================
/**
* 输入文本(支持普通字符)
* @param text 要输入的文本
*/
public void typeText(String text) {
for (char c : text.toCharArray()) {
// 转换字符为KeyCode(基础字符)
int keyCode = KeyEvent.getExtendedKeyCodeForChar(c);
if (keyCode != KeyEvent.VK_UNDEFINED) {
robot.keyPress(keyCode);
robot.keyRelease(keyCode);
robot.delay(50);
}
}
}
/**
* 按下组合键(如Ctrl+C、Alt+F4)
* @param keyCodes 键码数组(如new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_C})
*/
public void pressCombinationKey(int[] keyCodes) {
// 按下所有键
for (int keyCode : keyCodes) {
robot.keyPress(keyCode);
robot.delay(50);
}
// 释放所有键
for (int keyCode : keyCodes) {
robot.keyRelease(keyCode);
robot.delay(50);
}
}
// ====================== 辅助方法 ======================
/**
* 自动识别Windows系统的显示缩放比例(可选,需JNA依赖)
* 若无JNA,建议手动传入(如1.25=125%)
*/
public static double getWindowsScaleRatio() {
try {
// 需引入JNA依赖(可选)
// import com.sun.jna.platform.win32.User32;
// import com.sun.jna.platform.win32.WinDef;
// WinDef.HDC hdc = User32.INSTANCE.GetDC(null);
// int dpi = User32.INSTANCE.GetDeviceCaps(hdc, 88); // 水平DPI
// User32.INSTANCE.ReleaseDC(null, hdc);
// return dpi / 96.0; // Windows默认DPI=96,120DPI=125%
return 1.0; // 无JNA时默认1.0
} catch (Exception e) {
return 1.0;
}
}
// ====================== 测试示例 ======================
// public static void main(String[] args) {
// try {
// // 初始化工具类(Windows 125%缩放=1.25)
// DesktopRobotUtil robotUtil = new DesktopRobotUtil(1.25);
//
// // 1. 精准点击(结合前文校准后的坐标)
// int targetX = 1753; // 校准后的X
// int targetY = 278; // 校准后的Y
// robotUtil.leftClick(targetX, targetY);
// System.out.println("已点击坐标:(" + targetX + "," + targetY + ")");
//
// // 2. 滚轮向下滑动
// robotUtil.scrollWheel(-1, 5); // -1=向下,5=幅度
// System.out.println("已向下滑动滚轮5格");
//
// // 3. 输入文本
// robotUtil.typeText("Hello World!");
// System.out.println("已输入文本:Hello World!");
//
// // 4. 按下Ctrl+C
// robotUtil.pressCombinationKey(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_C});
// System.out.println("已按下Ctrl+C");
//
// } catch (AWTException e) {
// e.printStackTrace();
// }
// }
}
2.1 功能概述
DesktopRobotUtil 是一个基于 Java AWT Robot 类的工具类,用于模拟鼠标和键盘操作。支持以下功能:
- 鼠标移动、点击(左键、右键、双击)
- 滚轮滑动
- 键盘输入(单字符、组合键)
- 自动校准屏幕缩放比例
2.2 核心方法解析
鼠标操作
moveMouse(int x, int y)
将鼠标移动到指定物理像素坐标(自动根据缩放比例转换为逻辑像素)。
- 参数
x/y:校准后的屏幕坐标(物理像素)。 - 内部逻辑:通过
scaleRatio转换坐标,并限制在屏幕范围内。
leftClick(int x, int y)
移动到目标坐标后执行左键单击。
- 调用
moveMouse确保位置准确,再触发mousePress和mouseRelease。
doubleClick(int x, int y)
模拟人类双击行为,包含两次单击和合理的延迟。
- 每次单击后增加 120ms 按压延迟,双击间隔 200ms。
键盘操作
typeText(String text)
逐字符输入文本,支持普通字符(跳过未定义键码的字符)。
- 使用
KeyEvent.getExtendedKeyCodeForChar转换字符为键码。
pressCombinationKey(int[] keyCodes)
按下并释放组合键(如 Ctrl+C)。
- 参数
keyCodes:按顺序传入组合键的键码(如{KeyEvent.VK_CONTROL, KeyEvent.VK_C})。
滚轮操作
scrollWheel(int direction, int amount)
控制滚轮滑动方向和幅度。
direction:正数为向上,负数为向下。amount:滑动幅度(推荐 1-10)。
注意事项
- 权限问题 :某些操作系统可能需要特殊权限才能使用
Robot。 - 坐标范围:自动校验坐标是否在屏幕物理分辨率范围内。
3. OperationController 接口
java
@PostMapping("/operation/interact")
public String interact(@RequestBody OparetionRequest request) throws NoApiKeyException, UploadFileException, IOException, AWTException {
log.info("接收交互请求:{}", request);
String result = oparetionService.operation(request);
CoordinateMappingUtil.Point point;
try {
DesktopRobotUtil robotUtil = new DesktopRobotUtil(1.0);
point = extractXY(result);
robotUtil.doubleClick(point.getX(), point.getY());
log.info("已点击坐标:{}", point);
} catch (Exception e) {
log.error("交互异常", e);
return "交互异常:" + e.getMessage();
}
return String.valueOf(point);
}
三、结果演示


如果觉得这份修改实用、总结清晰,别忘了动动小手点个赞👍,再关注一下呀~ 后续还会分享更多 AI 接口封装、代码优化的干货技巧,一起解锁更多好用的功能,少踩坑多提效!🥰 你的支持就是我更新的最大动力,咱们下次分享再见呀~🌟