使用JavaMail API递归提取EML文件中嵌套.msg附件及子附件
我刚好处理过类似的嵌套附件提取场景,给你一套基于JavaMail API的完整实现,还搭配了Apache POI来解析.msg文件里的嵌套附件,直接就能用:
第一步:添加依赖
先把需要的Maven依赖加上(Gradle的话可以自行转换),JavaMail用来读取EML,POI-Scratchpad用来解析.msg文件:
<!-- JavaMail API 处理EML邮件 --> <dependency> <groupId>com.sun.mail</groupId> <artifactId>javax.mail</artifactId> <version>1.6.2</version> </dependency> <!-- Apache POI 解析.msg格式附件 --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-scratchpad</artifactId> <version>5.2.3</version> </dependency>
第二步:完整提取代码
下面是一个工具类,包含递归提取EML中所有附件(包括.msg里的多层嵌套附件)的逻辑,注释写得很清楚:
import org.apache.poi.hsmf.MAPIMessage; import org.apache.poi.hsmf.exceptions.ChunkNotFoundException; import javax.mail.*; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMultipart; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Properties; public class EmlAttachmentExtractor { // 附件保存的根目录,可以自行修改 private static final String SAVE_ROOT = "./extracted_eml_attachments"; public static void main(String[] args) throws Exception { // 初始化保存目录 File rootDir = new File(SAVE_ROOT); if (!rootDir.exists()) { rootDir.mkdirs(); } // 替换成你的EML文件路径 File targetEml = new File("./your_email.eml"); Session mailSession = Session.getDefaultInstance(new Properties()); Message emlMessage = new MimeMessage(mailSession, targetEml.toURI().toURL().openStream()); // 启动递归提取流程 traverseMessageParts(emlMessage, rootDir); System.out.println("✅ 所有附件提取完成!保存路径:" + rootDir.getAbsolutePath()); } /** * 递归遍历邮件的所有内容部分,处理附件 */ private static void traverseMessageParts(Part part, File currentDir) throws MessagingException, IOException { // 判断当前Part是否是附件(两种情况:明确标记为ATTACHMENT,或者有文件名) boolean isAttachment = Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition()) || (part.getFileName() != null && !part.getFileName().trim().isEmpty()); if (isAttachment) { String fileName = part.getFileName(); // 保存当前附件 saveSingleAttachment(part, currentDir, fileName); // 如果是.msg文件,进一步提取其内部的附件 if (fileName.toLowerCase().endsWith(".msg")) { File msgFile = new File(currentDir, fileName); // 为当前.msg创建单独的子目录存放其附件 File msgAttachDir = new File(currentDir, fileName.replace(".msg", "_sub_attachments")); extractMsgInnerAttachments(msgFile, msgAttachDir); } return; } // 如果是Multipart类型,递归处理每个子Part if (part.getContent() instanceof MimeMultipart) { MimeMultipart multipart = (MimeMultipart) part.getContent(); for (int i = 0; i < multipart.getCount(); i++) { BodyPart subPart = multipart.getBodyPart(i); traverseMessageParts(subPart, currentDir); } } } /** * 保存单个附件到指定目录,处理文件名特殊字符 */ private static void saveSingleAttachment(Part part, File saveDir, String fileName) throws MessagingException, IOException { // 替换文件名中的非法字符,避免创建文件失败 String safeFileName = fileName.replaceAll("[^a-zA-Z0-9\\.\\-_]", "_"); File outputFile = new File(saveDir, safeFileName); try (InputStream in = part.getInputStream(); FileOutputStream out = new FileOutputStream(outputFile)) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } System.out.println("📦 已保存附件:" + outputFile.getAbsolutePath()); } } /** * 解析.msg文件,提取其中的所有附件,支持递归处理嵌套的.msg附件 */ private static void extractMsgInnerAttachments(File msgFile, File saveDir) throws IOException { if (!saveDir.exists()) { saveDir.mkdirs(); } try (MAPIMessage msg = new MAPIMessage(msgFile.getAbsolutePath())) { MAPIMessage.Attachment[] attachments = msg.getAttachmentFiles(); if (attachments == null || attachments.length == 0) { System.out.println("ℹ️ 该.msg文件没有内部附件:" + msgFile.getAbsolutePath()); return; } for (MAPIMessage.Attachment attachment : attachments) { // 优先取长文件名,没有的话用短文件名 String fileName = attachment.getLongFilename(); if (fileName == null || fileName.trim().isEmpty()) { fileName = attachment.getShortFilename(); } String safeFileName = fileName.replaceAll("[^a-zA-Z0-9\\.\\-_]", "_"); File outputFile = new File(saveDir, safeFileName); try (FileOutputStream out = new FileOutputStream(outputFile)) { out.write(attachment.getAttachmentData()); System.out.println("📥 已提取.msg内部附件:" + outputFile.getAbsolutePath()); // 如果提取出的附件还是.msg,继续递归处理 if (safeFileName.toLowerCase().endsWith(".msg")) { extractMsgInnerAttachments(outputFile, new File(saveDir, safeFileName.replace(".msg", "_sub_attachments"))); } } } } catch (ChunkNotFoundException e) { System.err.println("❌ 解析.msg文件失败:" + msgFile.getAbsolutePath() + ",错误信息:" + e.getMessage()); } } }
关键说明
- 递归逻辑:
traverseMessageParts方法会遍历EML的所有内容部分,不管嵌套多少层,只要是附件就会被保存;如果是.msg附件,会调用extractMsgInnerAttachments继续提取其内部的附件。 - 文件名安全处理:替换了文件名中的非法字符,避免在不同操作系统下创建文件失败。
- .msg解析:用Apache POI的
MAPIMessage处理.msg文件,支持提取其中的附件,并且同样递归处理嵌套的.msg。 - 可扩展性:如果不需要解析.msg里的附件,可以直接去掉POI依赖和
extractMsgInnerAttachments相关代码。
内容的提问来源于stack exchange,提问作者RRM
相关产品推荐
相关产品推荐

