脏唐臭汉邋遢宋 發表於 2025-5-9 14:28:00

将数据导出 Excel 并异步发送到指定邮箱:一次性能优化实战

<p>使用 Spring Boot 实现 Excel 导出 + 邮件异步发送功能,解决接口阻塞问题</p>
<p>一、背景介绍<br>最近我在开发一个跑团管理系统的数据导出功能,需求是将用户查询的成员信息、跑量统计等数据导出为 Excel 文件,并通过邮件自动发送至指定邮箱,而非让用户手动下载文件。<br>虽然功能逻辑看起来不复杂,但在实际开发过程中遇到了一些关键性问题:<br>导出 Excel 后发送邮件耗时较长,导致接口响应延迟;<br>QQ 邮箱 SMTP 校验严格,出现 550 The "From" header is missing or invalid 异常;<br>接口体验差,用户点击后需要等待很久才能收到响应。<br>于是我对整个流程进行了分析与重构,最终实现了高性能、高可用的数据导出 + 邮件发送功能。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>二、功能实现流程概览<br>用户发起请求,选择导出类型(成员列表 / 跑量统计等);<br>系统调用服务层获取数据;<br>使用 Apache POI 构建 Excel 文件并写入内存流(ByteArrayOutputStream);<br>将生成的 Excel 字节数组作为附件,使用 JavaMailSender 发送邮件;<br>返回接口响应,提示邮件已发送成功(实则后台异步执行);</p>
<p>&nbsp;</p>
<p>三、遇到的问题与解决方案<br>✅ 问题一:Excel 导出时报错 sheetName must not be null<br>原因:<br>Apache POI 要求每个 Sheet 必须有名称,否则抛出异常。<br>解决方案:<br>在创建 ExcelUtil 实例后显式设置 sheetName,例如:</p>
<p>ExcelUtil&lt;PersonalRunningGroupExportVO&gt; util = new ExcelUtil&lt;&gt;(PersonalRunningGroupExportVO.class);<br>util.setSheetName("跑团数据");</p>
<p>&nbsp;</p>
<p>✅ 问题二:QQ 邮箱发送失败,报错 550 The "From" header is missing or invalid<br>原因:<br>QQ 邮箱对发件人地址校验非常严格,要求 MAIL FROM 地址必须与登录账号一致,否则拒绝发送。<br>解决方案:<br>在邮件构建器中显式设置发件人地址:</p>
<p>MimeMessageHelper helper = new MimeMessageHelper(message, true);<br>helper.setFrom("your-qq@qq.com"); // 必须与 spring.mail.username 一致</p>
<p>同时确保配置文件中使用的是授权码,而非登录密码:</p>
<p><img src="https://img2024.cnblogs.com/blog/3353251/202505/3353251-20250509142227248-1058654527.png"></p>
<p>&nbsp;</p>
<p>spring:<br>mail:<br>    host: smtp.qq.com<br>    port: 587<br>    username: your-qq@qq.com<br>    password: 授权码<br>    properties:<br>      mail.smtp.auth: true<br>      mail.smtp.starttls.enable: true</p>
<p>&nbsp;</p>
<p>✅ 问题三:发送邮件过程很慢,影响接口响应时间<br>原因:<br>邮件发送属于 I/O 操作,受网络、服务器限制,同步执行会阻塞主线程,影响用户体验。<br>解决方案:<br>引入 Spring 的异步任务支持 @Async,将邮件发送操作放入子线程处理。<br>步骤如下:<br>1.在启动类上添加 @EnableAsync<br>2.创建邮件服务类 EmailService,并在方法上添加 @Async<br>3.Controller 中调用异步方法发送邮件,立即返回结果</p>
<p>示例代码:</p>
<p><img src="https://img2024.cnblogs.com/blog/3353251/202505/3353251-20250509142307534-563540067.png"></p>
<p>&nbsp;</p>
<p>@Async<br>public void sendExcelEmailAsync(String to, byte[] data, String fileName) {<br>    try {<br>      MimeMessage message = javaMailSender.createMimeMessage();<br>      MimeMessageHelper helper = new MimeMessageHelper(message, true);<br>      helper.setFrom("your-qq@qq.com");<br>      helper.setTo(to);<br>      helper.setSubject("导出的跑团数据");<br>      helper.setText("请查收附件中的跑团数据。");<br>      helper.addAttachment(fileName + ".xlsx", new ByteArrayResource(data));<br>      javaMailSender.send(message);<br>    } catch (Exception e) {<br>      log.error("邮件发送失败:{}", e.getMessage());<br>    }<br>}</p>
<p>四、优化效果<br>优化前<br>接口需等待邮件发送完成才返回<br>用户需等待 3~5 秒甚至超时<br>多次请求客易造成线程阻塞<br>QQ 邮箱频繁报错无法发送<br>优化后<br>接口立即返回,邮件后台异步发送<br>接口响应时间缩短至 300ms 内<br>支持并发发送,提升吞吐能力<br>设置 From 后成功发送,兼客性强</p>
<p>&nbsp;</p>
<p>五、总结<br>这次开发让我深刻体会到:<br>邮件发送应尽量避免阻塞主线程,建议统一走异步化处理;<br>Spring Boot 提供了强大的异步支持,只需简单配置即可使用;<br>QQ 邮箱对发件人地址校验严格,务必保证 From 和 Username 一致;<br>使用 ByteArrayOutputStream 替代临时文件,更安全高效;<br>合理封装 ExcelUtil 工具类,提高复用性和可维护性。</p>
<p>&nbsp;</p>
<p>六、完整功能点清单(便于读者参考)<br>功能模块&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;技术点<br>数据导出&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Apache POl<br>Excel写入内存            ByteArrayOutputStream<br>邮件发送              JavaMailSender/Jakarta Mail<br>异步处理            @Async + ThreadPoolTaskExecutor<br>邮箱验证              SMTP from 一致性校验<br>接口优化              非阻塞响应设计<br>邮箱类型             QQ/Gmail/网易等通用适配<br><br></p>
<p>七、结语<br>如果你也在做类似的数据导出 + 邮件通知功能,希望这篇文章能帮你少踩坑,快速上线稳定版本。如有疑问欢迎留言交流,也欢迎你分享你的优化思路!</p>
<p><br><br><br><br><br><br></p><br><br>
来源:https://www.cnblogs.com/wecandoallthings/p/18868141
頁: [1]
查看完整版本: 将数据导出 Excel 并异步发送到指定邮箱:一次性能优化实战