├── .gitignore ├── src └── main │ ├── resources │ └── application.yml │ └── java │ └── cn │ └── yangself │ ├── wechatBotClient │ ├── utils │ │ ├── UUIDRandom.java │ │ ├── NetPostRequest │ │ │ ├── RestTemlateConfig.java │ │ │ └── NetRequest.java │ │ └── GetImgFromRemote.java │ ├── domain │ │ └── WXMsg.java │ ├── controller │ │ └── WXMsgController.java │ └── service │ │ └── WXServerListener.java │ └── WechatBotClientApplication.java ├── README.md └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | target/ 3 | *.iml -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | config: 2 | weChat: 3 | # url: ws://localhost:5555 4 | url: ws://192.168.208.15:5555 5 | 6 | server: 7 | port: 8080 -------------------------------------------------------------------------------- /src/main/java/cn/yangself/wechatBotClient/utils/UUIDRandom.java: -------------------------------------------------------------------------------- 1 | package cn.yangself.wechatBotClient.utils; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * 获取随机字符串 7 | */ 8 | public class UUIDRandom { 9 | private static String getUUID(){ 10 | return UUID.randomUUID().toString().replaceAll("-", ""); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WeChat-Bot-Client 微信机器人客户端 2 | 3 | 本项目是基于服务器端项目:[带二次开发接口的PC微信聊天机器人](https://github.com/cixingguangming55555/wechat-bot) 进行二次开发的。 4 | 5 | 也是对目前项目中已经存在的Java版客户端进行了改良。 6 | 7 | 两个优点: 8 | 9 | - 结构清晰,在`WXServerListener`类中的`onMessage()`方法里添加反馈方法即可实现机器人的自动回复功能,同时包装了发送消息的请求,调用方法,传递参数即可使用。 10 | - 添加了自动重启的机制,当服务器端断开连接,或者客户端发生异常时,客户端会自动重启,等待新的连接。 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/cn/yangself/wechatBotClient/domain/WXMsg.java: -------------------------------------------------------------------------------- 1 | package cn.yangself.wechatBotClient.domain; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Data 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | @Builder 13 | public class WXMsg { 14 | private String id; 15 | private String wxid; 16 | private String content; 17 | private String roomId; 18 | private int type; 19 | private String nick; 20 | 21 | public String toJson() { 22 | return JSON.toJSONString(this); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/cn/yangself/wechatBotClient/utils/NetPostRequest/RestTemlateConfig.java: -------------------------------------------------------------------------------- 1 | package cn.yangself.wechatBotClient.utils.NetPostRequest; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.http.client.ClientHttpRequestFactory; 6 | import org.springframework.http.client.SimpleClientHttpRequestFactory; 7 | import org.springframework.web.client.RestTemplate; 8 | 9 | /** 10 | * 发送请求的依赖 11 | */ 12 | @Configuration 13 | public class RestTemlateConfig { 14 | @Bean 15 | public RestTemplate restTemplate(ClientHttpRequestFactory factory){ 16 | return new RestTemplate(factory); 17 | } 18 | 19 | @Bean 20 | public ClientHttpRequestFactory simpleClientHttpRequestFactory(){ 21 | SimpleClientHttpRequestFactory factory=new SimpleClientHttpRequestFactory(); 22 | factory.setConnectTimeout(15000); 23 | factory.setReadTimeout(5000); 24 | return factory; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/cn/yangself/WechatBotClientApplication.java: -------------------------------------------------------------------------------- 1 | package cn.yangself; 2 | 3 | import cn.yangself.wechatBotClient.service.WXServerListener; 4 | import org.java_websocket.enums.ReadyState; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.context.ConfigurableApplicationContext; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 11 | import org.springframework.scheduling.annotation.EnableScheduling; 12 | 13 | @SpringBootApplication 14 | public class WechatBotClientApplication { 15 | 16 | @Value("${config.weChat.url}") 17 | private String weChatUrl; 18 | 19 | public static String[] args; 20 | public static ConfigurableApplicationContext context ; 21 | 22 | public static void main(String[] args) { 23 | WechatBotClientApplication.args = args; 24 | WechatBotClientApplication.context = SpringApplication.run(WechatBotClientApplication.class, args); 25 | } 26 | 27 | @Bean 28 | public WXServerListener getWXServerListener() throws Exception { 29 | WXServerListener client = new WXServerListener(weChatUrl); 30 | client.connect(); 31 | while (!client.getReadyState().equals(ReadyState.OPEN)) { 32 | Thread.sleep(500); 33 | System.out.println("正在建立连接......"); 34 | } 35 | return client; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | cn.yangself 8 | wechat-bot-client 9 | 1.0-SNAPSHOT 10 | war 11 | 12 | wechat-bot-client 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.2.5.RELEASE 18 | 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | org.java-websocket 28 | Java-WebSocket 29 | 1.5.0 30 | 31 | 32 | org.projectlombok 33 | lombok 34 | 1.18.10 35 | provided 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter 40 | 41 | 42 | com.alibaba 43 | fastjson 44 | 1.2.83 45 | 46 | 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-maven-plugin 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/main/java/cn/yangself/wechatBotClient/utils/GetImgFromRemote.java: -------------------------------------------------------------------------------- 1 | package cn.yangself.wechatBotClient.utils; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.File; 5 | import java.io.FileOutputStream; 6 | import java.io.InputStream; 7 | import java.net.HttpURLConnection; 8 | import java.net.URL; 9 | 10 | /** 11 | * 通过url下载网络图片到本地的工具类 12 | * 如果调用接口收到网络连接的图片保存到缓存中进行发送操作 13 | */ 14 | public class GetImgFromRemote { 15 | 16 | public static void downloadImg(String imgUrl, String imgName) throws Exception { 17 | //new一个URL对象 18 | URL url = new URL(imgUrl); 19 | //打开链接 20 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 21 | //设置请求方式为"GET" 22 | conn.setRequestMethod("GET"); 23 | //超时响应时间为5秒 24 | conn.setConnectTimeout(5 * 1000); 25 | //通过输入流获取图片数据 26 | InputStream inStream = conn.getInputStream(); 27 | //得到图片的二进制数据,以二进制封装得到数据,具有通用性 28 | byte[] data = readInputStream(inStream); 29 | //new一个文件对象用来保存图片,默认保存当前工程根目录 30 | File imageFile = new File(imgName); 31 | //获取上级文件夹 32 | File parentFile = imageFile.getParentFile(); 33 | //如果上级文件夹不存在就创建文件夹 34 | if (!parentFile.exists()){ 35 | parentFile.mkdirs(); 36 | } 37 | //创建输出流 38 | FileOutputStream outStream = new FileOutputStream(imageFile); 39 | //写入数据 40 | outStream.write(data); 41 | //关闭输出流 42 | outStream.close(); 43 | } 44 | 45 | public static byte[] readInputStream(InputStream inStream) throws Exception { 46 | ByteArrayOutputStream outStream = new ByteArrayOutputStream(); 47 | //创建一个Buffer字符串 48 | byte[] buffer = new byte[1024]; 49 | //每次读取的字符串长度,如果为-1,代表全部读取完毕 50 | int len = 0; 51 | //使用一个输入流从buffer里把数据读取出来 52 | while ((len = inStream.read(buffer)) != -1) { 53 | //用输出流往buffer里写入数据,中间参数代表从哪个位置开始读,len代表读取的长度 54 | outStream.write(buffer, 0, len); 55 | } 56 | //关闭输入流 57 | inStream.close(); 58 | //把outStream里的数据写入内存 59 | return outStream.toByteArray(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/cn/yangself/wechatBotClient/utils/NetPostRequest/NetRequest.java: -------------------------------------------------------------------------------- 1 | package cn.yangself.wechatBotClient.utils.NetPostRequest; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.http.HttpEntity; 6 | import org.springframework.http.HttpHeaders; 7 | import org.springframework.http.MediaType; 8 | import org.springframework.http.converter.HttpMessageConverter; 9 | import org.springframework.http.converter.StringHttpMessageConverter; 10 | import org.springframework.stereotype.Component; 11 | import org.springframework.web.client.RestTemplate; 12 | 13 | import java.nio.charset.Charset; 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | /** 19 | * 这个类用来发送Post请求 20 | */ 21 | @Component 22 | public class NetRequest { 23 | 24 | @Autowired 25 | private RestTemplate restTemplate ; 26 | 27 | /** 28 | * 发送post请求 29 | * @param paramsMap 30 | * @param url 31 | * @return 32 | */ 33 | public Map sendPost(String url, Map paramsMap){ 34 | Map res = new HashMap(); 35 | try { 36 | HttpHeaders headers = new HttpHeaders(); 37 | headers.add("Content-Type", MediaType.APPLICATION_JSON_UTF8_VALUE); 38 | //用HttpEntity封装整个请求报文 39 | HttpEntity httpEntity= new HttpEntity(paramsMap,headers); 40 | 41 | String backInfo = getRestTemplateBuilder().postForObject(url, httpEntity, String.class); 42 | res = JSON.parseObject(backInfo); 43 | } catch (Exception e) { 44 | res.put("code", 500); 45 | res.put("success", false); 46 | res.put("messgae", "参数有误"); 47 | e.printStackTrace(); 48 | } 49 | return res; 50 | } 51 | 52 | public RestTemplate getRestTemplateBuilder(){ 53 | List> httpMessageConverters = restTemplate.getMessageConverters(); 54 | httpMessageConverters.stream().forEach(httpMessageConverter -> { 55 | if (httpMessageConverter instanceof StringHttpMessageConverter) { 56 | StringHttpMessageConverter messageConverter = (StringHttpMessageConverter) httpMessageConverter; 57 | messageConverter.setDefaultCharset(Charset.forName("UTF-8")); 58 | } 59 | }); 60 | return restTemplate; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/cn/yangself/wechatBotClient/controller/WXMsgController.java: -------------------------------------------------------------------------------- 1 | package cn.yangself.wechatBotClient.controller; 2 | 3 | import cn.yangself.wechatBotClient.service.WXServerListener; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.web.bind.annotation.*; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | @Controller 12 | @RequestMapping("/wxbot") 13 | public class WXMsgController { 14 | @Autowired 15 | private WXServerListener wxServerListener; 16 | 17 | 18 | @PostMapping("/textMsg") 19 | @ResponseBody 20 | public Map sendTextMsg(@RequestBody Map sendMap){ 21 | Map resultMap = new HashMap<>(); 22 | try{ 23 | String wxid = sendMap.get("wxid").toString(); 24 | String msg = sendMap.get("msg").toString(); 25 | wxServerListener.sendTextMsg(wxid,msg); 26 | resultMap.put("code", 200); 27 | resultMap.put("msg", "发送成功!"); 28 | }catch(Exception e){ 29 | resultMap.put("code", 500); 30 | resultMap.put("msg", "发送失败!服务器发生错误!"); 31 | resultMap.put("errorMsg", e.getMessage()); 32 | e.printStackTrace(); 33 | } 34 | return resultMap; 35 | } 36 | 37 | //暂不可用 38 | //@PostMapping("/atMsg") 39 | //@ResponseBody 40 | //public Map sendAtMsg(@RequestBody Map sendMap){ 41 | // Map resultMap = new HashMap<>(); 42 | // try{ 43 | // //String wxid = sendMap.get("wxid").toString(); 44 | // //String text = sendMap.get("text").toString(); 45 | // //String text = "null"; 46 | // //String roomId = sendMap.get("roomId").toString(); 47 | // wxServerListener.sendAtMsg(); 48 | // resultMap.put("code", 200); 49 | // resultMap.put("msg", "发送成功!"); 50 | // }catch(Exception e){ 51 | // resultMap.put("code", 500); 52 | // resultMap.put("msg", "发送失败!服务器发生错误!"); 53 | // resultMap.put("errorMsg", e.getMessage()); 54 | // e.printStackTrace(); 55 | // } 56 | // return resultMap; 57 | //} 58 | 59 | @GetMapping("/contactList") 60 | @ResponseBody 61 | public Map getContact(){ 62 | Map resultMap = new HashMap<>(); 63 | try{ 64 | wxServerListener.getContactList(); 65 | resultMap.put("code", 200); 66 | resultMap.put("msg", "发送成功!"); 67 | }catch(Exception e){ 68 | resultMap.put("code", 500); 69 | resultMap.put("msg", "发送失败!服务器发生错误!"); 70 | resultMap.put("errorMsg", e.getMessage()); 71 | e.printStackTrace(); 72 | } 73 | return resultMap; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/cn/yangself/wechatBotClient/service/WXServerListener.java: -------------------------------------------------------------------------------- 1 | package cn.yangself.wechatBotClient.service; 2 | 3 | import cn.yangself.WechatBotClientApplication; 4 | import cn.yangself.wechatBotClient.domain.WXMsg; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.java_websocket.client.WebSocketClient; 7 | import org.java_websocket.enums.ReadyState; 8 | import org.java_websocket.handshake.ServerHandshake; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.boot.SpringApplication; 11 | import org.springframework.context.ConfigurableApplicationContext; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.stereotype.Component; 14 | 15 | import java.net.URI; 16 | import java.net.URISyntaxException; 17 | import java.util.Date; 18 | import java.util.concurrent.ArrayBlockingQueue; 19 | import java.util.concurrent.ExecutorService; 20 | import java.util.concurrent.ThreadPoolExecutor; 21 | import java.util.concurrent.TimeUnit; 22 | 23 | @Slf4j 24 | public class WXServerListener extends WebSocketClient { 25 | 26 | private static final int HEART_BEAT = 5005; //服务器返回心跳包 27 | private static final int RECV_TXT_MSG = 1; //收到的消息为文字消息 28 | private static final int RECV_PIC_MSG = 3; //收到的消息为图片消息 29 | private static final int USER_LIST = 5000; //发送消息类型为获取用户列表 30 | private static final int GET_USER_LIST_SUCCSESS = 5001; //获取用户列表成功 31 | private static final int GET_USER_LIST_FAIL = 5002; //获取用户列表失败 32 | private static final int TXT_MSG = 555; //发送消息类型为文本 33 | private static final int PIC_MSG = 500; //发送消息类型为图片 34 | private static final int AT_MSG = 550; //发送群中@用户的消息 35 | private static final int CHATROOM_MEMBER = 5010; //获取群成员 36 | private static final int CHATROOM_MEMBER_NICK = 5020; 37 | private static final int PERSONAL_INFO = 6500; 38 | private static final int DEBUG_SWITCH = 6000; 39 | private static final int PERSONAL_DETAIL =6550; 40 | private static final int DESTROY_ALL = 9999; 41 | 42 | private static final String ROOM_MEMBER_LIST = "op:list member"; 43 | private static final String CONTACT_LIST = "user list"; 44 | private static final String NULL_MSG = "null"; 45 | 46 | public WXServerListener(String url) throws URISyntaxException { 47 | super(new URI(url)); 48 | } 49 | 50 | @Override 51 | public void onOpen(ServerHandshake serverHandshake) { 52 | log.info("正在建立连接......"); 53 | } 54 | 55 | /** 56 | * 在这里进行消息监听 57 | * @param s 58 | */ 59 | @Override 60 | public void onMessage(String s) { 61 | //在这里编写应答的一些代码 62 | //可以在这里通过正则,或者是发送的消息类型进行判断,进行一些文字回复 63 | //也可以在这里调用的其他的接口,可以使用Utils包下面的NetPostRequest进行相应的调用 64 | 65 | //注意对sender为ROOT时的消息进行过滤,还有对公众号的消息进行过滤(gh_xxxxxx) 66 | log.info("接收到的消息 --> " + s); 67 | } 68 | 69 | @Override 70 | public void onClose(int i, String s, boolean b) { 71 | log.info("断开连接!"); 72 | //重启客户端 73 | restartListener(); 74 | } 75 | 76 | @Override 77 | public void onError(Exception e) { 78 | log.info("服务器发生异常!"); 79 | log.info(e.getMessage()); 80 | e.printStackTrace(); 81 | //重启客户端 82 | restartListener(); 83 | } 84 | 85 | 86 | /** 87 | * 发送信息 88 | * @param json 要发送信息的json字符串 89 | */ 90 | private void sendMsg(String json) { 91 | try { 92 | send(json); 93 | } catch (Exception e) { 94 | //发送消息失败! 95 | log.info("发送消息失败!"); 96 | log.info(e.getMessage()); 97 | e.printStackTrace(); 98 | } 99 | } 100 | 101 | /** 102 | * 获取会话ID 103 | * @return 104 | */ 105 | private String getSessionId(){ 106 | return String.valueOf(new Date().getTime()); 107 | } 108 | 109 | /** 110 | * 发送文本消息 111 | * @param wxid 个人的wxid或者群id(xxx@chatroom) 112 | * @param text 要发送的消息内容 113 | */ 114 | public void sendTextMsg(String wxid, String text){ 115 | //创建发送消息JSON 116 | String json = WXMsg.builder() 117 | .content(text) 118 | .wxid(wxid) 119 | .type(TXT_MSG) 120 | .id(getSessionId()) 121 | .build() 122 | .toJson(); 123 | log.info("发送文本消息 --> " + json); 124 | sendMsg(json); 125 | } 126 | 127 | /** 128 | * 发送图片消息 129 | * @param wxid 个人的wxid或者群id(xxx@chatroom) 130 | * @param imgUrlStr 发送图片的绝对路径 131 | */ 132 | public void sendImgMsg(String wxid, String imgUrlStr) { 133 | //创建发送消息JSON 134 | String json = WXMsg.builder() 135 | .content(imgUrlStr) 136 | .wxid(wxid) 137 | .type(PIC_MSG) 138 | .id(getSessionId()) 139 | .build() 140 | .toJson(); 141 | log.info("发送图片消息 --> " + json); 142 | sendMsg(json); 143 | } 144 | 145 | /** 146 | * 发送AT类型消息 ---> 暂不可用 147 | */ 148 | public void sendAtMsg(String wxid, String roomId, String text){ 149 | //创建发送消息JSON 150 | String json = WXMsg.builder() 151 | .content(text) 152 | .wxid(wxid) 153 | .roomId(roomId) 154 | .type(AT_MSG) 155 | .id(getSessionId()) 156 | .build() 157 | .toJson(); 158 | log.info("发送微信群AT成员消息 --> " + json); 159 | sendMsg(json); 160 | } 161 | 162 | /** 163 | * 获取联系人列表 164 | */ 165 | public void getContactList() { 166 | //创建发送消息JSON 167 | String json = WXMsg.builder() 168 | .content(CONTACT_LIST) 169 | .wxid(NULL_MSG) 170 | .type(USER_LIST) 171 | .id(getSessionId()) 172 | .build() 173 | .toJson(); 174 | log.info("发送获取联系人列表请求 --> " + json); 175 | sendMsg(json); 176 | } 177 | 178 | /** 179 | * 获取所有群成员列表 180 | */ 181 | public void getRoomMemberList() { 182 | //创建发送消息JSON 183 | String json = WXMsg.builder() 184 | .content(ROOM_MEMBER_LIST) 185 | .wxid(NULL_MSG) 186 | .type(CHATROOM_MEMBER) 187 | .id(getSessionId()) 188 | .build() 189 | .toJson(); 190 | log.info("发送获取所有群成员列表请求 --> " + json); 191 | sendMsg(json); 192 | } 193 | 194 | /** 195 | * Spring重启,实现客户端的自动重连 196 | */ 197 | public void restartListener(){ 198 | ExecutorService threadPool = new ThreadPoolExecutor(1, 1, 0, 199 | TimeUnit.SECONDS, new ArrayBlockingQueue<>(1), new ThreadPoolExecutor.DiscardOldestPolicy()); 200 | threadPool.execute(() -> { 201 | WechatBotClientApplication.context.close(); 202 | WechatBotClientApplication.context = SpringApplication.run(WechatBotClientApplication.class, 203 | WechatBotClientApplication.args); 204 | }); 205 | threadPool.shutdown(); 206 | 207 | } 208 | } 209 | --------------------------------------------------------------------------------