使用的工具
| 工具 | 用途 |
|---|---|
| Spring JavaMailSender | 邮件发送核心,运行时按租户动态构建 |
| EasyExcel | 流式生成 Excel,写入 ByteArrayOutputStream,避免落盘 |
| Spring @Async | 自定义线程池异步处理,API 快速返回 |
| ResourceBundle | 国际化解耦,根据请求头 Accept-Language 切换 Excel 表头语言 |
设计思路
核心原则:同步做数据准备,异步做文件生成和投递。
Controller → Service(同步) → @Async(异步)
API入口 参数校验 Excel构建
分页拉取消息 i18n国际化
查设备/模板/产品 SMTP投递
↓
立即返回 200
- 同步阶段:参数校验 → 循环分页拉取聊天记录 → 查设备信息获取租户 → 查邮件模板 → 查产品名称
- 异步阶段 :根据语言加载 i18n 资源 → EasyExcel 生成字节流 → 动态构建
JavaMailSender→MimeMessageHelper带附件发送 - 邮件模板内容由通知服务统一管理,支持变量替换(产品名、设备名、导出时间、格式)
关键代码
异步提交入口:
java
public BaseResult<?> sendChatHistoryEmail(SendChatHistoryDTO dto) {
// 1. 参数校验
// 2. 循环分页拉取 messages
// 3. 查 device / template / productName
// 4. 提交异步任务,立即返回
buildAndSendEmailAsync(messages, productName, pk, dk,
languageCode, device.getTenantId(), template, dto.getUserEmail());
return BaseResult.success();
}
异步方法 --- Excel + 邮件:
java
@Async(ThreadPoolConfiguration.AIBIZ_EXECUTOR)
void buildAndSendEmailAsync(...) {
// i18n 加载
Locale locale = languageCode.contains("en") ? Locale.US : Locale.SIMPLIFIED_CHINESE;
ResourceBundle bundle = ResourceBundle.getBundle("i18n.messages", locale);
// EasyExcel 生成字节流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ExcelWriter writer = EasyExcel.write(bos).build();
WriteSheet sheet = EasyExcel.writerSheet(bundle.getString("EXPORT_SHEET_NAME"))
.head(headList).build();
writer.write(rowList, sheet);
writer.finish();
// 发送
sendEmail(to, productName, pk, dk, exportTime, "excel",
bos.toByteArray(), fileName + ".xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
tenantId, template);
}
邮件发送核心:
java
private void sendEmail(...) throws MessagingException {
// 按租户动态构建 MailSender
String cfg = clientForNotice.queryNtcAccountInfo(NoticeModeEnum.EMAIL.name(), tenantId);
EmailTunnelConfig config = JSON.parseObject(cfg, EmailTunnelConfig.class);
JavaMailSender mailSender = buildMailSender(config);
MimeMessageHelper helper = new MimeMessageHelper(mailSender.createMimeMessage(), true, "UTF-8");
helper.setFrom(config.getFrom());
helper.setTo(to);
helper.setSubject(template.getTemplateName());
helper.setText(StrUtil.format(template.getTemplateContent(), ...), true);
// Lambda 延迟加载附件流
helper.addAttachment(attachmentName, () -> new ByteArrayInputStream(fileBytes), contentType);
mailSender.send(helper.getMimeMessage());
}
邮件发送者配置:
java
@Data
public class EmailTunnelConfig {
private String nickName; // 发件人昵称
private String from; // 发件人邮箱
private String username; // SMTP 账号
private String password; // SMTP 密码/授权码
private String host; // SMTP 服务器
private Integer port; // SMTP 端口
}
// 配置按租户存储,运行时动态拉取
String accountInfo = clientForNotice.queryNtcAccountInfo(NoticeModeEnum.EMAIL.name(), tenantId);
EmailTunnelConfig config = JSON.parseObject(accountInfo, EmailTunnelConfig.class);
// 动态构建 MailSender(多租户场景不能用单例 Bean)
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost(config.getHost());
sender.setPort(config.getPort());
sender.setUsername(config.getUsername());
sender.setPassword(config.getPassword());
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
常见邮箱 SMTP 参考:QQ(smtp.qq.com:587)、163(smtp.163.com:25/465)、企业微信(smtp.exmail.qq.com:587)、阿里企业邮箱(smtp.mxhichina.com:465)、Outlook(smtp.office365.com:587)。
设计要点
- 多租户邮箱 :
JavaMailSender运行时按tenantId动态构建,非单例 Bean - 拒绝策略 :
CallerRunsPolicy保证异步任务不丢失 - 附件流 :Lambda 延迟创建
ByteArrayInputStream,避免大文件占满内存 - i18n :
messages_zh_CN.properties/messages_en_US.properties,通过Accept-Language请求头自动切换