SpringBoot之自定义发送异常邮件Starter

前言:

   前一阵学了SpringBoot发送邮件、SpringBoot制作Starter和自定义业务异常等知识,我突发奇想,我可以制作一个能捕获项目所有异常,通过邮件形式把异常信息发送给开发者的Starter,开发者实现只需要配置相关信息,就可以使用。实现无侵入性编程,支持热拔插使用。

正文:

具体相关知识点:

SpringBoot中Starter知识、SpringBoot驱动配置文件知识(处理配置属性值多个,分割逗号处理方式)、SpringBoot发送邮件知识、SpringMVC处理全局异常(HandlerExceptionResolver)、SpringMVC配置(WebMvcConfigurer)

相关代码:

1. 创建SpringBoot项目,引入相关依赖:

        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
            org.springframework.boot
            spring-boot-starter-mail
        
        
        
            org.springframework.boot
            spring-boot-autoconfigure
        

注:Spring 官方 Starter通常命名为spring-boot-starter-{name}, 非官方Starter命名应遵循{name}-spring-boot-starter的格式。 

2.编写发送邮件服务:

package com.hanxiaozhang.exceptionmail.autocinfigure;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;

/**
 * 〈一句话功能简述〉
* 〈Mail服务〉 * * @author hanxinghua * @create 2019/8/24 * @since 1.0.0 */ public class MailService { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private JavaMailSender sender; /** * 发送纯文本的简单邮件 * * @param from * @param to * @param subject * @param content */ public void sendSimpleMail(String from,String to, String subject, String content){ SimpleMailMessage message = new SimpleMailMessage(); message.setFrom(from); message.setTo(to); message.setSubject(subject); message.setText(content); try { sender.send(message); logger.info("简单邮件已经发送!"); } catch (Exception e) { logger.error("发送简单邮件时发生异常!", e); } } /** * 发送html格式的邮件 * * @param from * @param to * @param subject * @param content */ public void sendHtmlMail(String from,String to, String subject, String content){ MimeMessage message = sender.createMimeMessage(); try { //true表示需要创建一个multipart message MimeMessageHelper helper = new MimeMessageHelper(message, true); helper.setFrom(from); helper.setTo(to); helper.setSubject(subject); helper.setText(content, true); sender.send(message); logger.info("html邮件已经发送。"); } catch (MessagingException e) { logger.error("发送html邮件时发生异常!", e); } } /** * 发送带附件的邮件 * * @param from * @param to * @param subject * @param content * @param filePath */ public void sendAttachmentsMail(String from,String to, String subject, String content, String filePath){ MimeMessage message = sender.createMimeMessage(); try { //true表示需要创建一个multipart message MimeMessageHelper helper = new MimeMessageHelper(message, true); helper.setFrom(from); helper.setTo(to); 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 from * @param subject * @param content 邮件内容,需要包括一个静态资源的id,比如: * @param rscPath 静态资源路径和文件名 * @param rscId 静态资源id */ public void sendInlineResourceMail(String from,String to, String subject, String content, String rscPath, String rscId){ MimeMessage message = sender.createMimeMessage(); try { //true表示需要创建一个multipart message MimeMessageHelper helper = new MimeMessageHelper(message, true); helper.setFrom(from); helper.setTo(to); 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); } } }

 3.编写配置文件类:

package com.hanxiaozhang.exceptionmail.autocinfigure;

import org.springframework.boot.context.properties.ConfigurationProperties;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * 〈一句话功能简述〉
* 〈读取异常信息邮件配置信息〉 * * @author hanxinghua * @create 2019/8/24 * @since 1.0.0 */ @ConfigurationProperties(prefix = "hanxiaozhang.mail") public class ExceptionMailProperties { /** * 字符集 */ private static final Charset DEFAULT_CHARSET; /** * 邮箱服务器地址 */ private String host; /** * 端口号 */ private Integer port; /** * 用户名 */ private String username; /** * 密码 */ private String password; /** * 邮箱协议,默认"smtp" */ private String protocol = "smtp"; /** * 默认编码 */ private Charset defaultEncoding; /** * 配置Map */ private Map properties; /** * JNDI名称 */ private String jndiName; /** * 测试连接 */ private boolean testConnection; /** * 邮件发送者,默认配置邮箱的用户,见getSender() */ private String sender; /** * 邮件接收者,默认配置邮箱的用户,见getReceiver() */ private String receiver; /** * 发送邮件主题 */ private String sendMailTitle="异常信息"; /** * 是否打印日志 */ private boolean isPrintLog=false; public ExceptionMailProperties() { this.defaultEncoding = DEFAULT_CHARSET; this.properties = new HashMap(); } public String getHost() { return this.host; } public void setHost(String host) { this.host = host; } public Integer getPort() { return this.port; } public void setPort(Integer port) { this.port = port; } public String getUsername() { return this.username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return this.password; } public void setPassword(String password) { this.password = password; } public String getProtocol() { return this.protocol; } public void setProtocol(String protocol) { this.protocol = protocol; } public Charset getDefaultEncoding() { return this.defaultEncoding; } public void setDefaultEncoding(Charset defaultEncoding) { this.defaultEncoding = defaultEncoding; } public Map getProperties() { return this.properties; } public void setJndiName(String jndiName) { this.jndiName = jndiName; } public String getJndiName() { return this.jndiName; } public boolean isTestConnection() { return this.testConnection; } public void setTestConnection(boolean testConnection) { this.testConnection = testConnection; } public String getSender() { if(null==sender||"".equals(sender)){ return getUsername(); } return sender; } public void setSender(String sender) { this.sender = sender; } public String getReceiver() { if(null==receiver||"".equals(receiver)){ return getUsername(); } return receiver; } public void setReceiver(String receiver) { this.receiver = receiver; } public String getSendMailTitle() { return sendMailTitle; } public void setSendMailTitle(String sendMailTitle) { this.sendMailTitle = sendMailTitle; } public boolean isPrintLog() { return this.isPrintLog; } public void setPrintLog(boolean printLog) { isPrintLog = printLog; } static { DEFAULT_CHARSET = StandardCharsets.UTF_8; } }

4.编写处理全局异常的GlobalExceptionResolver:

package com.hanxiaozhang.exceptionmail.autocinfigure;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.mail.MailProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 〈一句话功能简述〉
* 〈SpringMvc异常处理〉 * * * @author hanxinghua * @create 2019/8/31 * @since 1.0.0 */ public class GlobalExceptionResolver implements HandlerExceptionResolver { /** * 日志 */ private final Logger logger = LoggerFactory.getLogger(this.getClass()); /** * 发送邮件服务 */ private MailService mailService; /** * 异常邮件配置文件 */ private ExceptionMailProperties exceptionMailProperties; /** * 发送者集合(Spring源码写法) * 参考源码:org.springframework.core.env.AbstractEnvironment * 目的:处理配置文件多参数(逗号截取),例,spring.profiles.active=home,mail 写法 * */ private final Set receiverProfiles = new LinkedHashSet(); /** * 构造函数 * * @param mailService * @param exceptionMailProperties */ public GlobalExceptionResolver(MailService mailService,ExceptionMailProperties exceptionMailProperties) { this.mailService = mailService; this.exceptionMailProperties=exceptionMailProperties; } @Nullable @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); ex.printStackTrace(pw); //打印日志 if(exceptionMailProperties.isPrintLog()){ logger.info("exception-mail-starter-print-log:\n"+sw.toString()); } //发送邮件 sendExceptionMail(sw.toString()); return null; } /** * 发送异常邮件 * * @param content */ private void sendExceptionMail(String content){ this.doGetReceiverProfiles().forEach(x->{ mailService.sendSimpleMail(exceptionMailProperties.getSender(),x, exceptionMailProperties.getSendMailTitle(), content); }); } /** * 获取发送者集合(Spring源码写法) * * @return */ private Set doGetReceiverProfiles() { synchronized(this.receiverProfiles) { if (this.receiverProfiles.isEmpty()) { String receivers = exceptionMailProperties.getReceiver(); if (StringUtils.hasText(receivers)) { this.setReceiverProfiles(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(receivers))); } } return this.receiverProfiles; } } /** * 赋值发送者集合(Spring源码写法) * * @param receivers */ private void setReceiverProfiles(String... receivers) { Assert.notNull(receivers, "receiver array must not be null"); synchronized(this.receiverProfiles) { this.receiverProfiles.clear(); String[] var3 =receivers; int var4 = var3.length; //Java中i++语句是需要一个临时变量取存储返回自增前的值,而++i不需要。 //https://blog.csdn.net/u010188178/article/details/83996538 for(int var5 = 0; var5 < var4; ++var5) { String receiver = var3[var5]; this.validateReceiver(receiver); this.receiverProfiles.add(receiver); } } } /** * 验证发送者字符串(Spring源码写法) * * @param receiver */ private void validateReceiver(String receiver) { // 如果字符串里面的值为null, "", " ",那么返回值为false;否则为true if (!StringUtils.hasText(receiver)) { throw new IllegalArgumentException("Invalid hanxiaozhang.mail.receiver [" + receiver + "]: must contain text"); // 验证邮件格式 } else if (!verifyEmail(receiver)) { throw new IllegalArgumentException("Invalid hanxiaozhang.mail.receiver [" + receiver + "]: must mail format"); } } /** * 检查邮箱是否合法 * * @param mail * @return */ public static boolean verifyEmail(String mail){ String regExp = "^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((\\.[a-zA-Z0-9_-]{2,3}){1,2})$"; Pattern p = Pattern.compile(regExp); Matcher m = p.matcher(mail); return m.matches(); } }

注:我这个Starter支持配置多个发送者,此功能的代码借鉴参考了SpringBoot中[ spring.profiles.active=home,mail ]处理方法。

5.编写Start自动配置类:

package com.hanxiaozhang.exceptionmail.autocinfigure;


import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.mail.MailProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

/**
 * 〈一句话功能简述〉
* 〈ExceptionMailAutoConfigure〉 * * @author hanxinghua * @create 2019/8/24 * @since 1.0.0 */ @Configuration //判断当前classpath下是否存在指定类,若是存在则将当前的配置装载入spring容器 @ConditionalOnClass(value={MailService.class,GlobalExceptionResolver.class}) //使,使用 @ConfigurationProperties的类生效。在配置类只配置@ConfigurationProperties,没有使用@Component时使用 @EnableConfigurationProperties(value={ExceptionMailProperties.class,MailProperties.class}) //当配置文件中 hanxiaozhang.mail.enable 值为true时,实例化此类 @ConditionalOnProperty(prefix = "hanxiaozhang.mail",value = "enabled",havingValue = "true") public class ExceptionMailAutoConfigure implements WebMvcConfigurer { @Autowired private ExceptionMailProperties exceptionMailProperties; @Autowired private MailProperties mailProperties; @Bean @Primary //判断是否是当前spring context已经处理的此bean,若是不存在则将当前的配置装载入spring容器 @ConditionalOnMissingBean MailService mailService(){ BeanUtils.copyProperties(exceptionMailProperties,mailProperties); return new MailService(); } @Override public void extendHandlerExceptionResolvers(List resolvers) { resolvers.add(new GlobalExceptionResolver(mailService(),exceptionMailProperties)); } }

6.在resources/META-INF/下新建spring.factories文件:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.hanxiaozhang.exceptionmail.autocinfigure.ExceptionMailAutoConfigure

7.使用maven打包生成jar包,在项目中引入此Starter:

        
            com.hanxiaozhang
            spring-boot-starter-exception-mail
            0.0.1-SNAPSHOT
        

8.配置项目的YAML 文件:

hanxiaozhang.mail:
  #是否启动
  enabled: true
  #邮箱服务器地址
  host: smtp.sina.cn
  #邮箱用户名
  username: XXXXXX@sina.com
  #邮箱密码(注意:QQ邮箱应该使用授权码)
  password: XXXXX
  #邮件发送者,默认username
  #sender:
  #邮件接收者,默认username
  receiver: XXXXX@qq.com,XXXXX@163.com
  #邮件主题,默认“项目异常”
  sendMailTitle: XXX项目异常
  #打印日志,默认false
  printLog: true
  #编码格式
  default-encoding: UTF-8

9.编写一个异常,测试: 

    @RequestMapping("/test")
    public String  test(){
        int i=2;
        i=i/0;
        //throw  new DataNotFoundException("测试");
       return null;
    }

10.结果:

 SpringBoot之自定义发送异常邮件Starter_第1张图片

11.参考源码:

springBootStarterExceptionMail

你可能感兴趣的:(#Springboot,Spring,Starter,发送异常邮件)