Spring Boot 邮件发送(带附件)实战

使用的工具

工具用途
Spring JavaMailSender邮件发送核心,运行时按租户动态构建
EasyExcel流式生成 Excel,写入 ByteArrayOutputStream,避免落盘
Spring @Async自定义线程池异步处理,API 快速返回
ResourceBundle国际化解耦,根据请求头 Accept-Language 切换 Excel 表头语言

设计思路

核心原则:同步做数据准备,异步做文件生成和投递

Controller → Service(同步) → @Async(异步)
  API入口      参数校验          Excel构建
             分页拉取消息        i18n国际化
             查设备/模板/产品     SMTP投递
                ↓
           立即返回 200
  1. 同步阶段:参数校验 → 循环分页拉取聊天记录 → 查设备信息获取租户 → 查邮件模板 → 查产品名称
  2. 异步阶段:根据语言加载 i18n 资源 → EasyExcel 生成字节流 → 动态构建 JavaMailSenderMimeMessageHelper 带附件发送
  3. 邮件模板内容由通知服务统一管理,支持变量替换(产品名、设备名、导出时间、格式)

关键代码

异步提交入口

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 + 邮件

@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);
}

邮件发送核心

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());
}

邮件发送者配置

@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,避免大文件占满内存
  • i18nmessages_zh_CN.properties / messages_en_US.properties,通过 Accept-Language 请求头自动切换
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值