使用SpringMail寫一個(gè)告警發(fā)送郵件系統(tǒng)

沒(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)喜歡我

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末堤如,一起剝皮案震驚了整個(gè)濱河市蒲列,隨后出現(xiàn)的幾起案子窒朋,更是在濱河造成了極大的恐慌,老刑警劉巖蝗岖,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侥猩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡抵赢,警方通過(guò)查閱死者的電腦和手機(jī)欺劳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)铅鲤,“玉大人划提,你說(shuō)我怎么就攤上這事〔守埃” “怎么了腔剂?”我有些...
    開(kāi)封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)驼仪。 經(jīng)常有香客問(wèn)我,道長(zhǎng)袜漩,這世上最難降的妖魔是什么绪爸? 我笑而不...
    開(kāi)封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮宙攻,結(jié)果婚禮上奠货,老公的妹妹穿的比我還像新娘。我一直安慰自己座掘,他們只是感情好递惋,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著溢陪,像睡著了一般萍虽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上形真,一...
    開(kāi)封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天杉编,我揣著相機(jī)與錄音,去河邊找鬼咆霜。 笑死邓馒,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蛾坯。 我是一名探鬼主播光酣,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼脉课!你這毒婦竟也來(lái)了救军?” 一聲冷哼從身側(cè)響起财异,我...
    開(kāi)封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎缤言,沒(méi)想到半個(gè)月后宝当,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡胆萧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年庆揩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跌穗。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡订晌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蚌吸,到底是詐尸還是另有隱情锈拨,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布羹唠,位于F島的核電站奕枢,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏佩微。R本人自食惡果不足惜缝彬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望哺眯。 院中可真熱鬧谷浅,春花似錦、人聲如沸奶卓。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)夺姑。三九已至墩邀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瑟幕,已是汗流浹背磕蒲。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留只盹,地道東北人辣往。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像殖卑,于是被迫代替她去往敵國(guó)和親站削。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • SpringBoot集成郵件服務(wù)竟如此簡(jiǎn)單孵稽,還等什么许起,快來(lái)學(xué)習(xí)漲姿勢(shì)吧J肌! 學(xué)習(xí)目標(biāo) 快速掌握郵件業(yè)務(wù)類的核心邏輯...
    一只襪子閱讀 27,693評(píng)論 13 103
  • 團(tuán)隊(duì)開(kāi)發(fā)框架實(shí)戰(zhàn)—面向切面的編程 AOP 引言 軟件開(kāi)發(fā)的目標(biāo)是要對(duì)世界的部分元素或者信息流建立模型园细,實(shí)現(xiàn)軟件系統(tǒng)...
    Bobby0322閱讀 4,143評(píng)論 4 49
  • 有大才者未必有略猛频,有大略者未必有大才狮崩。 能知未必可行,能行未必盡知鹿寻。 觀古今人物睦柴,大致如此。 管子治政用兵奉君毡熏,都...
    人間三十年閱讀 287評(píng)論 0 3
  • 你每天堅(jiān)持的是什么痢法? 有的人說(shuō)狱窘,我每天堅(jiān)持跑步;有的人說(shuō)财搁,我每天堅(jiān)持一本書训柴;還有的人說(shuō),我每天都在堅(jiān)持我的夢(mèng)想妇拯。 ...
    吆毣茬衹仩冩慲懟芣起閱讀 238評(píng)論 0 0
  • 早上,早飯吃的晚洗鸵,肚子餓到有點(diǎn)疼越锈,拿著手上的兩個(gè)包子,卻絲毫沒(méi)有食欲膘滨,這是怎么回事呢甘凭,真的是蠻神奇的事情了。
    三分廢人閱讀 80評(píng)論 0 0