沒(méi)有bug的系統(tǒng)不是好系統(tǒng)雳攘!在我們?nèi)粘5拈_(kāi)發(fā)中系統(tǒng)報(bào)錯(cuò)了只會(huì)把錯(cuò)誤信息打印在日志中,然后等著用戶來(lái)反饋智末,程序員在著手處理,查日志徒河,定位錯(cuò)誤的代碼行系馆。
其實(shí)整個(gè)查看錯(cuò)誤的過(guò)程是有好幾步的,如果日志信息多了虚青,那么排查錯(cuò)誤就比較困難它呀。那我們能不能程序發(fā)生錯(cuò)誤了就馬上通知程序員和運(yùn)維人員呢?
答案是肯定能的棒厘,現(xiàn)在市面上有很多第三方類似的告警系統(tǒng),甚至不止這一個(gè)發(fā)送郵件來(lái)告警下隧,還有很多種方式奢人。
但是我不想用第三方軟件來(lái)做這么一個(gè)功能,我喜歡自己寫一個(gè)淆院,嘿嘿何乎,程序員嘛,自己動(dòng)手豐衣足食土辩,足夠滿足自己的業(yè)務(wù)需求就好了支救。
為什么要使用發(fā)送郵件而不是發(fā)送驗(yàn)證碼呢?因?yàn)榘l(fā)送驗(yàn)證碼要錢拷淘,成本太高各墨,發(fā)送郵件來(lái)告知是最方便也是成本最低的方案了。
好了启涯,咱們開(kāi)始動(dòng)手寫代碼吧贬堵,先說(shuō)說(shuō)整個(gè)架構(gòu)用的技術(shù)和版本,項(xiàng)目使用springBoot+springAop+springMail+SpringListener來(lái)搭建结洼。
咱們使用的是smtp.163.com服務(wù)來(lái)發(fā)送郵件黎做,所以咱們得有一個(gè)163郵箱賬號(hào),然后在設(shè)置頁(yè)把POP3/SMTP/IMAP服務(wù)給開(kāi)啟松忍。
配置好之后蒸殿,咱們來(lái)開(kāi)始寫代碼吧,我先把整個(gè)pom給貼出來(lái)鸣峭。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.spring.mail</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springMail</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入aopjar-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>
<!--引入springMail jar包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
主要的引入就是aop和springMail這兩個(gè)dependency宏所。
咱們?cè)俣x一個(gè)發(fā)送的模板消息實(shí)體類。
package com.spring.mail.bean;
import lombok.Data;
import lombok.ToString;
/**
* 定義發(fā)送信息模板
*/
@Data
@ToString
public class MailWarningBean {
/**
* 錯(cuò)誤信息
*/
private String errorMsg;
/**
* 參數(shù)列表
*/
private String params;
/**
* 類路徑
*/
private String path;
}
再把yml配置文件的代碼貼出來(lái)
server:
port: 8080
tomcat:
uri-encoding: utf-8
spring:
aop:
auto: true
mail:
host: 220.181.12.11 #linux有時(shí)候不能有效的進(jìn)行域名解析叽掘,所以我們直接用smtp.163.com來(lái)ping一下獲取真實(shí)的ip地址
username: XXXXXX #填寫你的郵箱地址(發(fā)送人)
password: XXXXXX #你的郵箱密碼
default-encoding: UTF-8
properties:
mail:
smtp:
auth: true
port: 465 #使用465端口 因?yàn)樵诜?wù)器上25端口不能用
socketFactory:
port: 465
class: javax.net.ssl.SSLSocketFactory
fallback: false
starttls:
enable: true
required: true
port: 465
咱們的思路主要是:使用spring的事件監(jiān)聽(tīng)來(lái)定義郵件的發(fā)送楣铁,使用aop的異常增強(qiáng)來(lái)發(fā)送郵件。當(dāng)異常產(chǎn)生了就發(fā)送郵件更扁,告知咱們的程序員盖腕。
先定義spring的事件監(jiān)聽(tīng)模塊赫冬。
package com.spring.mail.event;
import lombok.Data;
import org.springframework.context.ApplicationEvent;
/**
* 定義事件
* @param <T>
*/
@Data
public class MyEvent<T> extends ApplicationEvent {
private T t;
public MyEvent(Object source,T t){
super(source);
this.t=t;
}
}
事件類已經(jīng)寫好,咱們就開(kāi)始動(dòng)手寫事件的監(jiān)聽(tīng)類溃列,主要就是做郵件的發(fā)送劲厌,sendMail方法使用了Async注解,Async注解就是開(kāi)啟異步听隐,讓發(fā)送郵件異步來(lái)處理补鼻。
package com.spring.mail.event.listener;
import com.google.common.base.Throwables;
import com.spring.mail.bean.MailWarningBean;
import com.spring.mail.event.MyEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.mail.internet.MimeMessage;
@Component
@Slf4j
public class MailListener {
@Autowired
private JavaMailSender javaMailSender;
/**
* 定義事件、開(kāi)啟異步
* @param myEvent
*/
@EventListener
@Async
public void sendMail(MyEvent<MailWarningBean> myEvent){
MimeMessage mimeMessage=null;
try{
MailWarningBean mailWarningBean=myEvent.getT();
mimeMessage=javaMailSender.createMimeMessage();
MimeMessageHelper msgHelper=new MimeMessageHelper(mimeMessage,true);
//發(fā)送人郵箱
msgHelper.setFrom("XXXXXXX");
//收件人郵箱(單個(gè))
msgHelper.setTo("XXXXXXXX");
/**
* 發(fā)送多人
* //收件人
* InternetAddress[] internetAddresses=new InternetAddress[]{
* new InternetAddress("XXXXXXX.com","","utf-8"),
* new InternetAddress("XXXXXXX.com","","utf-8")
* };
* msgHelper.setTo(internetAddresses);
*/
//主題
msgHelper.setSubject("服務(wù)器系統(tǒng)傳錯(cuò)誤雅任,請(qǐng)盡快處理风范!");
//拼接好發(fā)送的內(nèi)容
StringBuffer sb=new StringBuffer();
sb.append("<p><h2>您的類方法路徑"+mailWarningBean.getPath()+"出現(xiàn)錯(cuò)誤!</h2></p>");
sb.append("<p>參數(shù)列表:"+mailWarningBean.getParams()+"</p>");
sb.append("<p>錯(cuò)誤信息:<p style='color:red;'>"+mailWarningBean.getErrorMsg()+"沪么。</p>");
sb.append("請(qǐng)盡快處理硼婿。</p><p>出現(xiàn)錯(cuò)誤服務(wù)器ip地址:<strong>XXX.XXX.XX</strong></p>");
msgHelper.setText(sb.toString(),true);
javaMailSender.send(mimeMessage); //進(jìn)行發(fā)送
log.info("發(fā)送郵件成功");
}catch(Exception e){
log.error("發(fā)送告警郵件出現(xiàn)錯(cuò)誤,錯(cuò)誤原因:"+ Throwables.getStackTraceAsString(e));
}
}
}
咱們?cè)賮?lái)做aop的處理禽车,aop的增強(qiáng)方式有前置增強(qiáng)寇漫、后置增強(qiáng)、環(huán)繞增強(qiáng)殉摔、異常增強(qiáng)等方式州胳,咱們采用異常增強(qiáng),因?yàn)樵蹅冎饕臉I(yè)務(wù)就是做告警逸月,當(dāng)程序出錯(cuò)了咱們就發(fā)送郵件告知運(yùn)維人員和開(kāi)發(fā)人員栓撞。來(lái),我把a(bǔ)op的代碼貼出來(lái)彻采。
package com.spring.mail.aop;
import com.google.common.base.Throwables;
import com.spring.mail.bean.MailWarningBean;
import com.spring.mail.event.MyEvent;
import com.spring.mail.event.listener.MailListener;
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.Autowired;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MailAspect {
@Autowired
private MailListener mailListener;
//定義切面
@Pointcut("execution(public * com.spring.mail.controller.*.*(..))")
public void MailAspect(){}
/**
* 使用aop的異常增強(qiáng)方式來(lái)發(fā)送郵件
* @param joinPoint
* @param e
*/
@AfterThrowing(pointcut = "MailAspect()",throwing = "e")
public void deAfterThrowing(JoinPoint joinPoint,Throwable e){
Object[] param=joinPoint.getArgs(); //獲取參數(shù)列表
StringBuilder sb=new StringBuilder();
for(Object obj:param){
sb.append(obj+",");
}
MailWarningBean mailWarningBean=new MailWarningBean();
mailWarningBean.setErrorMsg(Throwables.getStackTraceAsString(e));
mailWarningBean.setParams(sb.delete(0,sb.length()).toString());
mailWarningBean.setPath(joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName()); //獲取方法路徑
MyEvent<MailWarningBean> myEvent=new MyEvent<>(this,mailWarningBean);
mailListener.sendMail(myEvent);
}
}
整個(gè)配置就寫的差不多了腐缤,在aop類上使用@Pointcut來(lái)定義切面,當(dāng)controller出現(xiàn)異常時(shí)發(fā)送郵件肛响。接下來(lái)岭粤,咱們就測(cè)試測(cè)試吧。
先寫一個(gè)controller
package com.spring.mail.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MainController {
@GetMapping("/hello")
public String hello(){
System.out.println(1/0);
return "test";
}
}
在接口內(nèi)故意寫一行代碼產(chǎn)生異常特笋,看會(huì)不會(huì)接收到郵件剃浇。
訪問(wèn)接口時(shí)報(bào)錯(cuò)了,日志信息打印出了郵件已發(fā)送猎物,咱們現(xiàn)在看看郵箱有沒(méi)有收到告警的郵件虎囚。
可以看到郵箱是收到了此郵件,說(shuō)明咱們整個(gè)告警模塊系統(tǒng)就寫好了蔫磨。完整的代碼可以點(diǎn)下面的鏈接去git克隆代碼淘讥,有不懂的地方歡迎大家留言提問(wèn)和討論。
git倉(cāng)庫(kù):https://github.com/wuyanzu01/springMail
請(qǐng)關(guān)注微信公眾號(hào):請(qǐng)快點(diǎn)喜歡我