├── .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 |
--------------------------------------------------------------------------------