效果圖:

文章目錄
- 一、實作原理
- 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. 效果圖