SpringBoot集成企業(yè)微信群機(jī)器人(運(yùn)維報(bào)警)

之前有集成過釘釘群機(jī)器人??報(bào)警慧库,這次主要是SpringBoot集成企業(yè)微信群機(jī)器人報(bào)警。

聲明:
1.本篇文章不涉及任何業(yè)務(wù)數(shù)據(jù)
2.禁止轉(zhuǎn)載2鍪取F氚濉!禁止轉(zhuǎn)載8鸸健8誓ァ!禁止轉(zhuǎn)載C型!<糜摺!
相關(guān)文檔:
企業(yè)微信群機(jī)器人配置說(shuō)明

1. 配置

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @descriptions: 配置參數(shù)
 * @author: xucl
 */
@Component
@Getter
@Setter
@ConfigurationProperties(prefix = "notice")
public class NoticeProperties {

    private String wechatKey;

    private List<String> phoneList;
}

這個(gè)key的獲取方式:群機(jī)器人配置說(shuō)明

yaml文件配置數(shù)據(jù):

notice:
  ####### 企業(yè)微信群機(jī)器人key
  wechat-key: xxxxxxxxx-xxx-xxx-xxxx-xxxxxxxxxx
  ####### 需要@的群成員手機(jī)號(hào)
  phone-list:

1.2 Http客戶端配置

這里用的Forest 莺债,感覺還挺強(qiáng)大靈活 官方文檔吗冤,如果你喜歡使用httpClient或者okHttp建議你看看forest ;如果你更喜歡RestTemplate九府,那就使用RestTemplate。

POM依賴

<!-- 輕量級(jí)HTTP客戶端框架 -->
        <dependency>
            <groupId>com.dtflys.forest</groupId>
            <artifactId>forest-spring-boot-starter</artifactId>
            <version>1.5.3</version>
        </dependency>

yaml文件配置

日志打開關(guān)閉請(qǐng)參考自己的業(yè)務(wù)需要

## 輕量級(jí)HTTP客戶端框架forest
forest:
  # 配置底層API為 okhttp3
  backend: okhttp3
  # 連接池最大連接數(shù)覆致,默認(rèn)值為500
  max-connections: 1000
  # 每個(gè)路由的最大連接數(shù)侄旬,默認(rèn)值為500
  max-route-connections: 500
  # 請(qǐng)求超時(shí)時(shí)間,單位為毫秒, 默認(rèn)值為3000
  timeout: 3000
   # 連接超時(shí)時(shí)間煌妈,單位為毫秒, 默認(rèn)值為2000
  connect-timeout: 3000
   # 請(qǐng)求失敗后重試次數(shù)儡羔,默認(rèn)為0次不重試
  retry-count: 1
   # 單向驗(yàn)證的HTTPS的默認(rèn)SSL協(xié)議,默認(rèn)為SSLv3
  ssl-protocol: SSLv3
   # 打開或關(guān)閉日志璧诵,默認(rèn)為true
  logEnabled: true
   # 打開/關(guān)閉Forest請(qǐng)求日志(默認(rèn)為 true)
  log-request: true
   # 打開/關(guān)閉Forest響應(yīng)狀態(tài)日志(默認(rèn)為 true)
  log-response-status: true
   # 打開/關(guān)閉Forest響應(yīng)內(nèi)容日志(默認(rèn)為 false)
  log-response-content: true

發(fā)送Http

使用前請(qǐng)注意

在 Spring Boot 項(xiàng)目中調(diào)用接口
只要在Spring Boot的配置類或者啟動(dòng)類上加上@ForestScan注解汰蜘,并在basePackages屬性里填上遠(yuǎn)程>接口的所在的包名,加入@ForestScan
src/main/java/MyApp.java

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

Forest 會(huì)掃描@ForestScan注解中basePackages屬性指定的包下面所有的接口之宿,然后會(huì)將符合條件的接口進(jìn)行動(dòng)態(tài)代理并注入到 Spring 的上下文中族操。

友情提示:
1.5.1以后版本可以跳過此步,不需要 @ForestScan 注解來(lái)指定掃描的包范圍

發(fā)送請(qǐng)求的客戶端

public interface NoticeClient {


    /**
     * 企業(yè)微信機(jī)器人 發(fā)送 https 請(qǐng)求
     *
     * @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.定義服務(wù)

2.1 接口

/**
 * @descriptions: 消息通知接口
 * @author: xucl
 */
public interface NoticeService {

    /**
     * 發(fā)送錯(cuò)誤信息至群機(jī)器人
     * @param throwable
     * @param msg
     */
    void sendError(Throwable throwable,String msg);

    /**
     * 發(fā)送文本信息至群機(jī)器人
     * @param msg
     */
    void sendByMd(String msg);

    /**
     * 發(fā)送md至群機(jī)器人
     * @param msg 文本消息
     * @param isAtALL  是否@所有人 true是 false否
     */
    void sendByText(String msg,boolean isAtALL);
}

2.2 工具實(shí)現(xiàn)

注意:這里的包名(com.github)填自己項(xiàng)目?jī)?nèi)的包名



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: 通知實(shí)現(xiàn)
 * @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;

    /**
     * 發(fā)送錯(cuò)誤信息至群機(jī)器人
     *
     * @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 ? "出現(xiàn)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);
    }

    /**
     * 發(fā)送文本信息至群機(jī)器人
     *
     * @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-發(fā)送文本消息異常 body:{}",msg,e);
        }
    }

    /**
     * 發(fā)送md至群機(jī)器人
     *
     * @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-發(fā)送文本消息異常 body:{}",msg,e);
        }
    }

    /**
     * 構(gòu)建發(fā)送文本消息格式的參數(shù)
     * @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;
    }

    /**
     * 構(gòu)建發(fā)送markdown消息格式的參數(shù)
     *
     * @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)填自己項(xiàng)目?jī)?nèi)的包名
            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#### 應(yīng)用:%s\n#### 環(huá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 內(nèi)容或問題
     * @param charNumber 長(zhǎng)度
     * @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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市色难,隨后出現(xiàn)的幾起案子泼舱,更是在濱河造成了極大的恐慌,老刑警劉巖枷莉,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娇昙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡笤妙,警方通過查閱死者的電腦和手機(jī)冒掌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蹲盘,“玉大人股毫,你說(shuō)我怎么就攤上這事」枷蓿” “怎么了皇拣?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)薄嫡。 經(jīng)常有香客問我氧急,道長(zhǎng),這世上最難降的妖魔是什么毫深? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任吩坝,我火速辦了婚禮,結(jié)果婚禮上哑蔫,老公的妹妹穿的比我還像新娘钉寝。我一直安慰自己,他們只是感情好闸迷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布嵌纲。 她就那樣靜靜地躺著,像睡著了一般腥沽。 火紅的嫁衣襯著肌膚如雪逮走。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天今阳,我揣著相機(jī)與錄音师溅,去河邊找鬼。 笑死盾舌,一個(gè)胖子當(dāng)著我的面吹牛墓臭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播妖谴,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼窿锉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起榆综,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤妙痹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后鼻疮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體怯伊,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡真慢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年包各,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了巡扇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痛黎。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡适瓦,死狀恐怖屋摔,靈堂內(nèi)的尸體忽然破棺而出始赎,到底是詐尸還是另有隱情邦泄,我是刑警寧澤迹炼,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布砸彬,位于F島的核電站,受9級(jí)特大地震影響斯入,放射性物質(zhì)發(fā)生泄漏砂碉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一刻两、第九天 我趴在偏房一處隱蔽的房頂上張望增蹭。 院中可真熱鬧,春花似錦磅摹、人聲如沸滋迈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)饼灿。三九已至,卻和暖如春帝美,著一層夾襖步出監(jiān)牢的瞬間赔退,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工证舟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人窗骑。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓女责,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親创译。 傳聞我的和親對(duì)象是個(gè)殘疾皇子抵知,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容