Skip to content

Commit 6f95386

Browse files
xyz9025yang ran
and
yang ran
authored
🆕 #1720 增加企业微信群机器人消息发送接口
* #1720 增加群机器人的消息类型 * #1720 增加文件流生成base64方法,用于图片转base64,群机器人图片消息发送测试 * #1720 增加群机器人消息推送地址webhook/send * #1720 增加群机器人webhook_key配置属性 * #1720 增加群机器人消息推送接口服务、不需要自动带accessToken的post请求接口 * #1720 新增微信群机器人消息发送api * #1720 新增微信群机器人消息发送api单元测试 * #1720 新增微信群机器人消息发送api单元测试配置、新增属性webhook配置 Co-authored-by: yang ran <[email protected]>
1 parent 17c2042 commit 6f95386

File tree

14 files changed

+427
-0
lines changed

14 files changed

+427
-0
lines changed

Diff for: weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java

+25
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,31 @@ public static class KefuMsgType {
105105
public static final String MINIPROGRAM_NOTICE = "miniprogram_notice";
106106
}
107107

108+
/**
109+
* 群机器人的消息类型.
110+
*/
111+
public static class GroupRobotMsgType {
112+
/**
113+
* 文本消息.
114+
*/
115+
public static final String TEXT = "text";
116+
117+
/**
118+
* 图片消息.
119+
*/
120+
public static final String IMAGE = "image";
121+
122+
/**
123+
* markdown消息.
124+
*/
125+
public static final String MARKDOWN = "markdown";
126+
127+
/**
128+
* 图文消息(点击跳转到外链).
129+
*/
130+
public static final String NEWS = "news";
131+
}
132+
108133
/**
109134
* 表示是否是保密消息,0表示否,1表示是,默认0.
110135
*/

Diff for: weixin-java-common/src/main/java/me/chanjar/weixin/common/util/fs/FileUtils.java

+29
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.io.IOException;
55
import java.io.InputStream;
66
import java.nio.file.Files;
7+
import java.util.Base64;
78

89
public class FileUtils {
910

@@ -34,4 +35,32 @@ public static File createTmpFile(InputStream inputStream, String name, String ex
3435
return createTmpFile(inputStream, name, ext, Files.createTempDirectory("weixin-java-tools-temp").toFile());
3536
}
3637

38+
/**
39+
* 文件流生成base64
40+
*
41+
* @param in 文件流
42+
* @return base64编码
43+
*/
44+
public static String imageToBase64ByStream(InputStream in) {
45+
byte[] data = null;
46+
// 读取图片字节数组
47+
try {
48+
data = new byte[in.available()];
49+
in.read(data);
50+
// 返回Base64编码过的字节数组字符串
51+
return Base64.getEncoder().encodeToString(data);
52+
} catch (IOException e) {
53+
e.printStackTrace();
54+
} finally {
55+
if (in != null) {
56+
try {
57+
in.close();
58+
} catch (IOException e) {
59+
e.printStackTrace();
60+
}
61+
}
62+
}
63+
return null;
64+
}
65+
3766
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package me.chanjar.weixin.cp.api;
2+
3+
import me.chanjar.weixin.common.error.WxErrorException;
4+
import me.chanjar.weixin.cp.bean.article.NewArticle;
5+
6+
import java.util.List;
7+
8+
/**
9+
* 微信群机器人消息发送api
10+
* 文档地址:https://work.weixin.qq.com/help?doc_id=13376
11+
* 调用地址:https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=
12+
*
13+
* @author yr
14+
* @date 2020-8-20
15+
*/
16+
public interface WxCpGroupRobotService {
17+
18+
/**
19+
* 发送text类型的消息
20+
*
21+
* @param content 文本内容,最长不超过2048个字节,必须是utf8编码
22+
* @param mentionedList userId的列表,提醒群中的指定成员(@某个成员),@all表示提醒所有人,如果开发者获取不到userId,可以使用mentioned_mobile_list
23+
* @param mobileList 手机号列表,提醒手机号对应的群成员(@某个成员),@all表示提醒所有人
24+
* @throws WxErrorException 异常
25+
*/
26+
void sendText(String content, List<String> mentionedList, List<String> mobileList) throws WxErrorException;
27+
28+
/**
29+
* 发送markdown类型的消息
30+
*
31+
* @param content markdown内容,最长不超过4096个字节,必须是utf8编码
32+
* @throws WxErrorException 异常
33+
*/
34+
void sendMarkDown(String content) throws WxErrorException;
35+
36+
/**
37+
* 发送image类型的消息
38+
*
39+
* @param base64 图片内容的base64编码
40+
* @param md5 图片内容(base64编码前)的md5值
41+
* @throws WxErrorException 异常
42+
*/
43+
void sendImage(String base64, String md5) throws WxErrorException;
44+
45+
/**
46+
* 发送news类型的消息
47+
*
48+
* @param articleList 图文消息,支持1到8条图文
49+
* @throws WxErrorException 异常
50+
*/
51+
void sendNews(List<NewArticle> articleList) throws WxErrorException;
52+
}

Diff for: weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java

+15
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,14 @@ public interface WxCpService {
173173
*/
174174
String post(String url, String postData) throws WxErrorException;
175175

176+
/**
177+
* 当不需要自动带accessToken的时候,可以用这个发起post请求
178+
*
179+
* @param url 接口地址
180+
* @param postData 请求body字符串
181+
*/
182+
String postWithoutToken(String url, String postData) throws WxErrorException;
183+
176184
/**
177185
* <pre>
178186
* Service没有实现某个API的时候,可以用这个,
@@ -328,6 +336,13 @@ public interface WxCpService {
328336

329337
WxCpOaService getOAService();
330338

339+
/**
340+
* 获取群机器人消息推送服务
341+
*
342+
* @return 群机器人消息推送服务
343+
*/
344+
WxCpGroupRobotService getGroupRobotService();
345+
331346
/**
332347
* http请求对象
333348
*/

Diff for: weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java

+32
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
5151
private WxCpOaService oaService = new WxCpOaServiceImpl(this);
5252
private WxCpTaskCardService taskCardService = new WxCpTaskCardServiceImpl(this);
5353
private WxCpExternalContactService externalContactService = new WxCpExternalContactServiceImpl(this);
54+
private WxCpGroupRobotService groupRobotService = new WxCpGroupRobotServiceImpl(this);
5455

5556
/**
5657
* 全局的是否正在刷新access token的锁.
@@ -217,6 +218,11 @@ public String post(String url, String postData) throws WxErrorException {
217218
return execute(SimplePostRequestExecutor.create(this), url, postData);
218219
}
219220

221+
@Override
222+
public String postWithoutToken(String url, String postData) throws WxErrorException {
223+
return this.executeNormal(SimplePostRequestExecutor.create(this), url, postData);
224+
}
225+
220226
/**
221227
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求.
222228
*/
@@ -296,6 +302,27 @@ protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E
296302
}
297303
}
298304

305+
/**
306+
* 普通请求,不自动带accessToken
307+
*/
308+
private <T, E> T executeNormal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
309+
try {
310+
T result = executor.execute(uri, data, WxType.CP);
311+
log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uri, data, result);
312+
return result;
313+
} catch (WxErrorException e) {
314+
WxError error = e.getError();
315+
if (error.getErrorCode() != 0) {
316+
log.error("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uri, data, error);
317+
throw new WxErrorException(error, e);
318+
}
319+
return null;
320+
} catch (IOException e) {
321+
log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uri, data, e.getMessage());
322+
throw new RuntimeException(e);
323+
}
324+
}
325+
299326
@Override
300327
public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider) {
301328
this.configStorage = wxConfigProvider;
@@ -412,6 +439,11 @@ public WxCpOaService getOAService() {
412439
return oaService;
413440
}
414441

442+
@Override
443+
public WxCpGroupRobotService getGroupRobotService() {
444+
return groupRobotService;
445+
}
446+
415447
@Override
416448
public WxCpTaskCardService getTaskCardService() {
417449
return taskCardService;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package me.chanjar.weixin.cp.api.impl;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import me.chanjar.weixin.common.api.WxConsts;
5+
import me.chanjar.weixin.common.error.WxErrorException;
6+
import me.chanjar.weixin.cp.api.WxCpGroupRobotService;
7+
import me.chanjar.weixin.cp.api.WxCpService;
8+
import me.chanjar.weixin.cp.bean.WxCpGroupRobotMessage;
9+
import me.chanjar.weixin.cp.bean.article.NewArticle;
10+
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
11+
import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
12+
13+
import java.util.List;
14+
15+
/**
16+
* 微信群机器人消息发送api 实现
17+
*
18+
* @author yr
19+
* @date 2020-08-20
20+
*/
21+
@RequiredArgsConstructor
22+
public class WxCpGroupRobotServiceImpl implements WxCpGroupRobotService {
23+
private final WxCpService cpService;
24+
25+
private String getApiUrl() {
26+
WxCpConfigStorage wxCpConfigStorage = cpService.getWxCpConfigStorage();
27+
return wxCpConfigStorage.getApiUrl(WxCpApiPathConsts.WEBHOOK_SEND) + wxCpConfigStorage.getWebhookKey();
28+
}
29+
30+
@Override
31+
public void sendText(String content, List<String> mentionedList, List<String> mobileList) throws WxErrorException {
32+
WxCpGroupRobotMessage message = new WxCpGroupRobotMessage()
33+
.setMsgType(WxConsts.GroupRobotMsgType.TEXT)
34+
.setContent(content)
35+
.setMentionedList(mentionedList)
36+
.setMentionedMobileList(mobileList);
37+
cpService.postWithoutToken(this.getApiUrl(), message.toJson());
38+
}
39+
40+
@Override
41+
public void sendMarkDown(String content) throws WxErrorException {
42+
WxCpGroupRobotMessage message = new WxCpGroupRobotMessage()
43+
.setMsgType(WxConsts.GroupRobotMsgType.MARKDOWN)
44+
.setContent(content);
45+
cpService.postWithoutToken(this.getApiUrl(), message.toJson());
46+
}
47+
48+
@Override
49+
public void sendImage(String base64, String md5) throws WxErrorException {
50+
WxCpGroupRobotMessage message = new WxCpGroupRobotMessage()
51+
.setMsgType(WxConsts.GroupRobotMsgType.IMAGE)
52+
.setBase64(base64)
53+
.setMd5(md5);
54+
cpService.postWithoutToken(this.getApiUrl(), message.toJson());
55+
}
56+
57+
@Override
58+
public void sendNews(List<NewArticle> articleList) throws WxErrorException {
59+
WxCpGroupRobotMessage message = new WxCpGroupRobotMessage()
60+
.setMsgType(WxConsts.GroupRobotMsgType.NEWS)
61+
.setArticles(articleList);
62+
cpService.postWithoutToken(this.getApiUrl(), message.toJson());
63+
}
64+
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package me.chanjar.weixin.cp.bean;
2+
3+
import com.google.gson.JsonArray;
4+
import com.google.gson.JsonObject;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Data;
7+
import lombok.NoArgsConstructor;
8+
import lombok.experimental.Accessors;
9+
import me.chanjar.weixin.cp.bean.article.NewArticle;
10+
11+
import java.util.List;
12+
13+
import static me.chanjar.weixin.common.api.WxConsts.GroupRobotMsgType.*;
14+
15+
/**
16+
* 微信群机器人消息
17+
*
18+
* @author yr
19+
* @date 2020-08-20
20+
*/
21+
@AllArgsConstructor
22+
@NoArgsConstructor
23+
@Accessors(chain = true)
24+
@Data
25+
public class WxCpGroupRobotMessage {
26+
/**
27+
* 消息类型
28+
*/
29+
private String msgType;
30+
31+
/**
32+
* 文本内容,最长不超过2048个字节,markdown内容,最长不超过4096个字节,必须是utf8编码
33+
* 必填
34+
*/
35+
private String content;
36+
/**
37+
* userid的列表,提醒群中的指定成员(@某个成员),@all表示提醒所有人,如果开发者获取不到userid,可以使用mentioned_mobile_list
38+
*/
39+
private List<String> mentionedList;
40+
/**
41+
* 手机号列表,提醒手机号对应的群成员(@某个成员),@all表示提醒所有人
42+
*/
43+
private List<String> mentionedMobileList;
44+
/**
45+
* 图片内容的base64编码
46+
*/
47+
private String base64;
48+
/**
49+
* 图片内容(base64编码前)的md5值
50+
*/
51+
private String md5;
52+
/**
53+
* 图文消息,一个图文消息支持1到8条图文
54+
*/
55+
private List<NewArticle> articles;
56+
57+
public String toJson() {
58+
JsonObject messageJson = new JsonObject();
59+
messageJson.addProperty("msgtype", this.getMsgType());
60+
61+
switch (this.getMsgType()) {
62+
case TEXT: {
63+
JsonObject text = new JsonObject();
64+
JsonArray uidJsonArray = new JsonArray();
65+
JsonArray mobileJsonArray = new JsonArray();
66+
67+
text.addProperty("content", this.getContent());
68+
69+
if (this.getMentionedList() != null) {
70+
for (String item : this.getMentionedList()) {
71+
uidJsonArray.add(item);
72+
}
73+
}
74+
if (this.getMentionedMobileList() != null) {
75+
for (String item : this.getMentionedMobileList()) {
76+
mobileJsonArray.add(item);
77+
}
78+
}
79+
text.add("mentioned_list", uidJsonArray);
80+
text.add("mentioned_mobile_list", mobileJsonArray);
81+
messageJson.add("text", text);
82+
break;
83+
}
84+
case MARKDOWN: {
85+
JsonObject text = new JsonObject();
86+
text.addProperty("content", this.getContent());
87+
messageJson.add("markdown", text);
88+
break;
89+
}
90+
case IMAGE: {
91+
JsonObject text = new JsonObject();
92+
text.addProperty("base64", this.getBase64());
93+
text.addProperty("md5", this.getMd5());
94+
messageJson.add("image", text);
95+
break;
96+
}
97+
case NEWS: {
98+
JsonObject text = new JsonObject();
99+
JsonArray array = new JsonArray();
100+
for (NewArticle article : this.getArticles()) {
101+
JsonObject articleJson = new JsonObject();
102+
articleJson.addProperty("title", article.getTitle());
103+
articleJson.addProperty("description", article.getDescription());
104+
articleJson.addProperty("url", article.getUrl());
105+
articleJson.addProperty("picurl", article.getPicUrl());
106+
array.add(articleJson);
107+
}
108+
text.add("articles", array);
109+
messageJson.add("news", text);
110+
break;
111+
}
112+
default:
113+
114+
}
115+
116+
return messageJson.toString();
117+
}
118+
}

0 commit comments

Comments
 (0)