微服务整合公众号告警系统

效果图:
微服务整合公众号告警系统_第1张图片

文章目录

          • 一、实现原理
            • 1. 原理设计图示
            • 2. 原理流程
          • 二、代码实战
            • 2.1. 错误收集对象
            • 2.2. 模拟方法
            • 2.3. aop拦截
            • 2.4. 异常信息收集队列
            • 2.5. 发送微信模板
            • 2.6. 微信告警模板
            • 2.7. redis存储图
            • 2.8. 效果图

一、实现原理
1. 原理设计图示

微服务整合公众号告警系统_第2张图片

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

微服务整合公众号告警系统_第3张图片

2.7. redis存储图

微服务整合公众号告警系统_第4张图片

2.8. 效果图

微服务整合公众号告警系统_第5张图片

你可能感兴趣的:(移动端,微服务,java,架构)