一. 前言
公司的一些项目部署到线上后,有时候会因为bug等其他原因触发异常信息,而且没有做相关的项目监控,虽然做了日志处理,但是还是需要每天打开日志检查,非常的麻烦。由于大多数SpringBoot项目都做了全局的统一异常处理,所以我们可以在每次异常被触发的时候,给我们发送一个邮件来捕获异常信息。
二. 配置Mail
Spring Boot2.x的版本默认集成了mail模块,所以只需要在pom.xml中添加相关依赖即可
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
接下来在application.yml中添加相关配置,这里以163邮箱为例
spring: mail: host: smtp.163.com #SMTP服务器地址 port: username: zhangjiaboke@163.com # 发件的邮箱 password: asdfdsfafaaxx #客户端授权密码,不是邮箱登录密码 protocol: smtp # SMTP服务器用到的协议 default-encoding: UTF-8 # 消息包的编码 #设置使用ssl连接时候才需要开启下面的内容配置TLS,安全起见,建议开启SSL,并添加以下配置 properties: mail.smtp.auth: true mail.smtp.starttls.enable: true mail.smtp.starttls.required: true mail.smtp.socketFactory.port: 465 mail.smtp.socketFactory.class: javax.net.ssl.SSLSocketFactory mail.smtp.socketFactory.fallback: false
其中,host为你的发件邮件的SMTP服务器地址这里给出常用邮箱SMTP,port为端口号
163邮箱:smtp.163.com , port不用写
126邮箱:smtp.126.com, port不用写
QQ邮箱:smtp.qq.com, port是587
值得注意的是,password并不是邮箱的登录密码,而是客户端的授权密码,每种邮箱获取客户端授权密码的方式不同,可以自行查询,这里不再赘述。
三. 编写发送邮箱工具类
参考了Clement-Xu大佬的《spring boot发送邮件 》一文
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.FileSystemResource; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import java.io.File; /** * @Author : ZhangJia * @Date : 2020/4/1 14:47 * @Description : */ @Component public class MailUtil { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private JavaMailSender sender; @Value("${spring.mail.username}") private String from; /** * 发送纯文本的简单邮件 * @param to * @param subject * @param content */ public void sendSimpleMail(String to, String subject, String content) { SimpleMailMessage message = new SimpleMailMessage(); String[] toRecive = to.split(","); message.setFrom(from); message.setTo(toRecive); message.setSubject(subject); message.setText(content); message.setCc(from); //添加抄送人,否则会报Failed messages: com.sun.mail.smtp.SMTPSendFailedException: 554 DT:SPM 163 smt try { sender.send(message); logger.info("简单邮件已经发送。"); } catch (Exception e) { logger.error("发送简单邮件时发生异常!", e); } } /** * 发送html格式的邮件 * @param to * @param subject * @param content */ public void sendHtmlMail(String to, String subject, String content) { MimeMessage message = sender.createMimeMessage(); try { String[] toRecive = to.split(","); //true表示需要创建一个multipart message MimeMessageHelper helper = new MimeMessageHelper(message, true); helper.setFrom(from); helper.setTo(toRecive); helper.setSubject(subject); helper.setText(content, true); sender.send(message); logger.info("html邮件已经发送。"); } catch (MessagingException e) { logger.error("发送html邮件时发生异常!", e); } } /** * 发送带附件的邮件 * @param to * @param subject * @param content * @param filePath */ public void sendAttachmentsMail(String to, String subject, String content, String filePath) { MimeMessage message = sender.createMimeMessage(); try { String[] toRecive = to.split(","); //true表示需要创建一个multipart message MimeMessageHelper helper = new MimeMessageHelper(message, true); helper.setFrom(from); helper.setTo(toRecive); helper.setSubject(subject); helper.setText(content, true); FileSystemResource file = new FileSystemResource(new File(filePath)); String fileName = filePath.substring(filePath.lastIndexOf(File.separator)); helper.addAttachment(fileName, file); sender.send(message); logger.info("带附件的邮件已经发送。"); } catch (MessagingException e) { logger.error("发送带附件的邮件时发生异常!", e); } } /** * 发送嵌入静态资源(一般是图片)的邮件 * @param to * @param subject * @param content 邮件内容,需要包括一个静态资源的id,比如:<img src=\"cid:rscId01\" > * @param rscPath 静态资源路径和文件名 * @param rscId 静态资源id */ public void sendInlineResourceMail(String to, String subject, String content, String rscPath, String rscId) { MimeMessage message = sender.createMimeMessage(); try { String[] toRecive = to.split(","); //true表示需要创建一个multipart message MimeMessageHelper helper = new MimeMessageHelper(message, true); helper.setFrom(from); helper.setTo(toRecive); helper.setSubject(subject); helper.setText(content, true); FileSystemResource res = new FileSystemResource(new File(rscPath)); helper.addInline(rscId, res); sender.send(message); logger.info("嵌入静态资源的邮件已经发送。"); } catch (MessagingException e) { logger.error("发送嵌入静态资源的邮件时发生异常!", e); } } }
四. 在全局异常类中发送错误信息
关于全局异常类的配置和使用,请查看本站《SpringBoot:统一异常处理》一文,在统一异常处理类中,将报错信息发送至邮箱即可。
mport io.zhangjia.springbootmd5.exception.TestException; import io.zhangjia.springbootmd5.util.Result; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; @ControllerAdvice public class GlobalExceptionHandler { @Autowired private MailUtil mailUtil; @ExceptionHandler @ResponseBody public Result handle(Exception e) { if(e instanceof TestException) { TestException testException = (TestException) e; int code = testException.getCode(); String message = testException.getMessage(); mailUtil.sendSimpleMail("zhangjia@188.com","自定义异常",message ); return new Result(code,message); } String message = e.getMessage(); if(StringUtils.isEmpty(message)){ message = "未定义错误"; } return new Result(-1,message); } }
此时,当有异常被触发的时候,邮箱便可以收到相关通知:
五. 美化邮件
在上面的例子中,我们采用了纯文本的方式发送邮件,不太美观,我们可以写个网页美化一下,采用Thymeleaf交互数据
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml"> <head> <meta charset="UTF-8"> <title>张甲博客:系统异常提示</title> <link rel='stylesheet' href='https://unpkg.com/ionicons@4.5.10-0/dist/css/ionicons.min.css'> <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Montserrat:1000,700&display=swap'> <style> body { background-color: #EBECF0; } #mail-body { display: flex; flex-direction: column; justify-content: space-between; align-items: center; } .mail-name { display: inline-block; width: 100px; height: 50px; line-height: 50px; background-color: #EBECF0; text-align: center; box-shadow: -5px -5px 20px #FFF, 5px 5px 20px #BABECC; border-radius: 8px; } .mail-value { display: inline-block; width: 400px; height: 50px; line-height: 50px; background-color: #EBECF0; margin-right: 8px; box-shadow: inset 2px 2px 5px #BABECC, inset -5px -5px 10px #FFF; box-sizing: border-box; transition: all 0.2s ease-in-out; appearance: none; -webkit-appearance: none; border-radius: 10px; margin-left: 20px; padding-left: 10px; } #mail-info { width: 535px; height: 100%; background-color: #EBECF0; margin-right: 8px; box-shadow: inset 2px 2px 5px #BABECC, inset -5px -5px 10px #FFF; box-sizing: border-box; transition: all 0.2s ease-in-out; appearance: none; -webkit-appearance: none; border-radius: 10px; padding: 10px; white-space: normal; word-break: break-all; overflow: hidden; } div { margin-bottom: 20px; } </style> </head> <body> <div id="mail-body"> <div class="segment"> <h1>Vchain 异常信息</h1> </div> <div> <span class="mail-name"> 异常代码: </span> <span class="mail-value" th:text="${code}"> </span> </div> <div> <span class="mail-name"> 异常信息: </span> <span class="mail-value" th:text="${message}"> </span> </div> <div> <span class="mail-name"> 发生时间: </span> <span class="mail-value" th:text="${create}"> </span> </div> <div id="mail-info" th:text="${info}"> </div> </div> </div> </body> </html>
此时将统一异常类修改为:
package com.enjoysix.vchain.exception; import com.enjoysix.vchain.util.MailUtil; import com.enjoysix.vchain.util.Result; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.Context; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * @Author : ZhangJia * @Date : 2019/11/1 15:00 * @Description : 统一异常处理类 * 统一异常处理类思路: * 程序可能遇到的任何错误都在service层处理 * (因为在Controller中调用了Service,所以Service发生的异常,也会被GlobalExceptionHandler统一处理) * 在service层可能发生错误的地方通过调用VchainException,传入异常类型,将异常抛出 */ @ControllerAdvice public class GlobalExceptionHandler { private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); @Autowired private MailUtil mailUtil; @Autowired private TemplateEngine templateEngine; @ExceptionHandler @ResponseBody public Result handle(Exception e) { Result result; int code; String message; if (e instanceof VchainException) { //如果是自定义异常 VchainException vchainException = (VchainException) e; code = vchainException.getCode(); message = vchainException.getMessage(); //Throwable类中的message result = new Result(code, message); //设置code和result } else { //如果是其他异常,统一报未定义错误 code = ExceptionCode.UNDEFINED_ERROR.getCode(); message = ExceptionCode.UNDEFINED_ERROR.getMsg(); result = new Result(code, message); } ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); e.printStackTrace(new PrintStream(byteArrayOutputStream)); String exception = byteArrayOutputStream.toString(); Map<String, Object> map = new HashMap<>(); map.put("code", code); map.put("message", message); map.put("info", exception); map.put("create", new Date()); Context context = new Context(); context.setVariables(map); String emailContent = templateEngine.process("/mail", context); mailUtil.sendHtmlMail("zhangjia@188.com", "系统报警信息", emailContent); return result; } }
当异常发生时,收到的邮件如下:
请登录之后再进行评论