天天看點

微服務整合公衆号告警系統

效果圖:

微服務整合公衆号告警系統

文章目錄

  • ​​一、實作原理​​
  • ​​1. 原理設計圖示​​
  • ​​2. 原理流程​​
  • ​​二、代碼實戰​​
  • ​​2.1. 錯誤收集對象​​
  • ​​2.2. 模拟方法​​
  • ​​2.3. aop攔截​​
  • ​​2.4. 異常資訊收集隊列​​
  • ​​2.5. 發送微信模闆​​
  • ​​2.6. 微信告警模闆​​
  • ​​2.7. redis存儲圖​​
  • ​​2.8. 效果圖​​
一、實作原理

1. 原理設計圖示

微服務整合公衆号告警系統

2. 原理流程

1.浏覽器或者app發起請求,在處理邏輯時發生異常,.

2.aop異常通知捕獲,收集異常資訊,存入隊列中。

3.全局異常捕獲,拼接錯誤json資料,将封裝好的json傳回前端

4.單獨的線程每隔1秒,就去隊列中擷取異常消息。

5.調用微信公衆号模闆接口發送模闆.

6.先判斷此通知在1分鐘之内是否已經發起通知,若發起,則流程結束。

7.若未發起,則,拼裝模闆異常資訊調用微信公衆号接口

8.推送告警通知。

二、代碼實戰

2.1. 錯誤收集對象

package com.mayikt.main.alarm.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

/**
 * 錯誤收集對象
 *
 * @author gblfy
 * @Date 2022-09-13
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AlarmEntity {

    /**
     * 類的名稱
     */
    private String className;
    /**
     * 方法名稱
     */
    private String methodName;
    /**
     * 服務名稱
     */
    private String serviceName;
    /**
     * 服務IP
     */
    private String ip;
    /**
     * 端口号碼
     */
    private String port;
    /**
     * 錯誤行号
     */
    private int wrongLineNumber;
    /**
     * 錯誤内容
     */
    private String errorMsg;
    /**
     * 發生錯誤時間
     */
    private Date errorTime;
}      

2.2. 模拟方法

@RestController
public class TestSecurityServiceImpl implements TestSecurityService {
    @GetMapping("/insert")
    @Override
    public String insert(int age) {
        int j = 1 / age;
        return "insert"+j;
    }
}      

2.3. aop攔截

package com.mayikt.main.alarm;

import com.mayikt.main.alarm.entity.AlarmEntity;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.Enumeration;

/**
 * 告警通知aop
 *
 * @author gblfy
 * @Date 2022-09-13
 **/
@Slf4j
@Aspect
@Component
public class AopInterceptionError {

    @Value("${spring.application.name}")
    private String serviceName;

    @Value("${server.port}")
    private String serverPort;


    /**
     * 通過aop異常通知攔截系統日志
     */
    @Pointcut("execution(public * com.mayikt.main.api.impl..*.*(..))")
    public void aopInterceptionError() {

    }

    //異常通知:在方法出現異常時進行通知,可以範文異常防撞對象,且可以指定在出現特定異常時執行通知
    @AfterThrowing(value = "aopInterceptionError()", throwing = "e")
    public void afterThrowing(JoinPoint joinpoint, Exception e) throws UnknownHostException {
        //aop 異常通知擷取棧幀 方法名稱 參數值 累的名稱 等 ip 和端口号碼 服務名稱
        log.info("joinpoint->{}", joinpoint);

        //報錯内容
        String errorMsg = e.getMessage();
        StackTraceElement stackTraceElement = e.getStackTrace()[0];
        //類的名稱
        String className = stackTraceElement.getClassName();
        //錯誤行号
        int lineNumber = stackTraceElement.getLineNumber();
        //方法名稱
        String methodName = stackTraceElement.getMethodName();
        AlarmEntity alarmEntity = new AlarmEntity(className,
                methodName,
                serviceName,
                getLocalHostLANAddress().getHostAddress(),
                serverPort,
                lineNumber,
                errorMsg,
                new Date());

        //将該對象直接存放入隊列中
        AlarmContainer.addAlarm(alarmEntity);
    }

    /**
     * ip擷取方案:
     * 第一種情況:非容器部署,直接擷取伺服器ip位址
     * 第二種情況:docker容器部署,擷取容器啟動傳遞的參數即可
     * docker run (加上參數==指定伺服器ip位址)
     */
    public InetAddress getLocalHostLANAddress() {
        try {
            InetAddress candidateAddress = null;
            // 周遊所有的網絡接口
            for (Enumeration ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements(); ) {
                NetworkInterface iface = (NetworkInterface) ifaces.nextElement();
                // 在所有的接口下再周遊IP
                for (Enumeration inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); ) {
                    InetAddress inetAddr = (InetAddress) inetAddrs.nextElement();
                    if (!inetAddr.isLoopbackAddress()) {// 排除loopback類型位址
                        if (inetAddr.isSiteLocalAddress()) {
                            // 如果是site-local位址,就是它了
                            return inetAddr;
                        } else if (candidateAddress == null) {
                            // site-local類型的位址未被發現,先記錄候選位址
                            candidateAddress = inetAddr;
                        }
                    }
                }
            }
            if (candidateAddress != null) {
                return candidateAddress;
            }
            // 如果沒有發現 non-loopback位址.隻能用最次選的方案
            InetAddress jdkSuppliedAddress = InetAddress.getLocalHost();
            return jdkSuppliedAddress;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }



}      

2.4. 異常資訊收集隊列

package com.mayikt.main.alarm;

import com.mayikt.main.alarm.entity.AlarmEntity;
import com.mayikt.main.manage.WechatTemplateManage;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.LinkedBlockingDeque;

/**
 * 異常資訊收集隊列
 *
 * @author gblfy
 * @Date 2022-09-13
 **/
@Component
public class AlarmContainer {
    @Autowired
    private WechatTemplateManage wechatTemplateManage;


    public AlarmContainer() {
        new SendAlarmThread().start();
    }

    /**
     * 緩存告警錯誤内容
     */
    private static LinkedBlockingDeque<AlarmEntity> alarmEntityDeque = new LinkedBlockingDeque<>();

    /**
     * 添加告警
     */
    public static void addAlarm(AlarmEntity alarmEntity) {
        alarmEntityDeque.offer(alarmEntity);
    }

    // /**
    //  * 擷取告警内容
    //  */
    // public static AlarmEntity getAlarm() {
    //     return alarmEntityDeque.poll();
    // }
    //
    // /**
    //  * 移除告警
    //  */
    // public static void removeAlarm() {
    //     alarmEntityDeque.remove();
    // }

    class SendAlarmThread extends Thread {

        @SneakyThrows
        @Override
        public void run() {
            while (true) {
                //擷取告警内容
                AlarmEntity alarmEntity = alarmEntityDeque.poll();
                if (alarmEntity != null) {
                    //    調用微信公衆号末班接口發送模闆
                    wechatTemplateManage.sendAlarmTemplate(alarmEntity);
                }
                //    為了避免cpu飚高,延遲1s
                Thread.sleep(1000);
            }
        }
    }
}      

2.5. 發送微信模闆

/**
     * 發送警告模闆
     *
     * @param alarmEntity
     * @return
     */
    public boolean sendAlarmTemplate(AlarmEntity alarmEntity) {

        /**
         * 高并發避免重複發的方案
         * redis key=服務名稱+ip+端口号+類的名稱+方法名稱+錯誤行号 value=報錯的參數值(内容) 設定過期時間比如:1分鐘 一分鐘内同樣的報錯隻會通知一次
         *
         */
        String serviceName = alarmEntity.getServiceName();
        String ip = alarmEntity.getIp();
        String port = alarmEntity.getPort();
        String className = alarmEntity.getClassName();
        String methodName = alarmEntity.getMethodName();

        int wrongLineNumber = alarmEntity.getWrongLineNumber();
        String errorMsg = alarmEntity.getErrorMsg();
        String errorRepeatKey = serviceName + "_" + ip + "_" + port + "_" + className + "_" + methodName + "_" + wrongLineNumber;
        String errorRepeatValue = errorRepeatKey + "_" + errorMsg;

        //判斷key是否存在
        String redisErrorMsg = RedisUtil.getString(errorRepeatKey);

        if (!StringUtils.isEmpty(redisErrorMsg)) {
            return false;
        }

        //将錯誤發送記錄存儲redis
        RedisUtil.setString(errorRepeatKey, errorRepeatValue, 60L);

        WxMpTemplateMessage wxMpTemplateMessage = new WxMpTemplateMessage();
        wxMpTemplateMessage.setTemplateId(alarmTemplateId);
        wxMpTemplateMessage.setToUser("oD8nF5jpNF9KXU_j49uvqOPVdiEU");
        List<WxMpTemplateData> data = new ArrayList<>();
        data.add(new WxMpTemplateData("first", serviceName));
        data.add(new WxMpTemplateData("keyword1", ip + ":" + port));
        data.add(new WxMpTemplateData("keyword2", "報錯的類:" + className));
        data.add(new WxMpTemplateData("keyword3", "報錯的方法:" + methodName));
        data.add(new WxMpTemplateData("keyword4", "報錯的行号:" + wrongLineNumber));
        data.add(new WxMpTemplateData("keyword5", "報錯的内容:" + errorMsg));
        data.add(new WxMpTemplateData("keyword6", SimpleDateFormatUtils.getFormatStrByPatternAndDate(alarmEntity.getErrorTime())));
        wxMpTemplateMessage.setData(data);

        //點選模闆指定跳轉位址
        // wxMpTemplateMessage.setUrl("http://www.mayikt.com");
        try {
            String appId = wxMpProperties.getConfigs().get(0).getAppId();
            System.out.println("appId" + appId);
            WxMpTemplateMsgService templateMsgService = WxMpConfiguration.getMpServices().get(appId).getTemplateMsgService();
            templateMsgService.sendTemplateMsg(wxMpTemplateMessage);
            return true;
        } catch (Exception e) {
            log.error("e->{}", e);
            e.printStackTrace();
            return false;
        }
    }      

2.6. 微信告警模闆

模闆标題:告警通知2
模闆内容:
服務名稱:{{first.DATA}}
IP和端口:{{keyword1.DATA}}
報錯的類:{{keyword2.DATA}}
報錯方法:{{keyword3.DATA}}
報錯行号:{{keyword4.DATA}}
報錯内容:{{keyword5.DATA}}
錯誤時間:{{keyword6.DATA}}
發生了系統錯誤,請您及時登入伺服器檢視并解決
模闆ID(用于接口調用):  _axxxxxxxxxxxxxxx3jMYdF4      
微服務整合公衆号告警系統

2.7. redis存儲圖

2.8. 效果圖