Outlook EML文件附件解析问题:javax.mail获取文件名返回Null
解决MimeBodyPart.getFileName()返回Null的问题及通用EML解析库推荐
我之前也碰到过一模一样的情况——用javax.mail解析EML附件时,getFileName()经常返回null,尤其是遇到一些老邮件或者非标准编码的邮件头时。下面是我整理的解决方案,以及能递归解析EML的通用库推荐:
一、手动解析附件文件名(解决Null问题)
getFileName()失效的核心原因是邮件的Content-Disposition或Content-Type头里的文件名参数可能用了RFC 2047编码,或者格式不符合标准,导致JavaMail的默认解析逻辑没识别到。我们可以手动解析这些头字段,再处理编码:
完整的文件名获取代码
import javax.mail.Part; import javax.mail.MessagingException; import javax.mail.internet.MimeUtility; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Map; public class AttachmentUtils { public static String getAttachmentFileName(Part part) throws MessagingException, UnsupportedEncodingException { String fileName = part.getFileName(); // 第一步:尝试默认方法获取,失败则手动解析Content-Disposition头 if (fileName == null) { String contentDisposition = part.getHeader("Content-Disposition", null); if (contentDisposition != null) { int startIndex = contentDisposition.indexOf("filename="); if (startIndex != -1) { String fileNamePart = contentDisposition.substring(startIndex + 9); // 处理带引号的文件名 if (fileNamePart.startsWith("\"")) { fileNamePart = fileNamePart.substring(1, fileNamePart.lastIndexOf("\"")); } else { // 处理不带引号的文件名,截断到下一个分号 int endIndex = fileNamePart.indexOf(";"); if (endIndex != -1) { fileNamePart = fileNamePart.substring(0, endIndex); } } // 解码RFC 2047编码的文件名(比如中文、特殊字符) fileName = MimeUtility.decodeText(fileNamePart.trim()); } } } // 第二步:如果还是null,尝试从Content-Type的name参数提取 if (fileName == null) { String contentType = part.getContentType(); int nameIndex = contentType.indexOf("name="); if (nameIndex != -1) { String namePart = contentType.substring(nameIndex + 5); if (namePart.startsWith("\"")) { namePart = namePart.substring(1, namePart.lastIndexOf("\"")); } else { int endIndex = namePart.indexOf(";"); if (endIndex != -1) { namePart = namePart.substring(0, endIndex); } } fileName = MimeUtility.decodeText(namePart.trim()); } } // 第三步:如果文件名没有扩展名,根据Content-Type推断 if (fileName != null && !fileName.contains(".")) { String contentType = part.getContentType(); Map<String, String> extMapping = new HashMap<>(); extMapping.put("application/pdf", ".pdf"); extMapping.put("image/jpeg", ".jpg"); extMapping.put("image/png", ".png"); extMapping.put("application/msword", ".doc"); extMapping.put("application/vnd.openxmlformats-officedocument.wordprocessingml.document", ".docx"); extMapping.put("text/plain", ".txt"); // 可以根据需求补充更多类型映射 String extension = extMapping.get(contentType); fileName += extension != null ? extension : ".bin"; } return fileName; } }
你可以在获取附件时调用这个方法,代替直接用part.getFileName(),基本能解决大部分Null的情况。
二、通用EML递归解析库推荐
如果需要处理复杂的EML结构(比如嵌套的Multipart、混合格式正文等),自己写递归逻辑容易踩坑,推荐用成熟的开源库:
1. Apache James Mime4j
这是Apache专门处理MIME消息的库,对复杂MIME结构的解析、编码处理都比JavaMail更可靠,支持递归解析嵌套的Multipart:
依赖(Maven)
<dependency> <groupId>org.apache.james</groupId> <artifactId>apache-mime4j-core</artifactId> <version>0.8.10</version> </dependency> <dependency> <groupId>org.apache.james</groupId> <artifactId>apache-mime4j-dom</artifactId> <version>0.8.10</version> </dependency>
递归解析示例
import org.apache.james.mime4j.dom.Message; import org.apache.james.mime4j.dom.Multipart; import org.apache.james.mime4j.dom.BodyPart; import org.apache.james.mime4j.dom.TextBody; import org.apache.james.mime4j.message.DefaultMessageBuilder; import org.apache.james.mime4j.stream.MimeConfig; import java.io.FileInputStream; import java.io.IOException; import java.io.FileOutputStream; public class EmlParser { public static void parseEmlFile(String emlFilePath) throws IOException { MimeConfig config = MimeConfig.DEFAULT; DefaultMessageBuilder builder = new DefaultMessageBuilder(); try (FileInputStream fis = new FileInputStream(emlFilePath)) { Message message = builder.parseMessage(fis); // 提取正文 extractMessageBody(message); // 提取附件 extractMessageAttachments(message, "save/attachments/"); } } private static void extractMessageBody(Message message) throws IOException { Object body = message.getBody(); if (body instanceof TextBody) { TextBody textBody = (TextBody) body; String content = textBody.getReader().readAllLines().stream() .reduce((line1, line2) -> line1 + "\n" + line2) .orElse(""); System.out.println("邮件正文:\n" + content); } else if (body instanceof Multipart) { Multipart multipart = (Multipart) body; for (int i = 0; i < multipart.getCount(); i++) { extractMessageBody(multipart.getBodyPart(i)); } } } private static void extractMessageAttachments(Message message, String saveDir) throws IOException { Object body = message.getBody(); if (body instanceof Multipart) { Multipart multipart = (Multipart) body; for (int i = 0; i < multipart.getCount(); i++) { BodyPart part = multipart.getBodyPart(i); String disposition = part.getDisposition(); if ((disposition != null && (disposition.equalsIgnoreCase("attachment") || disposition.equalsIgnoreCase("inline"))) || part.getFilename() != null) { String fileName = part.getFilename(); // 兜底处理文件名(虽然Mime4j已经处理得很好,但以防万一) if (fileName == null) { // 可以复用上面AttachmentUtils的逻辑 } // 保存附件到指定目录 try (FileOutputStream fos = new FileOutputStream(saveDir + fileName)) { part.writeTo(fos); } System.out.println("已保存附件:" + fileName); } else { // 递归处理嵌套的Multipart extractMessageAttachments(part, saveDir); } } } } }
2. 基于JavaMail的递归解析
如果你不想引入新依赖,也可以用JavaMail自己实现递归解析,封装上面的文件名获取逻辑即可:
import javax.mail.MessagingException; import javax.mail.Part; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; import java.io.IOException; import java.io.FileOutputStream; public class JavaMailEmlParser { public static void parseEml(MimeMessage message) throws MessagingException, IOException { processPart(message); } private static void processPart(Part part) throws MessagingException, IOException { if (part.isMimeType("multipart/*")) { MimeMultipart multipart = (MimeMultipart) part.getContent(); for (int i = 0; i < multipart.getCount(); i++) { processPart(multipart.getBodyPart(i)); } } else { String disposition = part.getDisposition(); if ((disposition != null && (disposition.equalsIgnoreCase(Part.ATTACHMENT) || disposition.equalsIgnoreCase(Part.INLINE))) || part.getFileName() != null) { String fileName = AttachmentUtils.getAttachmentFileName(part); try (FileOutputStream fos = new FileOutputStream("save/attachments/" + fileName)) { part.writeTo(fos); } } else { // 处理正文 String content = part.getContent().toString(); System.out.println("正文内容:\n" + content); } } } }
内容的提问来源于stack exchange,提问作者RRM
相关产品推荐
相关产品推荐

