SpringBoot集成企业微信群机器人(运维报警)

之前有集成过钉钉群机器人🤖报警,这次主要是SpringBoot集成企业微信群机器人报警。

声明:
1.本篇文章不涉及任何业务数据
2.禁止转载!!!禁止转载!!!禁止转载!!!
相关文档:
企业微信群机器人配置说明

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: xucl
 */
@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:

1.2 Http客户端配置

这里用的Forest ,感觉还挺强大灵活 官方文档,如果你喜欢使用httpClient或者okHttp建议你看看forest ;如果你更喜欢RestTemplate,那就使用RestTemplate。

POM依赖

<!-- 轻量级HTTP客户端框架 -->
        <dependency>
            <groupId>com.dtflys.forest</groupId>
            <artifactId>forest-spring-boot-starter</artifactId>
            <version>1.5.3</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

发送Http

使用前请注意

在 Spring Boot 项目中调用接口
只要在Spring Boot的配置类或者启动类上加上@ForestScan注解,并在basePackages属性里填上远程>接口的所在的包名,加入@ForestScan
src/main/java/MyApp.java

@SpringBootApplication
@Configuration
@ForestScan(basePackages = "com.yoursite.client")
public class MyApp {
     ...
}

Forest 会扫描@ForestScan注解中basePackages属性指定的包下面所有的接口,然后会将符合条件的接口进行动态代理并注入到 Spring 的上下文中。

友情提示:
1.5.1以后版本可以跳过此步,不需要 @ForestScan 注解来指定扫描的包范围

发送请求的客户端

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 );
}

2.定义服务

2.1 接口

/**
 * @descriptions: 消息通知接口
 * @author: xucl
 */
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);
}

2.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: xucl
 */
@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 "";
    }

最终效果:


8c9a2cc73c4dba5cea7f024d488eb1a.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容