Skip to content

Commit

Permalink
🆕 Wechat-Group#1720 增加企业微信群机器人消息发送接口
Browse files Browse the repository at this point in the history
* Wechat-Group#1720 增加群机器人的消息类型

* Wechat-Group#1720 增加文件流生成base64方法,用于图片转base64,群机器人图片消息发送测试

* Wechat-Group#1720 增加群机器人消息推送地址webhook/send

* Wechat-Group#1720 增加群机器人webhook_key配置属性

* Wechat-Group#1720 增加群机器人消息推送接口服务、不需要自动带accessToken的post请求接口

* Wechat-Group#1720 新增微信群机器人消息发送api

* Wechat-Group#1720 新增微信群机器人消息发送api单元测试

* Wechat-Group#1720 新增微信群机器人消息发送api单元测试配置、新增属性webhook配置

Co-authored-by: yang ran <yangran@xytdt.com>
  • Loading branch information
xyz9025 and yang ran committed Aug 21, 2020
1 parent 17c2042 commit 6f95386
Show file tree
Hide file tree
Showing 14 changed files with 427 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,31 @@ public static class KefuMsgType {
public static final String MINIPROGRAM_NOTICE = "miniprogram_notice";
}

/**
* 群机器人的消息类型.
*/
public static class GroupRobotMsgType {
/**
* 文本消息.
*/
public static final String TEXT = "text";

/**
* 图片消息.
*/
public static final String IMAGE = "image";

/**
* markdown消息.
*/
public static final String MARKDOWN = "markdown";

/**
* 图文消息(点击跳转到外链).
*/
public static final String NEWS = "news";
}

/**
* 表示是否是保密消息,0表示否,1表示是,默认0.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.Base64;

public class FileUtils {

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

/**
* 文件流生成base64
*
* @param in 文件流
* @return base64编码
*/
public static String imageToBase64ByStream(InputStream in) {
byte[] data = null;
// 读取图片字节数组
try {
data = new byte[in.available()];
in.read(data);
// 返回Base64编码过的字节数组字符串
return Base64.getEncoder().encodeToString(data);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package me.chanjar.weixin.cp.api;

import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.bean.article.NewArticle;

import java.util.List;

/**
* 微信群机器人消息发送api
* 文档地址:https://work.weixin.qq.com/help?doc_id=13376
* 调用地址:https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=
*
* @author yr
* @date 2020-8-20
*/
public interface WxCpGroupRobotService {

/**
* 发送text类型的消息
*
* @param content 文本内容,最长不超过2048个字节,必须是utf8编码
* @param mentionedList userId的列表,提醒群中的指定成员(@某个成员),@all表示提醒所有人,如果开发者获取不到userId,可以使用mentioned_mobile_list
* @param mobileList 手机号列表,提醒手机号对应的群成员(@某个成员),@all表示提醒所有人
* @throws WxErrorException 异常
*/
void sendText(String content, List<String> mentionedList, List<String> mobileList) throws WxErrorException;

/**
* 发送markdown类型的消息
*
* @param content markdown内容,最长不超过4096个字节,必须是utf8编码
* @throws WxErrorException 异常
*/
void sendMarkDown(String content) throws WxErrorException;

/**
* 发送image类型的消息
*
* @param base64 图片内容的base64编码
* @param md5 图片内容(base64编码前)的md5值
* @throws WxErrorException 异常
*/
void sendImage(String base64, String md5) throws WxErrorException;

/**
* 发送news类型的消息
*
* @param articleList 图文消息,支持1到8条图文
* @throws WxErrorException 异常
*/
void sendNews(List<NewArticle> articleList) throws WxErrorException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,14 @@ public interface WxCpService {
*/
String post(String url, String postData) throws WxErrorException;

/**
* 当不需要自动带accessToken的时候,可以用这个发起post请求
*
* @param url 接口地址
* @param postData 请求body字符串
*/
String postWithoutToken(String url, String postData) throws WxErrorException;

/**
* <pre>
* Service没有实现某个API的时候,可以用这个,
Expand Down Expand Up @@ -328,6 +336,13 @@ public interface WxCpService {

WxCpOaService getOAService();

/**
* 获取群机器人消息推送服务
*
* @return 群机器人消息推送服务
*/
WxCpGroupRobotService getGroupRobotService();

/**
* http请求对象
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
private WxCpOaService oaService = new WxCpOaServiceImpl(this);
private WxCpTaskCardService taskCardService = new WxCpTaskCardServiceImpl(this);
private WxCpExternalContactService externalContactService = new WxCpExternalContactServiceImpl(this);
private WxCpGroupRobotService groupRobotService = new WxCpGroupRobotServiceImpl(this);

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

@Override
public String postWithoutToken(String url, String postData) throws WxErrorException {
return this.executeNormal(SimplePostRequestExecutor.create(this), url, postData);
}

/**
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求.
*/
Expand Down Expand Up @@ -296,6 +302,27 @@ protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E
}
}

/**
* 普通请求,不自动带accessToken
*/
private <T, E> T executeNormal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
try {
T result = executor.execute(uri, data, WxType.CP);
log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uri, data, result);
return result;
} catch (WxErrorException e) {
WxError error = e.getError();
if (error.getErrorCode() != 0) {
log.error("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uri, data, error);
throw new WxErrorException(error, e);
}
return null;
} catch (IOException e) {
log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uri, data, e.getMessage());
throw new RuntimeException(e);
}
}

@Override
public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider) {
this.configStorage = wxConfigProvider;
Expand Down Expand Up @@ -412,6 +439,11 @@ public WxCpOaService getOAService() {
return oaService;
}

@Override
public WxCpGroupRobotService getGroupRobotService() {
return groupRobotService;
}

@Override
public WxCpTaskCardService getTaskCardService() {
return taskCardService;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package me.chanjar.weixin.cp.api.impl;

import lombok.RequiredArgsConstructor;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.api.WxCpGroupRobotService;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpGroupRobotMessage;
import me.chanjar.weixin.cp.bean.article.NewArticle;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;

import java.util.List;

/**
* 微信群机器人消息发送api 实现
*
* @author yr
* @date 2020-08-20
*/
@RequiredArgsConstructor
public class WxCpGroupRobotServiceImpl implements WxCpGroupRobotService {
private final WxCpService cpService;

private String getApiUrl() {
WxCpConfigStorage wxCpConfigStorage = cpService.getWxCpConfigStorage();
return wxCpConfigStorage.getApiUrl(WxCpApiPathConsts.WEBHOOK_SEND) + wxCpConfigStorage.getWebhookKey();
}

@Override
public void sendText(String content, List<String> mentionedList, List<String> mobileList) throws WxErrorException {
WxCpGroupRobotMessage message = new WxCpGroupRobotMessage()
.setMsgType(WxConsts.GroupRobotMsgType.TEXT)
.setContent(content)
.setMentionedList(mentionedList)
.setMentionedMobileList(mobileList);
cpService.postWithoutToken(this.getApiUrl(), message.toJson());
}

@Override
public void sendMarkDown(String content) throws WxErrorException {
WxCpGroupRobotMessage message = new WxCpGroupRobotMessage()
.setMsgType(WxConsts.GroupRobotMsgType.MARKDOWN)
.setContent(content);
cpService.postWithoutToken(this.getApiUrl(), message.toJson());
}

@Override
public void sendImage(String base64, String md5) throws WxErrorException {
WxCpGroupRobotMessage message = new WxCpGroupRobotMessage()
.setMsgType(WxConsts.GroupRobotMsgType.IMAGE)
.setBase64(base64)
.setMd5(md5);
cpService.postWithoutToken(this.getApiUrl(), message.toJson());
}

@Override
public void sendNews(List<NewArticle> articleList) throws WxErrorException {
WxCpGroupRobotMessage message = new WxCpGroupRobotMessage()
.setMsgType(WxConsts.GroupRobotMsgType.NEWS)
.setArticles(articleList);
cpService.postWithoutToken(this.getApiUrl(), message.toJson());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package me.chanjar.weixin.cp.bean;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import me.chanjar.weixin.cp.bean.article.NewArticle;

import java.util.List;

import static me.chanjar.weixin.common.api.WxConsts.GroupRobotMsgType.*;

/**
* 微信群机器人消息
*
* @author yr
* @date 2020-08-20
*/
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@Data
public class WxCpGroupRobotMessage {
/**
* 消息类型
*/
private String msgType;

/**
* 文本内容,最长不超过2048个字节,markdown内容,最长不超过4096个字节,必须是utf8编码
* 必填
*/
private String content;
/**
* userid的列表,提醒群中的指定成员(@某个成员),@all表示提醒所有人,如果开发者获取不到userid,可以使用mentioned_mobile_list
*/
private List<String> mentionedList;
/**
* 手机号列表,提醒手机号对应的群成员(@某个成员),@all表示提醒所有人
*/
private List<String> mentionedMobileList;
/**
* 图片内容的base64编码
*/
private String base64;
/**
* 图片内容(base64编码前)的md5值
*/
private String md5;
/**
* 图文消息,一个图文消息支持1到8条图文
*/
private List<NewArticle> articles;

public String toJson() {
JsonObject messageJson = new JsonObject();
messageJson.addProperty("msgtype", this.getMsgType());

switch (this.getMsgType()) {
case TEXT: {
JsonObject text = new JsonObject();
JsonArray uidJsonArray = new JsonArray();
JsonArray mobileJsonArray = new JsonArray();

text.addProperty("content", this.getContent());

if (this.getMentionedList() != null) {
for (String item : this.getMentionedList()) {
uidJsonArray.add(item);
}
}
if (this.getMentionedMobileList() != null) {
for (String item : this.getMentionedMobileList()) {
mobileJsonArray.add(item);
}
}
text.add("mentioned_list", uidJsonArray);
text.add("mentioned_mobile_list", mobileJsonArray);
messageJson.add("text", text);
break;
}
case MARKDOWN: {
JsonObject text = new JsonObject();
text.addProperty("content", this.getContent());
messageJson.add("markdown", text);
break;
}
case IMAGE: {
JsonObject text = new JsonObject();
text.addProperty("base64", this.getBase64());
text.addProperty("md5", this.getMd5());
messageJson.add("image", text);
break;
}
case NEWS: {
JsonObject text = new JsonObject();
JsonArray array = new JsonArray();
for (NewArticle article : this.getArticles()) {
JsonObject articleJson = new JsonObject();
articleJson.addProperty("title", article.getTitle());
articleJson.addProperty("description", article.getDescription());
articleJson.addProperty("url", article.getUrl());
articleJson.addProperty("picurl", article.getPicUrl());
array.add(articleJson);
}
text.add("articles", array);
messageJson.add("news", text);
break;
}
default:

}

return messageJson.toString();
}
}
Loading

0 comments on commit 6f95386

Please sign in to comment.