本次要做的东西需要满足如下要求
OK,先简单介绍一下企业微信群聊机器人
最近发现企业微信的robot特别好用,可以用较为简便的方式推送消息,甚至可以将机器人加入不同的群聊,灵活推送各类消息。完成一些企业微信服务号完成不了的工作
《如何配置群聊机器人》
直接在群聊上点击右键即可添加“群聊机器人”
点击已添加的机器人可看到webhook地址
通过使用说明,可以推送不同种类的消息到群聊内
package demo.service.wechat.inter;
import demo.common.ResultDTO;
/**
* MessageSendService
* 企业微信:微信消息推送接口
*
* @author John Chen
* @since 2019/12/12
*/
public interface MessageSendService<T> {
/**
* 推送消息
*
* @param msg 消息体
* @return 返回推送结果
*/
ResultDTO<String> sendMassage(T msg);
}
package demo.service.wechat.inter;
import demo.model.wechat.EnterpriseRobotMessageDO;
/**
* EnterpriceWechatRobotMessageSendService
* 企业微信机器人消息发送接口
*
* @author John Chen
* @since 2019/12/13
*/
public interface EnterpriceWechatRobotMessageSendService extends MessageSendService<EnterpriseRobotMessageDO> {
}
package demo.service.wechat.impl;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import demo.common.ResultDTO;
import demo.common.enumlibrary.skynet.EnumSkynetLogModule;
import demo.common.enumlibrary.skynet.category.EnumSkynetCategoryWechatMessageSend;
import demo.common.myexception.IllegalInputVariableException;
import demo.common.utls.OkHttp3Utils;
import demo.common.utls.SkynetUtils;
import demo.model.wechat.EnterpriseRobotMessageDO;
import demo.service.wechat.inter.EnterpriceWechatRobotMessageSendService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
/**
* EnterpriseWeChatRobotMessageSendServiceImpl
* 企业微信机器人消息推送接口
*
* @author John Chen
* @since 2019/12/12
*/
@Slf4j
public class EnterpriseWeChatRobotMessageSendServiceImpl implements EnterpriceWechatRobotMessageSendService {
private final static String MODULE = EnumSkynetLogModule.WECHAT_MESSAGE_SEND.getName();
private final static String CATEGORY = EnumSkynetCategoryWechatMessageSend.ENTERPRISE_ROBOT.getName();
/**
* 企业微信robot推送地址。在构建的时候传进来
*/
private final String defaultPushUrl;
/**
* 企业微信robot推送任务名称,无实际逻辑用途,仅用于记录日志
*/
private final String jobName;
private final Gson gson;
/**
* 企业微信机器人推送地址
*/
private final String wxEnterpriseRobotPushUrl;
private static final String ERR_CODE_KEY = "errcode";
private static final int ERR_CODE_SUCCESS_VALUE = 0;
private static final String ERR_MSG_KEY = "errmsg";
/**
* OkHttp3实例
*/
private OkHttp3Utils okHttp3Utils = new OkHttp3Utils();
/**
* 构造方法
*
* @param defaultPushUrl 默认消息推送地址
* @param jobName 名称
* @param gson gson
* @param wxEnterpriseRobotPushUrl 企业微信消息推送地址,参考地址:https://qyapi.weixin.qq.com/cgi-bin/webhook/send
*/
public EnterpriseWeChatRobotMessageSendServiceImpl(String defaultPushUrl, String jobName, Gson gson, String wxEnterpriseRobotPushUrl) {
this.defaultPushUrl = defaultPushUrl;
this.jobName = jobName;
this.gson = gson;
this.wxEnterpriseRobotPushUrl = wxEnterpriseRobotPushUrl;
}
/**
* 构造方法
*
* @param defaultPushUrl 默认消息推送地址
* @param jobName 名称
* @param gson gson
*/
public EnterpriseWeChatRobotMessageSendServiceImpl(String defaultPushUrl, String jobName, Gson gson) {
this.defaultPushUrl = defaultPushUrl;
this.jobName = jobName;
this.gson = gson;
this.wxEnterpriseRobotPushUrl = null;
}
/**
* 推送消息
*
* @param msg 消息体
* @return 返回推送结果
*/
@Override
public ResultDTO<String> sendMassage(EnterpriseRobotMessageDO msg) {
try {
String msgStr = gson.toJson(msg);
log.info("收到企业微信推送请求,job:{};开始推送消息:{}", jobName, msgStr);
String pushUrl = getPushUrl(msg);
log.debug("推送地址:{}", pushUrl);
//推送消息
String resultStr = okHttp3Utils.post(pushUrl, msgStr);
//记录结果并返回
log.info("推送结果:{}", resultStr);
JSONObject resultJson = JSONObject.parseObject(resultStr);
boolean success = resultJson.getInteger(ERR_CODE_KEY) == ERR_CODE_SUCCESS_VALUE;
String resMsg = resultJson.getString(ERR_MSG_KEY);
if (!success) {
SkynetUtils.printError(String.format("消息推送失败:%s", resMsg), MODULE, CATEGORY, "消息推送失败", jobName, "", null);
return new ResultDTO<>(success, "发送消息成功", resultJson.getString(ERR_CODE_KEY), resMsg);
}
return new ResultDTO<>(success, "发送消息成功", "200", resMsg);
} catch (Exception e) {
String errMsg = String.format("消息推送异常:%s", e.getMessage());
SkynetUtils.printError(errMsg, MODULE, CATEGORY, "消息推送异常", jobName, "", e);
return new ResultDTO<>(false, "消息发送异常", "999", errMsg);
}
}
/**
* 获取推送的url地址(带key参数)
*
* @param msg 入参
* @return 返回地址;如果无法组成地址,则返回null
*/
private String getPushUrl(EnterpriseRobotMessageDO msg) {
String key = msg.getRobotKey();
if (!StringUtils.isEmpty(key) && !StringUtils.isEmpty(wxEnterpriseRobotPushUrl)) {
final String urlKey = "key";
return wxEnterpriseRobotPushUrl + "?" + urlKey + "=" + key;
} else if (!StringUtils.isEmpty(defaultPushUrl)) {
return defaultPushUrl;
} else {
throw new IllegalInputVariableException("入参中key为空或企业微信机器人推送地址为空,且任务未配置默认推送地址,无法组合出推送地址,请检查代码!");
}
}
}
package demo.model.wechat;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* EnterpriseRobotMessageDO
* 企业微信机器人消息推送实体
*
* @author John Chen
* @since 2019/12/12
*/
@ToString
public class EnterpriseRobotMessageDO {
public final static String MSG_TYPE_TEXT = "text";
public final static String MSG_TYPE_MARKDOWN = "markdown";
public final static String MSG_TYPE_IMAGE = "image";
public final static String MSG_TYPE_NEWS = "news";
/**
* 推送robot key(不填则由实现决定如何处理)
* 优先使用pushKey
*/
@Getter
private String robotKey;
/**
* 消息类型枚举
*/
private String msgtype;
//region 不同消息类型用到的不同字段。每次仅需要实例化1个即可
/**
* type=text时需要去构建的实体
*/
private TextType text;
/**
* type=markdown时需要去构建的实体
*/
private MarkdownType markdown;
/**
* type=image时需要去构建的实体
*/
private ImageType image;
/**
* type=news时需要去构建的实体
*/
private NewsType news;
//endregion
/**
* 构建一个Text类型消息实体Builder
*
* @param content 消息内容
* @return 返回builder
*/
public static TextBuilder textBuilder(String content) {
return new TextBuilder(content);
}
/**
* 构建一个Markdown类型消息实体
*
* @param content 消息内容(Markdown格式)
* @return 返回builder
*/
public static MarkdownBuilder markdownBuilder(String content) {
return new MarkdownBuilder(content);
}
/**
* 构建一个Image类型消息实体
*
* @param base64 图片内容的base64编码;无需增加类似data:image/png;base64,的头。这一点要注意,因为在线转换工具大多会带上这个前缀
* @param md5 图片内容(base64编码前)的md5值;
* @return 返回builder
*/
public static ImageBuilder imageBuilder(String base64, String md5) {
return new ImageBuilder(base64, md5);
}
/**
* 构建一个news类型消息实体
*
* @param title 标题,不超过128个字节,超过会自动截断
* @param url 点击后跳转的链接。
* @param description 描述,不超过512个字节,超过会自动截断 非必填
* @param picUrl 图文消息的图片链接,支持JPG、PNG格式,较好的效果为大图 1068*455,小图150*150。 非必填
* @return 返回builder
*/
public static NewsBuilder newsBuilder(String title, String url, String description, String picUrl) {
return new NewsBuilder(title, url, description, picUrl);
}
public static NewsBuilder newsBuilder(String title, String url, String description) {
return new NewsBuilder(title, url, description, null);
}
public static NewsBuilder newsBuilder(String title, String url) {
return new NewsBuilder(title, url, null, null);
}
//region 消息实体类
@AllArgsConstructor
private static class TextType {
/**
* 文本内容,最长不超过2048个字节,必须是utf8编码
*/
private String content;
/**
* userid的列表,提醒群中的指定成员(@某个成员),@all表示提醒所有人,如果开发者获取不到userid,可以使用mentioned_mobile_list
*/
private List<String> mentioned_list;
/**
* 手机号列表,提醒手机号对应的群成员(@某个成员),@all表示提醒所有人
*/
private List<String> mentioned_mobile_list;
}
@AllArgsConstructor
private static class MarkdownType {
/**
* markdown内容,最长不超过4096个字节,必须是utf8编码
*/
private String content;
}
@AllArgsConstructor
private static class ImageType {
/**
* 图片内容的base64编码
*/
private String base64;
/**
* 图片内容(base64编码前)的md5值
*/
private String md5;
}
@AllArgsConstructor
private static class NewsType {
/**
* 图文消息,一个图文消息支持1到8条图文
*/
private List<Article> articles;
/**
* 图文消息实体
*/
@AllArgsConstructor
private static class Article {
/**
* 标题,不超过128个字节,超过会自动截断
*/
private String title;
/**
* 描述,不超过512个字节,超过会自动截断
* 非必填
*/
private String description;
/**
* 点击后跳转的链接。
*/
private String url;
/**
* 图文消息的图片链接,支持JPG、PNG格式,较好的效果为大图 1068*455,小图150*150。
* 非必填
*/
private String picurl;
}
}
//endregion
//region 各类构造方法,用于构建不同的消息类型实体
private EnterpriseRobotMessageDO(NewsType news) {
this.msgtype = MSG_TYPE_NEWS;
this.news = news;
}
private EnterpriseRobotMessageDO(NewsType news, String robotKey) {
this.msgtype = MSG_TYPE_NEWS;
this.news = news;
this.robotKey = robotKey;
}
private EnterpriseRobotMessageDO(ImageType image) {
this.msgtype = MSG_TYPE_IMAGE;
this.image = image;
}
private EnterpriseRobotMessageDO(ImageType image, String robotKey) {
this.msgtype = MSG_TYPE_IMAGE;
this.image = image;
this.robotKey = robotKey;
}
private EnterpriseRobotMessageDO(MarkdownType markdown) {
this.msgtype = MSG_TYPE_MARKDOWN;
this.markdown = markdown;
}
private EnterpriseRobotMessageDO(MarkdownType markdown, String robotKey) {
this.msgtype = MSG_TYPE_MARKDOWN;
this.markdown = markdown;
this.robotKey = robotKey;
}
private EnterpriseRobotMessageDO(TextType text) {
this.msgtype = MSG_TYPE_TEXT;
this.text = text;
}
private EnterpriseRobotMessageDO(TextType text, String robotKey) {
this.msgtype = MSG_TYPE_TEXT;
this.text = text;
this.robotKey = robotKey;
}
//endregion
//region 不同消息类型的Builder
/**
* Text类型消息Builder
*/
public static class TextBuilder {
/**
* 当需要@all时候需要填入mentioned_list或mentioned_mobile_list中的
*/
private static final String AT_ALL = "@all";
private String content;
private List<String> mentionedList;
private List<String> mentionedMobileList;
/**
* 构造方法,消息体必填
*
* @param content 消息体
*/
private TextBuilder(String content) {
this.content = content;
}
/**
* 添加userId,用于在消息中@某人
*
* @param mentioned 企业微信userId
* @return 返回建造者本身
*/
public TextBuilder addUserIdForAt(String... mentioned) {
if (mentioned != null && mentioned.length > 0) {
if (mentionedList == null) {
mentionedList = new ArrayList<>();
}
mentionedList.addAll(Arrays.asList(mentioned));
}
return this;
}
/**
* 添加手机号,用于添加某人
* 当无法获取到userId的时候,则可以添加手机号(需要是企业微信绑定的)
*
* @param mobiles 企业微信userId
* @return 返回建造者本身
*/
public TextBuilder addMobileForAt(String... mobiles) {
if (mobiles != null && mobiles.length > 0) {
if (mentionedMobileList == null) {
mentionedMobileList = new ArrayList<>();
}
mentionedMobileList.addAll(Arrays.asList(mobiles));
}
return this;
}
public TextBuilder atAll() {
addMobileForAt(AT_ALL);
return this;
}
public EnterpriseRobotMessageDO build() {
return new EnterpriseRobotMessageDO(new TextType(content, mentionedList, mentionedMobileList));
}
public EnterpriseRobotMessageDO build(String robotKey) {
return new EnterpriseRobotMessageDO(new TextType(content, mentionedList, mentionedMobileList), robotKey);
}
}
/**
* Markdown类型消息Builder
*/
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public static class MarkdownBuilder {
/**
* markdown内容,最长不超过4096个字节,必须是utf8编码
*/
private String content;
public EnterpriseRobotMessageDO build() {
return new EnterpriseRobotMessageDO(new MarkdownType(content));
}
public EnterpriseRobotMessageDO build(String robotKey) {
return new EnterpriseRobotMessageDO(new MarkdownType(content), robotKey);
}
}
/**
* Image类型消息Builder
*/
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public static class ImageBuilder {
/**
* 图片内容的base64编码
*/
private String base64;
/**
* 图片内容(base64编码前)的md5值
*/
private String md5;
public EnterpriseRobotMessageDO build() {
return new EnterpriseRobotMessageDO(new ImageType(base64, md5));
}
public EnterpriseRobotMessageDO build(String robotKey) {
return new EnterpriseRobotMessageDO(new ImageType(base64, md5), robotKey);
}
}
/**
* News类型消息Builder
*/
public static class NewsBuilder {
/**
* 图文消息,一个图文消息支持1到8条图文
*/
private List<NewsType.Article> articles;
/**
* 构造方法
*
* @param title 标题
* @param url 跳转地址
* @param description 描述(非必填)
* @param picUrl 图片地址(非必填)
*/
private NewsBuilder(String title, String url, String description, String picUrl) {
this.articles = new ArrayList<>(Collections.singletonList(new NewsType.Article(title, description, url, picUrl)));
}
/**
* 新增一个图文
*
* @param title 标题
* @param url 跳转地址
* @param description 描述(可为空)
* @param picUrl 图片地址
* @return 返回builder
*/
public NewsBuilder addArticles(String title, String url, String description, String picUrl) {
articles.add(new NewsType.Article(title, description, url, picUrl));
return this;
}
/**
* 新增一个图文
*
* @param title 标题
* @param url 跳转地址
* @param description 描述(可为空)
* @return 返回builder
*/
public NewsBuilder addArticles(String title, String url, String description) {
return addArticles(title, url, description, null);
}
/**
* 新增一个图文
*
* @param title 标题
* @param url 跳转地址
* @return 返回builder
*/
public NewsBuilder addArticles(String title, String url) {
return addArticles(title, url, null, null);
}
public EnterpriseRobotMessageDO build() {
return new EnterpriseRobotMessageDO(new NewsType(articles));
}
public EnterpriseRobotMessageDO build(String robotKey) {
return new EnterpriseRobotMessageDO(new NewsType(articles), robotKey);
}
}
//endregion
}
这里通过几个测试Demo来说明一下使用方法
package demo.nelsen.config;
import com.google.gson.Gson;
import demo.common.enumlibrary.tccomponent.EnumAirtravelOpsMonitorDataNelsenKeys;
import demo.service.wechat.impl.EnterpriseWeChatRobotMessageSendServiceImpl;
import demo.service.wechat.inter.EnterpriceWechatRobotMessageSendService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* WechatSendMessageConfig
* 企业微信消息推送Bean
*
* @author John Chen
* @since 2019/12/13
*/
@Configuration
public class WechatSendMessageConfig {
/**
* 企业微信机器人公共推送服务
* 注意:使用这个Bean的时候,在发送时需要带上对应的机器人Key,否则会导致发送到默认机器人上
*
* @param gson gson
* @return 返回bean
* @throws Exception 获取统一配置时可能出现的错误
*/
@Bean
public EnterpriceWechatRobotMessageSendService publicWxRobotSendService(Gson gson) throws Exception {
return new EnterpriseWeChatRobotMessageSendServiceImpl("https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=8606ac1f-dc01-423s3-9d74-121343ef539", "企业微信Robot公共推送服务", gson, "https://qyapi.weixin.qq.com/cgi-bin/webhook/send");
}
}
下面的测试用例分别构建了4中不同的消息类型进行推送
package demo.nelsen.tests;
import demo.common.utls.EncodeUtils;
import demo.model.wechat.EnterpriseRobotMessageDO;
import demo.service.wechat.inter.EnterpriceWechatRobotMessageSendService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.IOException;
/**
* WechatSendMessageConfig
* 微信发送消息测试用例集
*
* @author John Chen
* @since 2019/12/13
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class WechatSendMessageTests {
@Autowired
private EnterpriceWechatRobotMessageSendService publicWxRobotSendService;
@Test
public void tiantiUploadSendTest() {
/*
文本消息类型
*/
EnterpriseRobotMessageDO messageTextDO = EnterpriseRobotMessageDO.textBuilder("nelsen测试用例中-上传动态-测试用例-test消息").addMobileForAt("13800000000").build();
assert tianTiUploadRobotSendService.sendMassage(messageTextDO).getSuccess();
/*
markdown类型消息
*/
EnterpriseRobotMessageDO messageMarkdownDo = EnterpriseRobotMessageDO.markdownBuilder(
"# 消息发送测试\n" +
">小鲜肉最牛\n" +
">消息来自上传动态-Markdown类型消息").build();
assert tianTiUploadRobotSendService.sendMassage(messageMarkdownDo).getSuccess();
/*
图片类型
*/
String imageB = "data:image/gif;base64,R0lGODlhdQAmAKIAAOYyL+rU4llg6Jmd8e92dCky4eEGAv///yH5BAAAAAAALAAAAAB1ACYAAAP/eLrc/jC2IEoZMATJu/9gyFVWIUyksIls677LUJbrEcxWDe98f+CWk4I0w/iOSNANKJQBC8mo9LEEDp8F3XR7rOIU2Cx3jHwKsUKyWqRhEEvGN3xN91BoCq8l9tTW/244Rk4mOkBGgIl8VjF+d4V5A5KKf3IWiCCEOZRraGxPnGqeIZpzoVyjDBptDpYmp1yumI9BWq5QUQS6fn+lm3lYmLdSBsW8xcjJBgAABLwfAMhXRQt6ODWuJ8rb3AYK0d3h4OHIBC7jC0TCYb/ZB+Th3/Dc4/PmLOgSvkwK+xjMAAEmCwhQnrc8rFSxGkiwnoFnEnTp0mdqlJw0DgIggwgu/08xABM+KkhGJdm9JJqMhGkyQIAATA3HxVwATuO8jyQfECiXJyYzZwzyOSilcqWEmzkP1ES6LGmDcStshjspNJAgMOwwNmBajGZXGyJVhU22quxOaR7hrahqQ52DC1jh/nj1AG0Eux3BLgu59x3SkxHO9mVLhJfcirUc2IWA96vUbvf+QkNbldCgIGCKHi6h1e/Bu1+VOiYHYAVTwBHyCfWyocoZzZovbf3ok1njzx/IltUg+GG13twMfta0gdDruLH3MOD6uaPDcI8hCAZp4/k24elmtOacGXlcPAuYe/VmnVt0ncioTyeYU+ibQRWOHyCRXEz40BDyivYWYBzQAIbTbeBUNTIZRF1QlNlVimGdfffLSPg9oJ9+vVV4H23smWSgFqrZZc0AWiTWACMXcjAhfgAqgxpXKwDXDXbZsSPjVcth6NNtZj3nzCpM1dDfPDACM+OQ9l2I1HiPlfNceentMoGLygQp5Eq3aKfYaUgOdBIB9RyYSEsmvASiFgG0RMRLEK2RAAA7";
EnterpriseRobotMessageDO messageImageDo = EnterpriseRobotMessageDO.imageBuilder(
imageB
, EncodeUtils.md5bytes(EncodeUtils.decodeBase64(imageB))
).build();
assert tianTiUploadRobotSendService.sendMassage(messageImageDo).getSuccess();
/*
图文消息类型
*/
EnterpriseRobotMessageDO messageNewsDO = EnterpriseRobotMessageDO.newsBuilder("上传动态-news类型消息-跳转"
, "https://www.baidu.com/"
, "上传动态-news类型消息-测试用例1"
, "http://img1.imgtn.bdimg.com/it/u=3334640638,1744228669&fm=26&gp=0.jpg")
.addArticles("上传动态-news类型消息-跳转TCSchedule"
, "https://www.baidu.com/"
, "上传动态-news类型消息-测试用例2"
, "http://img1.imgtn.bdimg.com/it/u=3334640638,1744228669&fm=26&gp=0.jpg")
.build();
assert tianTiUploadRobotSendService.sendMassage(messageNewsDO).getSuccess();
}
}