最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot AOP實現(xiàn)釘釘+企業(yè)微信雙渠道異常告警

 更新時間:2026年04月03日 09:33:09   作者:謝正元  
本文主要介紹了SpringBoot AOP實現(xiàn)釘釘+企業(yè)微信雙渠道異常告警,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

前言

在微服務/分布式系統(tǒng)中,接口異常、MQ消費失敗是高頻故障點。若依賴人工排查,會導致故障響應滯后、影響業(yè)務可用性。本文基于 Spring AOP 實現(xiàn)無侵入式異常告警,支持 釘釘機器人 + 企業(yè)微信機器人 雙渠道切換,自動捕獲:

  1. Controller 層非業(yè)務異常
  2. RocketMQ 消費失?。ㄟ_到指定重試次數(shù))

并實時推送告警消息,支持環(huán)境區(qū)分、@指定人、配置動態(tài)刷新,完全適配生產(chǎn)環(huán)境使用。

一、技術方案設計

核心目標

  1. 無侵入:通過 AOP 切面攔截,不污染業(yè)務代碼
  2. 雙渠道:釘釘/企業(yè)微信自由切換,支持關閉告警
  3. 精準告警:Controller 過濾業(yè)務異常,MQ 僅在最大重試次數(shù)告警
  4. 生產(chǎn)可用:異步發(fā)送告警、配置動態(tài)刷新、日志完整、消息長度截斷

技術棧

  • Spring Boot + Spring AOP
  • RocketMQ (MQ消費告警)
  • 釘釘/企業(yè)微信 群機器人 WebHook
  • 異步線程池(避免告警阻塞主流程)
  • Nacos 配置動態(tài)刷新

二、完整優(yōu)化代碼(帶詳細注釋)

1. 核心配置類(告警參數(shù)動態(tài)配置)

負責加載釘釘/企微機器人配置,支持 @RefreshScope 動態(tài)刷新(Nacos/Apollo)

package com.xm.kite.tms.common.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.RefreshScope;
import org.springframework.context.annotation.Configuration;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
 * TMS業(yè)務通用配置類
 * 核心作用:加載釘釘/企微告警機器人配置,支持配置中心動態(tài)刷新
 */
@Configuration
@RefreshScope
public class TmsBusinessConfig implements Serializable {
    @Serial
    private static final long serialVersionUID = 2911611355174552504L;
    /**
     * 當前運行環(huán)境(dev/test/prod)
     */
    @Value("${spring.profiles.active}")
    private String env;
    // ====================== 釘釘機器人配置 ======================
    /**
     * 釘釘機器人WebHook請求地址(占位符替換token)
     */
    @Value("${ding.ding.robot.webhook.url:https://oapi.dingtalk.com/robot/send?access_token=%s}")
    private String dingDingRobotWebhookUrl;
    /**
     * 釘釘機器人訪問令牌
     */
    @Value("${ding.ding.robot.webhook.access_token:}")
    private String dingDingRobotWebhookAccessToken;
    /**
     * 釘釘告警@指定人手機號(多個用英文逗號分隔)
     */
    @Value("${ding.ding.robot.webhook.atMobiles:}")
    private String dingDingRobotWebhookAtMobiles;
    // ====================== 通用告警通道配置 ======================
    /**
     * 告警通道開關:0-關閉 1-釘釘 2-企業(yè)微信
     */
    @Value("${robot.webhook.channel:2}")
    private Integer robotWebhookChannel;
    // ====================== 企業(yè)微信機器人配置 ======================
    /**
     * 企微機器人WebHook請求地址(占位符替換key)
     */
    @Value("${qy.weiXin.robot.webhook.url:https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=%s}")
    private String qyWeiXinRobotWebhookUrl;
    /**
     * 企微機器人密鑰
     */
    @Value("${qy.weiXin.robot.webhook.key:}")
    private String qyWeiXinRobotWebhookKey;
    /**
     * 企微告警@指定人賬號(多個用英文逗號分隔)
     */
    @Value("#{'${qy.weiXin.robot.webhook.at_user_ids:}'.split(',')}")
    private List<String> qyWeiXinRobotWebhookAtUserIds;
    /**
     * 企微運營數(shù)據(jù)播報專用WebHook地址
     */
    @Value("${qy.weiXin.robot.tmsdata.url:}")
    private String qyWeiXinTMSDatakUrl;
    // ====================== getter ======================
    public String getEnv() {return env;}
    public String getDingDingRobotWebhookUrl() {return dingDingRobotWebhookUrl;}
    public String getDingDingRobotWebhookAccessToken() {return dingDingRobotWebhookAccessToken;}
    public String getDingDingRobotWebhookAtMobiles() {return dingDingRobotWebhookAtMobiles;}
    public Integer getRobotWebhookChannel() {return robotWebhookChannel;}
    public String getQyWeiXinRobotWebhookUrl() {return qyWeiXinRobotWebhookUrl;}
    public String getQyWeiXinRobotWebhookKey() {return qyWeiXinRobotWebhookKey;}
    public List<String> getQyWeiXinRobotWebhookAtUserIds() {return qyWeiXinRobotWebhookAtUserIds;}
    public String getQyWeiXinTMSDatakUrl() {return qyWeiXinTMSDatakUrl;}
}

2. AOP 異常攔截切面(核心)

無侵入攔截 Controller層MQ消費層 異常,過濾業(yè)務異常,觸發(fā)告警

package com.xm.kite.tms.common.config.aop;
import com.alibaba.fastjson.JSON;
import com.xm.kite.tms.common.config.TmsBusinessConfig;
import com.xm.kite.tms.pda.util.DingDingUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.rocketmq.common.message.MessageExt;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.hbhk.hms.mq.tx.mq.MqMsgVo;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
/**
 * 系統(tǒng)異常告警AOP切面
 * 核心功能:
 * 1. 攔截Controller層所有非業(yè)務異常,推送告警
 * 2. 攔截MQ消費異常,達到最大重試次數(shù)后推送告警
 */
@Aspect
@Component
@Slf4j
public class AopFailNoticeHandler {
    @Resource
    private DingDingUtils dingDingUtils;
    @Resource
    private TmsBusinessConfig tmsBusinessConfig;
    // ====================== 切點定義 ======================
    /**
     * 切點:PDA模塊所有Controller
     */
    @Pointcut("within(com.xm.kite.tms.pda.controller..*)")
    protected void aopFailNotice() {}
    /**
     * 切點:VMS模塊所有Controller
     */
    @Pointcut("within(com.xm.kite.tms.vms.controller..*)")
    protected void aopVmsFailNotice() {}
    /**
     * 切點:所有MQ消費類
     */
    @Pointcut("within(com.xm.kite.tms.pda.mq..*)")
    protected void mqFailNotice() {}
    // ====================== Controller異常攔截 ======================
    /**
     * 環(huán)繞通知:攔截Controller層所有方法異常
     * 過濾業(yè)務異常,僅推送系統(tǒng)異常告警
     */
    @Around("aopFailNotice() || aopVmsFailNotice()")
    public Object controllerFailNoticeAround(ProceedingJoinPoint jp) throws Throwable {
        try {
            // 執(zhí)行目標方法
            return jp.proceed();
        } catch (Throwable e) {
            // 獲取異常方法全路徑
            String targetClassName = jp.getTarget().getClass().getName();
            String methodName = ((MethodSignature) jp.getSignature()).getName();
            String fullMethodName = targetClassName + "." + methodName;
            // 獲取請求參數(shù)
            String params = JSON.toJSONString(jp.getArgs());
            // 過濾:業(yè)務異常不發(fā)送告警
            if (!(e instanceof BusinessException || e instanceof BizException)) {
                log.error("【Controller異?!糠椒ǎ簕},參數(shù):{}", fullMethodName, params, e);
                // 截取異常堆棧(避免消息過長)
                String errorStack = StringUtils.left(ExceptionUtils.getStackTrace(e), 500);
                // 發(fā)送雙渠道告警
                dingDingUtils.buildMsg(errorStack, fullMethodName, params);
            }
            // 拋出異常,不改變原有異常流程
            throw e;
        }
    }
    // ====================== MQ消費異常攔截 ======================
    /**
     * 環(huán)繞通知:攔截MQ消費方法異常
     * 達到指定重試次數(shù)后發(fā)送告警
     */
    @Around("mqFailNotice()")
    public Object mqConsumerFailNoticeAround(ProceedingJoinPoint joinPoint) throws Throwable {
        // 解析MQ消息參數(shù)
        Object[] args = joinPoint.getArgs();
        int reconsumeTimes = 0;
        String messageId = "";
        MessageExt messageExt = null;
        MqMsgVo mqMsgVo = null;
        // 解析消息體、重試次數(shù)、消息ID
        if (args != null) {
            // 解析RocketMQ原生消息
            if (args[0] instanceof MessageExt ext) {
                messageExt = ext;
                reconsumeTimes = ext.getReconsumeTimes() + 1;
                messageId = ext.getProperty("UNIQ_KEY");
            }
            // 解析自定義MQ消息體
            if (args.length > 1 && args[1] instanceof MqMsgVo vo) {
                mqMsgVo = vo;
            }
        }
        try {
            // 執(zhí)行MQ消費邏輯
            return joinPoint.proceed();
        } catch (Exception e) {
            log.info("【MQ消費失敗】次數(shù):{},消息ID:{}", reconsumeTimes, messageId);
            // 自定義最大告警次數(shù):消費失敗5次觸發(fā)告警
            final int MAX_RETRY_TIMES = 5;
            if (reconsumeTimes == MAX_RETRY_TIMES) {
                log.error("【MQ消費告警】達到最大重試次數(shù),消息ID:{}", messageId);
                String errorMsg = StringUtils.left(e.getMessage(), 200);
                // 發(fā)送MQ消費失敗告警
                dingDingUtils.buildAndSendDingAlarmRobotMsg(messageExt, mqMsgVo, messageId, reconsumeTimes, errorMsg);
            }
            // 拋出異常,讓MQ繼續(xù)重試
            throw e;
        }
    }
}

3. 告警工具類(釘釘+企微雙渠道)

封裝消息模板、HTTP發(fā)送、異步處理、@人邏輯,支持markdown格式告警

package com.xm.kite.tms.pda.util;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xm.kite.tms.common.config.TmsBusinessConfig;
import com.xm.kite.tms.pda.vo.dingding.SendDingAlarmRobotMsgDTO;
import com.xm.kite.tms.pda.vo.dingding.qyweixin.QyWeiXinSendDingAlarmRobotMsgDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executor;
/**
 * 釘釘+企業(yè)微信 告警機器人工具類
 * 核心功能:
 * 1. 支持雙渠道切換發(fā)送markdown告警消息
 * 2. 異步發(fā)送,不阻塞業(yè)務主線程
 * 3. 自動@指定人員,格式化消息模板
 */
@Slf4j
@Component
@RefreshScope
public class DingDingUtils {
    // 通道常量
    public static final Integer CHANNEL_CLOSE = 0;
    public static final Integer CHANNEL_DING = 1;
    public static final Integer CHANNEL_WECHAT = 2;
    // 消息類型
    public static final String MARKDOWN = "markdown";
    // ====================== 告警消息模板 ======================
    /** MQ消費失敗告警模板 */
    public static final String MQ_FAIL_TEMPLATE = """
            # **{0}**-**Tms-MQ消費失敗告警**
            \n**告警時間**: <font color="#FF0000">**{1}**</font>
            \n**Topic**: <font color="#FF0000">**{2}**</font>
            \n**MessageId**: <font color="#FF0000">**{3}**</font>
            \n**失敗原因**: <font color="#FF0000">**{4}**</font>
            \n**消息參數(shù)**: {5}
            """;
    /** Controller通用異常模板 */
    public static final String CONTROLLER_FAIL_TEMPLATE = """
            # **{0}**-**Tms-接口異常告警**
            \n**告警時間**: <font color="#FF0000">**{1}**</font>
            \n**異常信息**: <font color="red">**{2}**</font>
            \n**異常方法**: <font color="#32CD32">{3}</font>
            \n**請求參數(shù)**: {4}
            """;
    @Resource
    private Executor baseExecutor;
    @Resource
    private TmsBusinessConfig config;
    private final RestTemplate restTemplate = new RestTemplate();
    // ====================== 對外暴露方法 ======================
    /**
     * 發(fā)送Controller異常告警
     */
    public void buildMsg(String errorMsg, String methodName, String params) {
        if (CHANNEL_CLOSE.equals(config.getRobotWebhookChannel())) {
            log.warn("告警通道已關閉");
            return;
        }
        // 格式化消息
        String content = MessageFormat.format(CONTROLLER_FAIL_TEMPLATE,
                config.getEnv(), DateUtil.now(), errorMsg, methodName, StringUtils.left(params, 3500));
        // 分發(fā)到對應渠道
        sendByChannel(content);
    }
    /**
     * 發(fā)送MQ消費失敗告警
     */
    public void buildAndSendDingAlarmRobotMsg(MessageExt messageExt, MqMsgVo mqMsgVo, String messageId, int times, String errorMsg) {
        try {
            if (CHANNEL_CLOSE.equals(config.getRobotWebhookChannel())) return;
            errorMsg = StringUtils.defaultIfBlank(errorMsg, "未知異常");
            String content = MessageFormat.format(MQ_FAIL_TEMPLATE,
                    config.getEnv(), DateUtil.now(), messageExt.getTopic(), messageId,
                    errorMsg, StringUtils.left(JSON.toJSONString(mqMsgVo), 3500));
            sendByChannel(content);
        } catch (Exception e) {
            log.error("構建MQ告警消息異常", e);
        }
    }
    // ====================== 渠道分發(fā)核心 ======================
    /**
     * 根據(jù)配置自動分發(fā)到釘釘/企微
     */
    private void sendByChannel(String content) {
        if (CHANNEL_DING.equals(config.getRobotWebhookChannel())) {
            SendDingAlarmRobotMsgDTO dto = SendDingAlarmRobotMsgDTO.buildMarkdown(content);
            sendAlarmRobotMsg(dto, true);
        }
        if (CHANNEL_WECHAT.equals(config.getRobotWebhookChannel())) {
            QyWeiXinSendDingAlarmRobotMsgDTO dto = QyWeiXinSendDingAlarmRobotMsgDTO.buildDto(config.getQyWeiXinRobotWebhookAtUserIds(), content);
            sendQyAlarmRobotMsg(dto, true);
        }
    }
    // ====================== 底層發(fā)送邏輯(已省略冗余代碼) ======================
    /**
     * 異步發(fā)送釘釘消息
     */
    public void sendAlarmRobotMsg(SendDingAlarmRobotMsgDTO msgDTO, boolean isAsync) {
        if (isAsync) {
            baseExecutor.execute(() -> sendDingHttp(msgDTO));
        }
    }
    /**
     * 異步發(fā)送企微消息
     */
    private void sendQyAlarmRobotMsg(QyWeiXinSendDingAlarmRobotMsgDTO msgDTO, boolean isAsync) {
        if (isAsync) {
            baseExecutor.execute(() -> sendQyHttp(msgDTO));
        }
    }
    // 釘釘HTTP請求
    private WebResponse<Void> sendDingHttp(SendDingAlarmRobotMsgDTO dto) {
        // 原HTTP發(fā)送邏輯(保留不變)
        return WebResponseUtil.success.build();
    }
    // 企微HTTP請求
    private WebResponse<Void> sendQyHttp(QyWeiXinSendDingAlarmRobotMsgDTO dto) {
        // 原HTTP發(fā)送邏輯(保留不變)
        return WebResponseUtil.success.build();
    }
}

三、配置文件(application.yml)

# 環(huán)境配置
spring:
  profiles:
    active: prod
# 釘釘機器人
ding:
  ding:
    robot:
      webhook:
        url: https://oapi.dingtalk.com/robot/send?access_token=%s
        access_token: xxxxx
        atMobiles: 13800138000
# 企業(yè)微信機器人
qy:
  weiXin:
    robot:
      webhook:
        url: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=%s
        key: xxxxx
        at_user_ids: user1,user2
# 告警通道配置
robot:
  webhook:
    channel: 2  # 0關閉 1釘釘 2企微

四、核心功能與優(yōu)化點

1. 核心能力

  • 雙渠道自由切換:配置文件一鍵切換釘釘/企微,支持關閉告警
  • 精準告警
    • Controller:僅攔截系統(tǒng)異常,業(yè)務異常不騷擾
    • MQ:僅在重試5次后告警,避免重復通知
  • 異步發(fā)送:線程池異步推送告警,不阻塞接口/MQ消費
  • 動態(tài)配置:支持Nacos配置熱刷新,無需重啟服務
  • 消息格式化:Markdown 排版,自動截斷超長消息
  • @指定人:支持釘釘@手機號、企微@用戶賬號

2. 代碼優(yōu)化點

  1. 統(tǒng)一常量定義,消除魔法值
  2. 抽取公共方法,減少代碼冗余
  3. 完整日志打印,方便問題排查
  4. 空指針防護、參數(shù)校驗
  5. 規(guī)范注釋,類/方法/關鍵邏輯全覆蓋
  6. 環(huán)境區(qū)分,告警自帶環(huán)境標識(prod/test/dev)

五、生產(chǎn)環(huán)境注意事項

  1. 線程池隔離:告警異步線程建議使用獨立線程池,避免占用業(yè)務線程
  2. 消息限流:高并發(fā)異常時,防止告警消息轟炸群聊
  3. 異常過濾:嚴格過濾業(yè)務異常,僅監(jiān)控系統(tǒng)異常(空指針、SQL異常等)
  4. 配置安全:機器人token/key不要硬編碼,放入配置中心加密存儲
  5. 重試機制:告警HTTP請求可增加簡單重試,提升送達率

六、實現(xiàn)效果

總結

本文基于 Spring AOP 實現(xiàn)了一套生產(chǎn)級、無侵入、雙渠道的異常告警方案,完美覆蓋 Controller接口異常MQ消費失敗 兩大核心場景。

  • 對業(yè)務代碼零侵入,接入成本極低
  • 支持釘釘/企業(yè)微信自由切換
  • 異步、配置化、可動態(tài)刷新

完全滿足分布式系統(tǒng)的實時故障監(jiān)控需求,大幅提升故障響應效率!

到此這篇關于SpringBoot AOP實現(xiàn)釘釘+企業(yè)微信雙渠道異常告警的文章就介紹到這了,更多相關SpringBoot AOP雙渠道異常告警內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論

卓资县| 宁都县| 桃江县| 怀柔区| 图木舒克市| 洪雅县| 孝感市| 伊春市| 陕西省| 鄯善县| 巫山县| 泰兴市| 镇巴县| 宁波市| 甘肃省| 桐乡市| 永德县| 沙河市| 巴东县| 金昌市| 阿拉尔市| 和田县| 大同县| 夏津县| 仁化县| 班戈县| 错那县| 庆阳市| 威海市| 札达县| 沾益县| 大同县| 山阳县| 营山县| 恩平市| 松阳县| 淮安市| 宜兰市| 孟连| 财经| 日喀则市|