發送帶附件的郵件,本質上是構造一棵 MimeMultipart 樹:
MimeMultipart("mixed") —— 混合類型,同時包含正文與附件;MimeBodyPart 設置 text/html 正文;MimeBodyPart,通過 FileDataSource 綁定文件並設置 Content-Disposition: attachment。public final class MailUtil {
private MailUtil() {} // 工具類禁止實例化
/** 發送郵件統一入口 */
public static Result send(MailConfig config) {
try {
Session session = createSession(config);
MimeMessage message = buildMessage(session, config);
Transport.send(message);
return Result.success("發送成功");
} catch (AuthenticationFailedException e) {
return Result.fail("認證失敗:用戶名/密碼錯誤");
} catch (MessagingException e) {
return Result.fail("郵件構造/發送異常:" + e.getMessage());
} catch (Exception e) {
return Result.fail("未知錯誤:" + e.getMessage());
}
}
private static Session createSession(MailConfig config) {
Properties props = new Properties();
props.put("mail.smtp.host", config.getHost());
props.put("mail.smtp.port", config.getPort());
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true"); // TLS 加密
props.put("mail.smtp.timeout", "10000");
props.put("mail.smtp.connectiontimeout", "10000");
return Session.getInstance(props, new Authenticator() {
@Override protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(config.getUsername(), config.getPassword());
}
});
}
private static MimeMessage buildMessage(Session session, MailConfig config)
throws MessagingException, UnsupportedEncodingException {
MimeMessage msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(config.getFrom(), config.getPersonal(), "UTF-8"));
msg.setRecipients(Message.RecipientType.TO, parseAddresses(config.getTo()));
msg.setSubject(config.getSubject(), "UTF-8");
msg.setSentDate(new Date());
// 構建 Multipart 容器
MimeMultipart multipart = new MimeMultipart("mixed");
// 1. HTML 正文
MimeBodyPart htmlPart = new MimeBodyPart();
htmlPart.setContent(config.getHtmlContent(), "text/html;charset=UTF-8");
multipart.addBodyPart(htmlPart);
// 2. 附件們
for (String filePath : config.getAttachments()) {
MimeBodyPart attachPart = new MimeBodyPart();
FileDataSource fds = new FileDataSource(filePath);
attachPart.setDataHandler(new DataHandler(fds));
// 解決中文附件名亂碼 (RFC 2231 編碼)
attachPart.setFileName(MimeUtility.encodeText(fds.getName(), "UTF-8", null));
multipart.addBodyPart(attachPart);
}
msg.setContent(multipart);
msg.saveChanges(); // 必須調用,更新 Headers
return msg;
}
private static InternetAddress[] parseAddresses(String commaSeparated)
throws AddressException, UnsupportedEncodingException {
return Arrays.stream(commaSeparated.split("\\s*,\\s*"))
.map(s -> new InternetAddress(s.trim()))
.toArray(InternetAddress[]::new);
}
/** 配置類 */
@Data @Builder @AllArgsConstructor @NoArgsConstructor
public static class MailConfig {
private String host; // smtp.qq.com
private int port; // 465/587
private String username; // 發件賬號
private String password; // 授權碼(非登錄密碼)
private String from; // 發件地址
private String personal; // 發件人顯示名
private String to; // 收件人,逗號分隔
private String subject; // 主題
private String htmlContent; // HTML 正文
private List attachments; // 附件絕對路徑列表
}
/** 結果封裝 */
@Data @AllArgsConstructor
public static class Result {
private boolean success;
private String message;
public static Result success(String msg) { return new Result(true, msg); }
public static Result fail(String msg) { return new Result(false, msg); }
}
}
MailConfig config = MailConfig.builder()
.host("smtp.qq.com").port(465)
.username("xxx@qq.com").password("auth_code_xxx")
.from("xxx@qq.com").personal("系統通知")
.to("user1@example.com, user2@example.com")
.subject("【重要】月度報表")
.htmlContent("您好
請查收附件報表。
")
.attachments(Arrays.asList("/data/report.pdf", "/data/data.xlsx"))
.build();
MailUtil.Result result = MailUtil.send(config);
System.out.println(result.getMessage());
| 現象 | 原因 | 解決方案 |
|---|---|---|
| 163/郵箱顯示「有附件」圖標但無附件 | Multipart 子類型用錯,或未正確設置 Disposition | 根節點用 mixed,附件 Part 必須 setDisposition(Part.ATTACHMENT) |
| 中文附件名亂碼 | 未進行 RFC 2231 編碼 | MimeUtility.encodeText(name, "UTF-8", null) |
| 發送超時 | 未設置連接/讀取超時 | Props 設置 mail.smtp.timeout / connectiontimeout |
| SSL/TLS 握手失敗 | 端口/協議不匹配 | 465 用 SSL (props: mail.smtp.ssl.enable=true),587 用 STARTTLS |
| 大附件發送失敗 | 郵箱服務商限制單封大小(通常 20-50MB) | 分卷壓縮、雲存儲生成下載鏈接、或分多封發送 |
JavaMail 發送帶附件郵件的核心在於正確構建 MimeMultipart("mixed") 樹結構,並妥善處理編碼、超時、認證等工程化細節。本文提供的 MailUtil 封裝了完整的異常處理、TLS 加密、中文附件名支持,可直接用於生產環境。