之前有集成过钉钉群机器人🤖报警,这次主要是SpringBoot集成企业微信群机器人报警,主要使用两种方式调用企业微信的HttpApi,一种为Forest,一种为RestTemplate
相关文档:
1.配置参数
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @descriptions: 配置参数
* @author: xucux
*/
@Component
@Getter
@Setter
@ConfigurationProperties(prefix = "notice")
public class NoticeProperties {
private String wechatKey;
private List<String> phoneList;
}
这个key的获取方式:群机器人配置说明
yaml文件配置数据:
notice:
####### 企业微信群机器人key
wechat-key: xxxxxxxxx-xxx-xxx-xxxx-xxxxxxxxxx
####### 需要@的群成员手机号
phone-list:
2.Forest的Http客户端配置
使用Forest
这里用的Forest ,感觉还挺强大灵活 官方文档,如果你喜欢使用httpClient或者okHttp建议你看看forest ;如果你更喜欢RestTemplate,那就使用RestTemplate。
POM依赖
<!-- 轻量级HTTP客户端框架 -->
<dependency>
<groupId>com.dtflys.forest</groupId>
<artifactId>forest-spring-boot-starter</artifactId>
<version>1.5.28</version>
</dependency>
yaml文件配置
日志打开关闭请参考自己的业务需要
## 轻量级HTTP客户端框架forest
forest:
# 配置底层API为 okhttp3
backend: okhttp3
# 连接池最大连接数,默认值为500
max-connections: 1000
# 每个路由的最大连接数,默认值为500
max-route-connections: 500
# 请求超时时间,单位为毫秒, 默认值为3000
timeout: 3000
# 连接超时时间,单位为毫秒, 默认值为2000
connect-timeout: 3000
# 请求失败后重试次数,默认为0次不重试
retry-count: 1
# 单向验证的HTTPS的默认SSL协议,默认为SSLv3
ssl-protocol: SSLv3
# 打开或关闭日志,默认为true
logEnabled: true
# 打开/关闭Forest请求日志(默认为 true)
log-request: true
# 打开/关闭Forest响应状态日志(默认为 true)
log-response-status: true
# 打开/关闭Forest响应内容日志(默认为 false)
log-response-content: true
发送请求
public interface NoticeClient {
/**
* 企业微信机器人 发送 https 请求
*
* @param keyValue
* @return
*/
@Post(
url = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key={keyValue}",
headers = {
"Accept-Charset: utf-8",
"Content-Type: application/json"
},
dataType = "json"
)
ForestResponse<JsonObject> weChatNotice(@Var("keyValue") String keyValue, @JSONBody Map<String, Object> body );
}
3.定义服务
3.1 接口
/**
* @descriptions: 消息通知接口
* @author: xucux
*/
public interface NoticeService {
/**
* 发送错误信息至群机器人
* @param throwable
* @param msg
*/
void sendError(Throwable throwable,String msg);
/**
* 发送文本信息至群机器人
* @param msg
*/
void sendByMd(String msg);
/**
* 发送md至群机器人
* @param msg 文本消息
* @param isAtALL 是否@所有人 true是 false否
*/
void sendByText(String msg,boolean isAtALL);
}
3.2 服务实现
注意:这里的包名(com.github)填自己项目内的包名
import com.dtflys.forest.http.ForestResponse;
import com.google.gson.JsonObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* @descriptions: 通知实现
* @author: xucux
*/
@Service
@Slf4j
public class NoticeServiceImpl implements NoticeService {
@Value("${spring.application.name}")
private String appName;
@Value("${spring.profiles.active}")
private String env;
@Autowired
private NoticeClient noticeClient;
@Autowired
private NoticeProperties noticeProperties;
/**
* 发送错误信息至群机器人
*
* @param throwable
* @param msg
*/
@Override
public void sendError(Throwable throwable, String msg) {
String errorClassName = throwable.getClass().getSimpleName();
if (StringUtils.isBlank(msg)){
msg = throwable.getMessage() == null ? "出现null值" : throwable.getMessage();
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
throwable.printStackTrace(new PrintStream(baos));
String bodyStr = StringComUtils.limitStrNone(regexThrowableStr(baos.toString()),450);
String md = getMdByTemplate(appName, env, errorClassName, msg, bodyStr);
sendByMd(md);
}
/**
* 发送文本信息至群机器人
*
* @param msg
*/
@Override
public void sendByMd(String msg) {
try {
Map<String, Object> params = buildMdParams(msg);
ForestResponse<JsonObject> response = noticeClient.weChatNotice(noticeProperties.getWechatKey(), params);
log.debug("WeChatRobo-Send Error:{} Status:{}",response.isError(),response.getStatusCode());
} catch (Exception e) {
log.error("WeChatRobot-发送文本消息异常 body:{}",msg,e);
}
}
/**
* 发送md至群机器人
*
* @param msg
*/
@Override
public void sendByText(String msg,boolean isAtALL) {
try {
Map<String, Object> params = buildTextParams(msg,noticeProperties.getPhoneList(),isAtALL);
ForestResponse<JsonObject> response = noticeClient.weChatNotice(noticeProperties.getWechatKey(), params);
log.debug("WeChatRobo-Send Error:{} Status:{}",response.isError(),response.getStatusCode());
} catch (Exception e) {
log.error("WeChatRobot-发送文本消息异常 body:{}",msg,e);
}
}
/**
* 构建发送文本消息格式的参数
* @param phoneList @群用户
* @param isAtALL 是否@所有人
* @return
*/
private Map<String,Object> buildTextParams(String text, List<String> phoneList, boolean isAtALL){
Map<String,Object> params = new HashMap<>();
Map<String,Object> data = new HashMap<>();
data.put("content",text);
if (isAtALL){
phoneList.add("@all");
}
if (CollectionUtils.isNotEmpty(phoneList)){
data.put("mentioned_mobile_list", phoneList);
}
params.put("msgtype","text");
params.put("text",data);
return params;
}
/**
* 构建发送markdown消息格式的参数
*
* @param md
* @return
*/
private Map<String,Object> buildMdParams(String md){
Map<String,Object> params = new HashMap<>();
Map<String,Object> data = new HashMap<>();
data.put("content",md);
params.put("msgtype","markdown");
params.put("markdown",data);
return params;
}
private String regexThrowableStr(String str){
try {
// 注意:这里的包名(com.github)填自己项目内的包名
String pattern = "(com)(\\.)(github)(.{10,200})(\\))";
Pattern r = Pattern.compile(pattern);
Matcher m=r.matcher(str);
List<String> list = new ArrayList<>();
while (m.find()) {
list.add(m.group());
}
if (CollectionUtils.isEmpty(list)){
return str;
}
String s = list.stream().collect(Collectors.joining("\n"));
return s;
} catch (Exception e) {
return str;
}
}
private String getMdByTemplate(String appName,String env,String errorClassName,String msg,String bodyStr){
String titleTpl = "### 异常告警通知\n#### 应用:%s\n#### 环境:<font color=\"info\">%s</font>\n##### 异常:<font color=\"warning\">%s</font>\n";
String bodyTpl = "\nMsg:%s\nDetail:\n>%s";
String footerTpl = "\n<font color=\"comment\">%s</font>";
String title = String.format(titleTpl, appName, env, errorClassName);
String body = String.format(bodyTpl, msg,bodyStr);
String dateStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"));
String footer = String.format(footerTpl, dateStr);
return title.concat(body).concat(footer);
}
}
使用到的工具方法:
/**
* 限制文本描述
*
* @param content 内容或问题
* @param charNumber 长度
* @return
*/
public static String limitStrNone(String content ,int charNumber){
if (StringUtils.isNotBlank(content)){
if (content.length() > charNumber){
String substring = content.substring(0, charNumber);
return substring;
}else {
return content;
}
}
return "";
}
4.群接收消息的效果
5.使用RestTemplate发送请求时
配置RestTemplate
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import java.util.Collections;
/**
* @descriptions: RestTemplate配置
* @author: xucux
*/
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory,){
RestTemplate restTemplate = new RestTemplate(factory);
//restTemplate.setInterceptors(Collections.singletonList(restLogInterceptor)); // 这里可以注入自定义拦截器
return restTemplate;
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
// 单位为ms
factory.setReadTimeout(10000);
// 单位为ms
factory.setConnectTimeout(10000);
return factory;
}
}
重新在写一个NoticeClient,其中还有一个方法是介绍RestTemplate如何发送文件
import com.alibaba.fastjson.JSONObject;
import com.sztb.component.exception.BaseAssert;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
/**
* @descriptions: 发送给企业微信机器人
* @author: xucux
*
* https://work.weixin.qq.com/api/doc/90000/90136/91770
*/
public class NoticeClient {
@Autowired
private RestTemplate restTemplate;
public static final String SEND_MESSAGE_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key={weChatKey}";
public static final String UPLOAD_FILE_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key={weChatKey}&type=file";
public ResponseEntity<JSONObject> weChatNotice(String weChatKey, Map<String, Object> body ){
BaseAssert.verify(StringUtils.isBlank(weChatKey),"消息推送给机器人失败,未配置:weChatKey");
ResponseEntity<JSONObject> response = restTemplate.postForEntity(SEND_MESSAGE_URL, body, JSONObject.class,weChatKey);
return response;
}
public ResponseEntity<JSONObject> uploadMedia(String weChatKey, String fileName, InputStream inputStream){
BaseAssert.verify(StringUtils.isAnyBlank(weChatKey,fileName),"文件推送企业微信失败,未配置:weChatKey,fileName");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
// 新建流资源,必须重写contentLength()和getFilename()
Resource resource = new InputStreamResource(inputStream){
//文件长度,单位字节
@Override
public long contentLength() throws IOException {
long size = inputStream.available();
return size;
}
//文件名
@Override
public String getFilename(){
return fileName;
}
};
body.add("media",resource);
HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity(body, headers);
ResponseEntity<JSONObject> response = restTemplate.postForEntity(UPLOAD_FILE_URL, entity, JSONObject.class,weChatKey);
return response;
}
}