基于 QQ 邮箱的邮件配置与异常通知
1. 为什么选择 QQ 邮箱1.1 QQ 邮箱的优势免费个人用户免费使用稳定腾讯企业级服务高可用性简单SMTP/POP3 配置简单安全支持 SSL/TLS 加密普及率高国内用户覆盖面广1.2 应用场景系统异常告警用户注册/登录通知密码找回业务操作提醒定时报表发送2. QQ 邮箱准备工作2.1 开启 SMTP 服务登录 QQ 邮箱mail.qq.com进入设置点击左上角设置 → 选择账户开启服务找到 POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务开启 IMAP/SMTP服务 或 POP3/SMTP服务点击开启获取授权码按照提示发送短信验证获取16 位授权码重要后续配置使用⚠️ 授权码相当于密码请妥善保管2.2 QQ 邮箱 SMTP 服务器信息配置项值SMTP 服务器smtp.qq.comSMTP 端口SSL465 或 587启用 SSL是发件人邮箱your-qq-numberqq.com授权码开启服务后获得的 16 位字符串3. 项目配置Spring Boot3.1 引入依赖pom.xmlxml!-- Spring Boot 邮件支持 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-mail/artifactId /dependency !-- Thymeleaf 模板引擎用于邮件模板 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency !-- Lombok -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency3.2 配置文件application.ymlyamlspring: mail: # QQ 邮箱配置 host: smtp.qq.com port: 465 # SSL 端口 username: 123456789qq.com # 替换为你的 QQ 邮箱 password: abcdefghijklmnop # 授权码不是 QQ 密码 # SSL 配置 properties: mail: smtp: auth: true ssl: enable: true socketFactory: class: javax.net.ssl.SSLSocketFactory port: 465 starttls: enable: true required: true # 调试模式生产环境关闭 debug: false # 编码配置 default-encoding: UTF-8 protocol: smtp test-connection: false # 启动时测试连接 # 自定义邮件配置 mail: # 异常通知接收者多个邮箱用逗号分隔 alert-recipients: adminexample.com, opsexample.com # 是否启用邮件通知 alert-enabled: true # 发送者显示名称 from-name: 系统监控中心3.3 配置类可选javaimport org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.thymeleaf.spring5.SpringTemplateEngine; import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver; import org.thymeleaf.templateresolver.ITemplateResolver; import java.util.Properties; Configuration public class MailConfig { Value(${spring.mail.host}) private String host; Value(${spring.mail.port}) private Integer port; Value(${spring.mail.username}) private String username; Value(${spring.mail.password}) private String password; Bean public JavaMailSender javaMailSender() { JavaMailSenderImpl mailSender new JavaMailSenderImpl(); mailSender.setHost(host); mailSender.setPort(port); mailSender.setUsername(username); mailSender.setPassword(password); Properties props mailSender.getJavaMailProperties(); props.put(mail.transport.protocol, smtp); props.put(mail.smtp.auth, true); props.put(mail.smtp.ssl.enable, true); props.put(mail.smtp.socketFactory.class, javax.net.ssl.SSLSocketFactory); props.put(mail.smtp.socketFactory.port, port); props.put(mail.debug, false); return mailSender; } Bean public ITemplateResolver thymeleafTemplateResolver() { ClassLoaderTemplateResolver templateResolver new ClassLoaderTemplateResolver(); templateResolver.setPrefix(templates/mail/); templateResolver.setSuffix(.html); templateResolver.setTemplateMode(HTML); templateResolver.setCharacterEncoding(UTF-8); return templateResolver; } Bean public SpringTemplateEngine thymeleafTemplateEngine(ITemplateResolver templateResolver) { SpringTemplateEngine templateEngine new SpringTemplateEngine(); templateEngine.setTemplateResolver(templateResolver); return templateEngine; } }4. 邮件发送服务实现4.1 基础邮件服务javaimport lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; 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.Service; import org.thymeleaf.context.Context; import org.thymeleaf.spring5.SpringTemplateEngine; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import java.io.File; import java.util.Map; Slf4j Service RequiredArgsConstructor public class MailService { private final JavaMailSender mailSender; private final SpringTemplateEngine templateEngine; Value(${spring.mail.username}) private String from; Value(${mail.from-name:系统通知}) private String fromName; /** * 发送简单文本邮件 * param to 收件人 * param subject 主题 * param content 内容 */ public void sendSimpleMail(String to, String subject, String content) { try { SimpleMailMessage message new SimpleMailMessage(); message.setFrom(from); message.setTo(to.split(,)); message.setSubject(subject); message.setText(content); mailSender.send(message); log.info(简单邮件发送成功: to{}, subject{}, to, subject); } catch (Exception e) { log.error(简单邮件发送失败: to{}, subject{}, to, subject, e); throw new RuntimeException(邮件发送失败, e); } } /** * 发送 HTML 邮件 * param to 收件人多个用逗号分隔 * param subject 主题 * param htmlContent HTML 内容 */ public void sendHtmlMail(String to, String subject, String htmlContent) { try { MimeMessage message mailSender.createMimeMessage(); MimeMessageHelper helper new MimeMessageHelper(message, true, UTF-8); helper.setFrom(from, fromName); helper.setTo(to.split(,)); helper.setSubject(subject); helper.setText(htmlContent, true); mailSender.send(message); log.info(HTML邮件发送成功: to{}, subject{}, to, subject); } catch (Exception e) { log.error(HTML邮件发送失败: to{}, subject{}, to, subject, e); throw new RuntimeException(邮件发送失败, e); } } /** * 发送带附件的邮件 * param to 收件人 * param subject 主题 * param htmlContent HTML 内容 * param attachments 附件列表文件路径 - 附件名称 */ public void sendAttachmentMail(String to, String subject, String htmlContent, MapString, String attachments) { try { MimeMessage message mailSender.createMimeMessage(); MimeMessageHelper helper new MimeMessageHelper(message, true, UTF-8); helper.setFrom(from, fromName); helper.setTo(to.split(,)); helper.setSubject(subject); helper.setText(htmlContent, true); // 添加附件 if (attachments ! null !attachments.isEmpty()) { for (Map.EntryString, String entry : attachments.entrySet()) { FileSystemResource file new FileSystemResource(new File(entry.getKey())); helper.addAttachment(entry.getValue(), file); } } mailSender.send(message); log.info(带附件邮件发送成功: to{}, subject{}, to, subject); } catch (Exception e) { log.error(带附件邮件发送失败: to{}, subject{}, to, subject, e); throw new RuntimeException(邮件发送失败, e); } } /** * 使用 Thymeleaf 模板发送邮件 * param to 收件人 * param subject 主题 * param templateName 模板名称不含后缀 * param variables 模板变量 */ public void sendTemplateMail(String to, String subject, String templateName, MapString, Object variables) { try { Context context new Context(); if (variables ! null) { variables.forEach(context::setVariable); } String htmlContent templateEngine.process(templateName, context); sendHtmlMail(to, subject, htmlContent); log.info(模板邮件发送成功: to{}, subject{}, template{}, to, subject, templateName); } catch (Exception e) { log.error(模板邮件发送失败: to{}, subject{}, template{}, to, subject, templateName, e); throw new RuntimeException(邮件发送失败, e); } } }4.2 邮件模板示例创建模板文件src/main/resources/templates/mail/exception-alert.htmlhtml!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 title系统异常通知/title style body { font-family: Microsoft YaHei, Arial, sans-serif; line-height: 1.6; color: #333; background-color: #f5f5f5; margin: 0; padding: 20px; } .container { max-width: 800px; margin: 0 auto; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); overflow: hidden; } .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; text-align: center; } .header h1 { margin: 0; font-size: 24px; } .header .time { margin-top: 10px; opacity: 0.9; font-size: 14px; } .content { padding: 30px; } .exception-info { background: #fff3f3; border-left: 4px solid #f44336; padding: 15px; margin-bottom: 20px; border-radius: 4px; } .exception-name { font-weight: bold; color: #f44336; margin-bottom: 10px; font-size: 18px; } .exception-message { font-family: monospace; background: #f8f8f8; padding: 10px; border-radius: 4px; overflow-x: auto; } .stack-trace { background: #f8f8f8; padding: 15px; border-radius: 4px; overflow-x: auto; font-family: monospace; font-size: 12px; margin: 20px 0; } .system-info { background: #f0f7ff; padding: 15px; border-radius: 4px; margin: 20px 0; } .system-info p { margin: 5px 0; } .footer { background: #f8f8f8; padding: 20px; text-align: center; color: #666; font-size: 12px; border-top: 1px solid #e0e0e0; } .badge { display: inline-block; background: #f44336; color: white; padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: bold; } .severity-high { background: #f44336; } .severity-medium { background: #ff9800; } .severity-low { background: #4caf50; } /style /head body div classcontainer div classheader h1⚠️ 系统异常告警/h1 div classtime span th:text${timestamp}/span /div /div div classcontent div classexception-info div classexception-name span classbadge th:classappendseverity- ${severity}异常/span span th:text${exceptionName}/span /div div classexception-message th:text${message}/div /div div classsystem-info h3 系统信息/h3 pstrong应用名称/strong span th:text${applicationName}/span/p pstrong服务器IP/strong span th:text${serverIp}/span/p pstrong请求URL/strong span th:text${requestUrl}/span/p pstrong请求方法/strong span th:text${requestMethod}/span/p pstrong请求参数/strong span th:text${requestParams}/span/p pstrong用户IP/strong span th:text${clientIp}/span/p pstrong用户代理/strong span th:text${userAgent}/span/p /div div th:if${stackTrace ! null} h3 异常堆栈/h3 div classstack-trace pre th:text${stackTrace}/pre /div /div div th:if${suggestion ! null} h3 处理建议/h3 div classexception-info stylebackground: #e8f5e9; border-left-color: #4caf50; div th:text${suggestion}/div /div /div /div div classfooter p此为系统自动发送的异常告警邮件请勿直接回复。/p p如需帮助请联系系统管理员。/p /div /div /body /html5. 异常通知系统5.1 异常信息封装类javaimport lombok.Builder; import lombok.Data; import java.io.PrintWriter; import java.io.StringWriter; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Map; Data Builder public class ExceptionAlert { private String applicationName; // 应用名称 private String serverIp; // 服务器IP private String exceptionName; // 异常名称 private String message; // 异常消息 private String stackTrace; // 异常堆栈 private String severity; // 严重级别 (high/medium/low) private String requestUrl; // 请求URL private String requestMethod; // 请求方法 private MapString, String[] requestParams; // 请求参数 private String clientIp; // 客户端IP private String userAgent; // 用户代理 private String timestamp; // 发生时间 private String suggestion; // 处理建议 /** * 从异常构建告警信息 */ public static ExceptionAlert fromException(Exception e, String applicationName, String serverIp) { StringWriter sw new StringWriter(); PrintWriter pw new PrintWriter(sw); e.printStackTrace(pw); return ExceptionAlert.builder() .applicationName(applicationName) .serverIp(serverIp) .exceptionName(e.getClass().getSimpleName()) .message(e.getMessage()) .stackTrace(sw.toString()) .severity(determineSeverity(e)) .timestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))) .suggestion(getSuggestion(e)) .build(); } /** * 根据异常类型判断严重级别 */ private static String determineSeverity(Exception e) { if (e instanceof NullPointerException || e instanceof IllegalArgumentException) { return medium; } else if (e instanceof RuntimeException) { return high; } else if (e instanceof Exception) { return low; } return medium; } /** * 获取处理建议 */ private static String getSuggestion(Exception e) { if (e instanceof NullPointerException) { return 请检查相关对象是否为 null确保在使用前进行判空处理。; } else if (e instanceof IllegalArgumentException) { return 请检查传入的参数是否符合要求确保参数格式和范围正确。; } else if (e instanceof RuntimeException) { return 请查看详细堆栈信息定位具体问题代码位置。; } return 请查看详细错误信息并根据堆栈定位问题。; } }5.2 异常通知服务javaimport lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; Slf4j Service RequiredArgsConstructor public class ExceptionAlertService { private final MailService mailService; Value(${mail.alert-recipients}) private String alertRecipients; Value(${mail.alert-enabled:true}) private boolean alertEnabled; Value(${spring.application.name:未知应用}) private String applicationName; /** * 发送异常告警邮件 */ public void sendExceptionAlert(Exception e) { if (!alertEnabled) { log.debug(邮件告警已禁用不发送异常通知); return; } try { // 获取当前请求信息 HttpServletRequest request getCurrentRequest(); String serverIp getServerIp(); // 构建告警信息 ExceptionAlert alert ExceptionAlert.fromException(e, applicationName, serverIp); // 填充请求相关信息 if (request ! null) { alert.setRequestUrl(request.getRequestURL().toString()); alert.setRequestMethod(request.getMethod()); alert.setRequestParams(request.getParameterMap()); alert.setClientIp(getClientIp(request)); alert.setUserAgent(request.getHeader(User-Agent)); } // 发送邮件 sendAlertEmail(alert); } catch (Exception ex) { log.error(发送异常告警邮件失败, ex); } } /** * 发送自定义告警 */ public void sendCustomAlert(String title, String content, String severity) { if (!alertEnabled) { return; } try { MapString, Object variables new HashMap(); variables.put(title, title); variables.put(content, content); variables.put(severity, severity); variables.put(timestamp, java.time.LocalDateTime.now() .format(java.time.format.DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); variables.put(applicationName, applicationName); variables.put(serverIp, getServerIp()); mailService.sendTemplateMail(alertRecipients, 【系统告警】 title, custom-alert, variables); log.info(自定义告警邮件发送成功: title{}, title); } catch (Exception e) { log.error(发送自定义告警失败, e); } } /** * 发送告警邮件 */ private void sendAlertEmail(ExceptionAlert alert) { MapString, Object variables new HashMap(); variables.put(exceptionName, alert.getExceptionName()); variables.put(message, alert.getMessage()); variables.put(stackTrace, alert.getStackTrace()); variables.put(severity, alert.getSeverity()); variables.put(timestamp, alert.getTimestamp()); variables.put(applicationName, alert.getApplicationName()); variables.put(serverIp, alert.getServerIp()); variables.put(requestUrl, alert.getRequestUrl()); variables.put(requestMethod, alert.getRequestMethod()); variables.put(clientIp, alert.getClientIp()); variables.put(userAgent, alert.getUserAgent()); variables.put(suggestion, alert.getSuggestion()); // 格式化请求参数 if (alert.getRequestParams() ! null !alert.getRequestParams().isEmpty()) { StringBuilder params new StringBuilder(); alert.getRequestParams().forEach((key, values) - { params.append(key).append(); if (values ! null values.length 0) { params.append(String.join(,, values)); } params.append(; ); }); variables.put(requestParams, params.toString()); } else { variables.put(requestParams, 无); } String subject String.format(【%s】%s - %s, alert.getSeverity().toUpperCase(), alert.getExceptionName(), alert.getApplicationName()); mailService.sendTemplateMail(alertRecipients, subject, exception-alert, variables); } /** * 获取当前请求 */ private HttpServletRequest getCurrentRequest() { ServletRequestAttributes attributes (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); return attributes ! null ? attributes.getRequest() : null; } /** * 获取客户端真实IP */ private String getClientIp(HttpServletRequest request) { String ip request.getHeader(X-Forwarded-For); if (ip null || ip.isEmpty() || unknown.equalsIgnoreCase(ip)) { ip request.getHeader(Proxy-Client-IP); } if (ip null || ip.isEmpty() || unknown.equalsIgnoreCase(ip)) { ip request.getHeader(WL-Proxy-Client-IP); } if (ip null || ip.isEmpty() || unknown.equalsIgnoreCase(ip)) { ip request.getHeader(HTTP_CLIENT_IP); } if (ip null || ip.isEmpty() || unknown.equalsIgnoreCase(ip)) { ip request.getHeader(HTTP_X_FORWARDED_FOR); } if (ip null || ip.isEmpty() || unknown.equalsIgnoreCase(ip)) { ip request.getRemoteAddr(); } // 多个代理的情况取第一个IP if (ip ! null ip.contains(,)) { ip ip.split(,)[0].trim(); } return ip; } /** * 获取服务器IP */ private String getServerIp() { try { return InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { return unknown; } } }5.3 全局异常处理器javaimport lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import java.util.HashMap; import java.util.Map; Slf4j RestControllerAdvice RequiredArgsConstructor public class GlobalExceptionHandler { private final ExceptionAlertService alertService; /** * 处理所有未捕获的异常 */ ExceptionHandler(Exception.class) ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public MapString, Object handleException(Exception e) { log.error(系统异常, e); // 发送异常告警邮件 alertService.sendExceptionAlert(e); MapString, Object response new HashMap(); response.put(code, 500); response.put(message, 系统内部错误请稍后再试); response.put(error, e.getMessage()); return response; } /** * 处理自定义业务异常 */ ExceptionHandler(BusinessException.class) ResponseStatus(HttpStatus.BAD_REQUEST) public MapString, Object handleBusinessException(BusinessException e) { log.warn(业务异常: {}, e.getMessage()); MapString, Object response new HashMap(); response.put(code, e.getCode()); response.put(message, e.getMessage()); return response; } /** * 处理参数校验异常 */ ExceptionHandler(IllegalArgumentException.class) ResponseStatus(HttpStatus.BAD_REQUEST) public MapString, Object handleIllegalArgumentException(IllegalArgumentException e) { log.warn(参数异常: {}, e.getMessage()); MapString, Object response new HashMap(); response.put(code, 400); response.put(message, e.getMessage()); return response; } }5.4 业务异常类javaimport lombok.Getter; Getter public class BusinessException extends RuntimeException { private final Integer code; public BusinessException(String message) { super(message); this.code 500; } public BusinessException(Integer code, String message) { super(message); this.code code; } public BusinessException(String message, Throwable cause) { super(message, cause); this.code 500; } }6. 高级功能6.1 邮件发送队列防止阻塞javaimport lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; Slf4j Component public class MailQueueService { private final MailService mailService; private final ThreadPoolExecutor executor; Autowired public MailQueueService(MailService mailService) { this.mailService mailService; this.executor new ThreadPoolExecutor( 2, 5, 60, TimeUnit.SECONDS, new LinkedBlockingQueue(1000), new ThreadPoolExecutor.CallerRunsPolicy() ); } /** * 异步发送邮件 */ Async public void sendAsync(Runnable mailTask) { executor.execute(() - { try { mailTask.run(); } catch (Exception e) { log.error(异步发送邮件失败, e); } }); } /** * 发送简单邮件异步 */ public void sendSimpleMailAsync(String to, String subject, String content) { sendAsync(() - mailService.sendSimpleMail(to, subject, content)); } /** * 发送模板邮件异步 */ public void sendTemplateMailAsync(String to, String subject, String template, MapString, Object variables) { sendAsync(() - mailService.sendTemplateMail(to, subject, template, variables)); } }6.2 邮件发送频率限制javaimport com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; Component public class MailRateLimiter { // 限制每个接收者每小时最多接收10封邮件 private static final int MAX_EMAILS_PER_HOUR 10; private final CacheString, AtomicInteger cache CacheBuilder.newBuilder() .expireAfterWrite(1, TimeUnit.HOURS) .build(); /** * 检查是否允许发送邮件 */ public boolean allowSend(String recipient) { try { AtomicInteger count cache.get(recipient, AtomicInteger::new); if (count.get() MAX_EMAILS_PER_HOUR) { return false; } count.incrementAndGet(); return true; } catch (Exception e) { return true; // 缓存异常时允许发送 } } /** * 获取当前小时已发送数量 */ public int getCurrentCount(String recipient) { AtomicInteger count cache.getIfPresent(recipient); return count ! null ? count.get() : 0; } }6.3 健康检查与重试机制javaimport lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.availability.AvailabilityChangeEvent; import org.springframework.boot.availability.ReadinessState; import org.springframework.context.ApplicationEventPublisher; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; Slf4j Component public class MailHealthChecker { Autowired private MailService mailService; Autowired private ApplicationEventPublisher eventPublisher; private boolean mailAvailable true; /** * 每5分钟检查一次邮件服务状态 */ Scheduled(fixedDelay 300000) public void checkMailHealth() { try { // 发送测试邮件到系统邮箱 mailService.sendSimpleMail(adminlocalhost, Health Check, Mail service is working); if (!mailAvailable) { log.info(邮件服务已恢复); mailAvailable true; // 发布服务恢复事件 AvailabilityChangeEvent.publish(eventPublisher, this, ReadinessState.ACCEPTING_TRAFFIC); } } catch (Exception e) { if (mailAvailable) { log.error(邮件服务异常, e); mailAvailable false; // 发布服务不可用事件 AvailabilityChangeEvent.publish(eventPublisher, this, ReadinessState.REFUSING_TRAFFIC); } } } public boolean isMailAvailable() { return mailAvailable; } }7. 测试用例7.1 单元测试javaimport org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.HashMap; import java.util.Map; SpringBootTest class MailServiceTest { Autowired private MailService mailService; Test void testSendSimpleMail() { mailService.sendSimpleMail(testexample.com, 测试邮件, 这是一封测试邮件); } Test void testSendHtmlMail() { String html h1测试邮件/h1p这是一封HTML邮件/p; mailService.sendHtmlMail(testexample.com, HTML测试, html); } Test void testSendTemplateMail() { MapString, Object variables new HashMap(); variables.put(username, 张三); variables.put(content, 您的订单已发货); mailService.sendTemplateMail(testexample.com, 订单通知, order-notification, variables); } }7.2 异常告警测试javaimport org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; SpringBootTest class ExceptionAlertTest { Autowired private ExceptionAlertService alertService; Test void testExceptionAlert() { try { // 模拟空指针异常 String str null; str.length(); } catch (Exception e) { alertService.sendExceptionAlert(e); } } Test void testCustomAlert() { alertService.sendCustomAlert(数据库连接失败, 数据库连接池已耗尽请检查数据库状态, high); } }8. 常见问题与解决方案8.1 邮件发送超时问题发送邮件时出现超时异常解决增加超时配置yamlspring: mail: properties: mail: smtp: connectiontimeout: 5000 # 连接超时毫秒 timeout: 5000 # 读取超时 writetimeout: 5000 # 写入超时8.2 授权码错误错误信息535 Error: authentication failed原因使用了 QQ 密码而非授权码解决在 QQ 邮箱设置中获取正确的 16 位授权码8.3 SSL 连接失败错误信息javax.net.ssl.SSLHandshakeException解决添加 SSL 配置或使用 587 端口yamlspring: mail: port: 587 properties: mail: smtp: starttls: enable: true required: true8.4 邮件被识别为垃圾邮件解决建议使用企业邮箱域名设置正确的邮件主题和内容格式添加退订链接控制发送频率8.5 生产环境安全配置yaml# 使用环境变量存储敏感信息 spring: mail: username: ${QQ_MAIL_USERNAME} password: ${QQ_MAIL_PASSWORD} # 生产环境关闭调试 debug: false9. 总结9.1 核心功能✅ QQ 邮箱 SMTP 配置✅ 多种邮件类型支持文本、HTML、附件、模板✅ 完善的异常通知系统✅ 全局异常捕获与邮件告警✅ 邮件发送队列与异步处理✅ 频率限制与健康检查9.2 最佳实践敏感信息保护授权码使用环境变量不硬编码异步发送避免邮件发送阻塞主业务频率限制防止邮件轰炸降级策略邮件服务不可用时不影响主业务模板管理使用 Thymeleaf 管理邮件模板便于维护监控告警邮件服务本身也需要监控