这一部分是我也看了好久,才决定用Java的Robot + User32来实现。Robot是java.awt下的一个类,该类用于为测试自动化、自运行演示程序和其他需要控制鼠标和键盘的应用程序生成本机系统输入事件,因此可试用该类进行模拟鼠标键盘操作。User32是JNA下的一个类,该类提供对W32 USER32库的访问,也就是说可以试用该类来进行windows的一些操作,而这里我用来选择对话窗口。

其实我这里用到的awt和JNA都是它们其中的一个小功能点而已,还有很多其他的功能可以去学习使用。下面放一下我发送消息的方法。

/*** @Function: SendMessageService.java* @Description: 向指定QQ群发送消息* @param: communityName 为必填的非空参数,用于发送消息。* @param: message 要发送的消息内容。* @param: communityId 为非必填参数,可为空,用于记录日志。* @author: JuFF_白羽* @date: 2018年7月15日 上午2:36:11*/
private void sendQQMessage(String message, Long communityId, String communityName) throws AWTException {WinDef.HWND hwnd = User32.INSTANCE.FindWindow(null, communityName); // 第一个参数是Windows窗体的窗体类,第二个参数是窗体的标题。不熟悉windows编程的需要先找一些Windows窗体数据结构的知识来看看,还有windows消息循环处理,其他的东西不用看太多。if (hwnd == null) {LOGGER.info("找不到[{}]聊天窗口", communityId);} else {Robot robot = new Robot();boolean showWindow = User32.INSTANCE.ShowWindow(hwnd, 9); // SW_RESTOREif (showWindow) {boolean setForegroundWindow = User32.INSTANCE.SetForegroundWindow(hwnd);if (setForegroundWindow) {robot.delay(3000);// 等3秒KeyboardUtil.keyPressString(robot, message);// 输入内容KeyboardUtil.keyPress(robot, KeyEvent.VK_ENTER);// 按下回车LOGGER.info("新消息已发送到[{}]聊天窗口", communityId);} else {LOGGER.info("设置前景窗口失败");}} else {LOGGER.info("显示窗口失败");}}
}

从上面的代码来看,并没有什么复杂,而其实里面Robot和User32的调用过程都各有一个坑,先从调用Robot发现的问题说起好了。在JUnit Test运行时,Robot能正常实例化,然后模拟键盘按键和鼠标点击等操作,但打包成jar后部署到服务器上时,运行到实例化Robot对象时却报了 java.awt.AWTException: headless environment 这个异常。而这个异常我查了百度,发现很多都没说清楚具体原因,于是又去查了谷歌,相关解释见Java SE使用无头模式。我下面稍微说说我的理解,不一定准确。

--Robot

在awt中,java.awt.Tookit类是抽象窗口工具包(AWT)的所有实际实现的抽象超级父类,而该工具包下的子类用于将各种AWT组件绑定到特定的本地工具包而实现的,如果不支持显示设备、键盘或鼠标,则会受到影响从而抛出一个无头异常。换句话我是这么理解的,在我本地电脑上JUnit Test时,由于我本地电脑有正常的显示设备、键盘和鼠标,所有默认设置运行并没有被影响,而像这种云服务器上,一般不会正常提供有显示设备和键鼠的,于是就会抛出这个异常。但awt并不是一定需要有相关外设才能被正常运行,而是和计算机系统是否支持相关属性。

GraphicsEnvironment还提供了方法来检测当前系统是否无头:

public static boolean isHeadless() 测试此环境中是否支持显示、键盘和鼠标。如果此方法返回true,那么依赖于显示器、键盘或鼠标的Toolkit和GraphicsEnvironment将会抛出HeadlessException。

public boolean isHeadlessInstance() 返回此图形环境中是否支持显示、键盘和鼠标。如果返回true,那么依赖于显示器、键盘或鼠标的GraphicsEnvironment将会抛出HeadlessException。

从上两个方法可知,返回false时就能正常调用,返回true时则会在调用时抛出HeadlessException。而我部署在服务器上的程序抛出的是无头环境异常,那么解决方法是什么呢?在原文里有说明:


System Properties Setup

To set up headless mode, set the appropriate system property by using the setProperty()method. This method enables you to set the desired value for the system property that is indicated by the specific key.

System.setProperty("java.awt.headless", "true");

In this code, java.awt.headless is a system property, and true is a value that is assigned to it.

You can also use the following command line if you plan to run the same application in both a headless and a traditional environment:

java -Djava.awt.headless=true;

用System调用setProperty(),而这个方法能够为特定key所指示的系统属性设置期望的value。设置无头模式时,key为java.awt.headless,value为true或false。我这里遇到的是因为处于无头环境,所以要转成用传统环境运行。因此需要禁用掉无头环境,即,添加此静态代码块即可。如果计划在无头和传统环境中运行相同的应用程序,还可以使用下面那个命令。至此,Robot在服务器上遇到的问题算是解决了。

接下来说明一下Robot的具体使用方法。模拟键鼠操作其实就是调用一些Robot提供的固定方法,传入按键对应的值,来实现按键的操作,并且基本都有一个共同点,就是方法基本包括“按下”和“弹起”,当先执行一次“按下”的方法,再执行一次“弹起”的方法,就完成了一个按键的模拟;组合键则为先执行完要“按下”的方法,例如撤销操作为ctrl+z,则调用keyPress(KeyEvent.VK_CONTROL)、keyPress(KeyEvent.VK_Z)两个方法后,再调用keyRelease(KeyEvent.VK_Z)、keyRelease(KeyEvent.VK_CONTROL),最好保证先按下的后弹起。

为了简化操作,写了一个工具类供调用,工具类如下。按键对应的值可查看类java.awt.event.KeyEvent。

package com.gnz48.zzt.util;import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.KeyEvent;/*** @Description: 键盘工具类*               <p>*               模拟键盘操作的工具。* @author JuFF_白羽* @date 2018年7月12日 下午6:08:30*/
public class KeyboardUtil {public static void main(String[] args) throws Exception {Robot robot = new Robot();// 调用系统方法打开记事本Runtime.getRuntime().exec("notepad");robot.delay(2000);// 全屏显示// keyPressWithAlt(robot,KeyEvent.VK_SPACE);// 输入xkeyPress(robot, KeyEvent.VK_X);// 输入回车keyPress(robot, KeyEvent.VK_ENTER);robot.delay(1000);// 输入字符串keyPressString(robot, "哈哈哈哈哈哈哈哈哈嗝");}/*** @Description: Shift组合键* @author JuFF_白羽* @param r* @param key*/public static void keyPressWithShift(Robot r, int key) {// 按下Shiftr.keyPress(KeyEvent.VK_SHIFT);// 按下某个键r.keyPress(key);// 释放某个键r.keyRelease(key);// 释放Shiftr.keyRelease(KeyEvent.VK_SHIFT);// 等待100msr.delay(100);}/*** @Description: Ctrl组合键* @author JuFF_白羽* @param r* @param key*/public static void keyPressWithCtrl(Robot r, int key) {r.keyPress(KeyEvent.VK_CONTROL);r.keyPress(key);r.keyRelease(key);r.keyRelease(KeyEvent.VK_CONTROL);r.delay(100);}/*** @Description: Alt组合键* @author JuFF_白羽* @param r* @param key*/public static void keyPressWithAlt(Robot r, int key) {r.keyPress(KeyEvent.VK_ALT);r.keyPress(key);r.keyRelease(key);r.keyRelease(KeyEvent.VK_ALT);r.delay(100);}/*** @Title: keyPressString* @Description: 将文本输入到文本框中*               <p>*               实现原理是使用剪切板,将字符串参数放入剪切板中,然后模拟粘贴(Ctrl+V)。* @author JuFF_白羽* @param r*            Java的自动化系统输入事件对象* @param str*            要写入文本框的字符串*/public static void keyPressString(Robot r, String str) {// 获取剪切板Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();// 将传入字符串封装下Transferable tText = new StringSelection(str);// 将字符串放入剪切板clip.setContents(tText, null);// 按下Ctrl+V实现粘贴文本keyPressWithCtrl(r, KeyEvent.VK_V);r.delay(100);}/*** @Description: 输入数字* @author JuFF_白羽* @param r* @param number*/public static void keyPressNumber(Robot r, int number) {// 将数字转成字符串String str = Integer.toString(number);// 调用字符串的方法keyPressString(r, str);}/*** @Description: 按一次某个按键* @author JuFF_白羽* @param r* @param key* @return void 返回类型*/public static void keyPress(Robot r, int key) {// 按下键r.keyPress(key);// 释放键r.keyRelease(key);r.delay(100);}/*** @Description: 快速打开QQ消息(这个组合键因人而异)*               <p>*               这里使用的方法是ctrl+alt+z来弹出QQ消息* @author JuFF_白羽* @param r*/public static void keyPressAtlWithCtrlWithZ(Robot r) {r.keyPress(KeyEvent.VK_ALT);r.keyPress(KeyEvent.VK_CONTROL);r.keyPress(KeyEvent.VK_Z);r.keyRelease(KeyEvent.VK_Z);r.keyRelease(KeyEvent.VK_CONTROL);r.keyRelease(KeyEvent.VK_ALT);r.delay(100);}/*** @Description: 点击一下鼠标左键* @author JuFF_白羽* @param r*/public static void mouseLeftHit(Robot r) {r.mousePress(KeyEvent.BUTTON1_DOWN_MASK);r.mouseRelease(KeyEvent.BUTTON1_DOWN_MASK);r.delay(100);}
}

--User32

User32是提供对W32 USER32库的访问的类,具体过程:

  1. 先获取一个User32实例,通过这个实例调用 HWND FindWindow(String lpClassName, String lpWindowName) 检索顶级窗口,通过窗口名字符串匹配,并且不会检索子窗口。lpClassName为窗口类字符串或空;lpWindowName为窗口的标题。返回的HWND为null则说明未找到该窗口。
  2. 获得该窗口后,就可以通过User32调用 boolean ShowWindow(HWND hWnd, int nCmdShow) 设置指定窗口的显示状态,设置成功将返回true,否则返回false。hWnd为上一步获取的窗口句柄;nCmdShow为控制窗口的显示方式,值为WinMain函数在nCmdShow其中获得的值。对应值如下所示:

    SW_FORCEMINIMIZE:在WindowNT5.0中最小化窗口,即使拥有窗口的线程被挂起也会最小化。在从其他线程最小化窗口时才使用这个参数。nCmdShow=11。

    SW_HIDE:隐藏窗口并激活其他窗口。nCmdShow=0。

    SW_MAXIMIZE:最大化指定的窗口。nCmdShow=3。

    SW_MINIMIZE:最小化指定的窗口并且激活在Z序中的下一个顶层窗口。nCmdShow=6。

    SW_RESTORE:激活并显示窗口。如果窗口最小化或最大化,则系统将窗口恢复到原来的尺寸和位置。在恢复最小化窗口时,应用程序应该指定这个标志。nCmdShow=9。

    SW_SHOW:在窗口原来的位置以原来的尺寸激活和显示窗口。nCmdShow=5。

    SW_SHOWDEFAULT:依据在STARTUPINFO结构中指定的SW_FLAG标志设定显示状态,STARTUPINFO 结构是由启动应用程序的程序传递给CreateProcess函数的。nCmdShow=10。

    SW_SHOWMAXIMIZED:激活窗口并将其最大化。nCmdShow=3。

    SW_SHOWMINIMIZED:激活窗口并将其最小化。nCmdShow=2。

    SW_SHOWMINNOACTIVE:窗口最小化,激活窗口仍然维持激活状态。nCmdShow=7。

    SW_SHOWNA:以窗口原来的状态显示窗口。激活窗口仍然维持激活状态。nCmdShow=8。

    SW_SHOWNOACTIVATE:以窗口最近一次的大小和状态显示窗口。激活窗口仍然维持激活状态。nCmdShow=4。

    SW_SHOWNORMAL:激活并显示一个窗口。如果窗口被最小化或最大化,系统将其恢复到原来的尺寸和大小。应用程序在第一次显示窗口的时候应该指定此标志。nCmdShow=1。


  3. 在设置成功显示状态操作后,通过User32调用 boolean SetForegroundWindow(HWND hWnd) 将窗口置为最前并获取焦点,即激活输入框光标,设置成功返回ture,否则返回false。hWnd为第一步获取的窗口句柄。

通过以上三步获取到窗口并能执行输入操作后,调用Robot,将要发送的消息内容字符串放入剪切板中,然后通过模拟粘贴操作,即ctrl+v组合键,将消息粘贴到聊天输入窗中,最后模拟回车键发送消息,至此就是一个完整的发送消息过程。不过有一个问题就是,云服务器是通过远程桌面进行访问的,在远程桌面访问关闭后,会进入锁屏,这样User32操作就会失败,目前还没找到解决的办法。


【GNZ48-章泽婷应援会】基于Java的SNH48Group应援会机器人(一)项目简介

【GNZ48-章泽婷应援会】基于Java的SNH48Group应援会机器人(二)获取数据

【GNZ48-章泽婷应援会】基于Java的SNH48Group应援会机器人(三)发送消息相关推荐

  1. 工作随记-Java利用企业微信群机器人定时发送消息

    hi,大家好,我是恰恰 阅读本文需要2分钟~ 最近利用企业微信群机器人做的需求主要有 1.返奖率通知与告警:抽奖箱能抽出垃圾也能抽出大货,每隔5分钟查询一下这个返奖率,如果用户频繁抽出大货,这个抽奖箱 ...

  2. 基于JAVA的简易坦克大战(三)

    第一章 运行环境(软.硬件环境) 1.1 坦克大战游戏的运行软件环境 本坦克大战游戏是基于JAVA语言程序设计,利用Eclipse 3.7.0在Windows操作系统环境下开发测试实现的,它的软件运行 ...

  3. 详解23种设计模式(基于Java)—— 结构型模式(三 / 五)

    目录 3.结构型模式(7种) 3.1.代理模式 3.1.1.概述 3.1.2.结构 3.1.3.静态代理 3.1.4.JDK动态代理 3.1.5.CGLIB动态代理 3.1.6.三种代理的对比 3.1 ...

  4. 技巧: 用 JAXM 发送和接收 SOAP 消息—Java API 使许多手工生成和发送消息方面必需的步骤自动化...

    简介: 在本篇技巧文章中,作者兼开发人员 Nicholas Chase 向您演示如何使用用于 XML 消息传递的 Java API(Java API for XML Messaging (JAXM)) ...

  5. java发微信_java访问微信接口发送消息

    最近在开发activiti流程的时候有个需求:流程到达每个审批节点后,需要向该节点的审批人发送一个消息,提示有审批需要处理. 参考了一下微信的开发者文档和网络上的一些技术博客,现在记录一下.以便后续继 ...

  6. Java代码实现向企业微信用户发送消息

    公司内部交流使用的企业微信,最近项目中要实现向员工发送企业微信通知,于是看了下企业微信的api,简单实现了下: 1. 其实就是一个HTTP请求,如下 请求方式:POST(HTTPS) 请求地址: ht ...

  7. 基于python,控制微信自动登录并发送消息给指定联系人

    给出微信联系人.微信启动路径.消息内容,实现自动登录并并发送消息,如果需要定时发送可以开一个线程或者用while True实现.上代码,报错的话可能是缺少对应的库,自行百度安装即可. 下面代码 复制后 ...

  8. 基于JAVA的网上订餐外卖系统(Java+MySQL)

    基于JAVA的网上订餐外卖系统(Java+MySQL)-编号:jsp0812 美食是人类永恒的话题,无论是在古代还是现代人们对美食都有一种非常的热爱在里面,但是随着时代的发展,人们可能没有更多的时间去 ...

  9. 基于JAVA的网上订餐外卖系统-计算机毕业设计

    项目介绍 美食是人类永恒的话题,无论是在古代还是现代人们对美食都有一种非常的热爱在里面,但是随着时代的发展,人们可能没有更多的时间去研究美食,很多时候人们在下班或者放学之后更希望通过网络来进行订餐,为 ...

最新文章

  1. Lua之Lua安装与Lua变量-TTLSA(一)
  2. Win8 Metro(C#)数字图像处理--2.69中点滤波器
  3. 【Android 内存优化】Android 原生 API 图片压缩原理 ( 图片质量压缩方法 | 查找 Java 源码中的 native 方法对应的 C++ 源码 )
  4. 准确率 召回率_机器学习tips(四):精确率(precision)和召回率(recall)
  5. python播放在线音乐_Python实现在线音乐播放器
  6. 计算机网络怎么查看连接打印机驱动,如何检测网络打印机是否已成功连接到计算机[检测方法]...
  7. [置顶] 混响音效
  8. 各种浏览器css不兼容的写法
  9. hibench测试出现问题--zookeeper
  10. opencv-api filter2D
  11. SAP License:决胜职场先决条件 白领们必须要懂得的人际经
  12. PHP网站首页空白刷新就好了,部署好后网站一片空白,不显示内容
  13. 了解OutOfMemoryError异常 - 深入Java虚拟机读后总结
  14. 如何在没有配备U1芯片的iPhone上使用AIrTag?
  15. 本地调试微信接口花生壳等域名被限制拉黑
  16. Cocos Creator ts版本使用protobuf
  17. The client was disconnected by the server because of inactivity解决方案
  18. poj1273 Drainage Ditches
  19. 日期时间格式 - 助手类[方法] - 收集
  20. 2022-23 年电子邮箱哪个好用?邮箱大全测评来了,请及时查看哦

热门文章

  1. IDEA 在 mac 下中文输入法使用英文标点符号
  2. python简单操作题,【python操作题】- 虎课网
  3. Codeforces Global Round 1 D - Jongmah(dp)
  4. Oracle 表关联、半关联、反关联
  5. 我整理的车联网行业可能要了解的简称解释(不定期更新)
  6. mysql primary key 多个_关于mysql中primary key重复的解决方法
  7. 生命在于学习——文件解析
  8. Reversing:逆向工程揭密
  9. 跑步时戴什么耳机好,五款最适合跑步佩戴的耳机推荐
  10. 烤仔建工携手MultiverseDAO,共促元宇宙生态建设