目录
一、引言
二、代码实现
1. 新增工具类 CoordinateExtractUtil
1.1 核心方法说明
2. DesktopRobotUtil 修改
2.1 功能概述
2.2 核心方法解析
鼠标操作
键盘操作
滚轮操作
注意事项
3. OperationController 接口
三、结果演示
一、引言
在前文 基于GUI-PLUS 的桌面GUI交互全流程总结-CSDN博客 中一个最最简单的,能把桌面上各种软件位置定位的办法被我搞出来了,接下来,我会用Java 的 Robot 方法来实现将这个定位好的坐标用到实际操作上。
二、代码实现
1. 新增工具类 CoordinateExtractUtil
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 修改
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 AWTRobot类的工具类,用于模拟鼠标和键盘操作。支持以下功能:
- 鼠标移动、点击(左键、右键、双击)
- 滚轮滑动
- 键盘输入(单字符、组合键)
- 自动校准屏幕缩放比例
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 接口
@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 接口封装、代码优化的干货技巧,一起解锁更多好用的功能,少踩坑多提效!🥰 你的支持就是我更新的最大动力,咱们下次分享再见呀~🌟