Java解析eml邮件格式文件

基本介绍

关于邮件的需求总是以邮件发送或接收为主,之前涉及的技术选型有Java Mail、Apache Commons Email、Spring Mail,由于工作上的需要对eml格式的文件进行解析,随了解了一下使用Java来解析eml格式文件的实现,所谓的eml格式是微软公司在Outlook中所使用的一种遵循RFC822及其后续扩展的文件格式,并成为各类电子邮件软件的通用格式(本地电子邮件文件存储的文件格式),它的来源是电子邮件的英文E-mail的缩写形式,可以用outlook邮箱打开,也可以用各种本地邮箱客户端打开,如Foxmail、Notes等。

经过数番资料的百科发现可以使用Java Mail、Mime4J(Apache James子项目模块)的解析为主,Apache James有一个基于一组丰富的现代高效组件的模块化体系结构,它最终提供了运行在JVM上的完整、稳定、安全和可扩展的邮件服务器集成。James由内部项目(Server、Mailet、Mailbox、Protocols、MPT)和外部项目(Hupa、Mime4J、jSieve、jSPF、jDKIM)组成,其中Mime4J是解析邮件数据文件的实现,参考如下图所示:

Apache James Mime4J提供了一个解析器,用于普通RFC822和MIME中的电子邮件流格式。解析器使用回调机制报告解析事件,例如实体标头的开始、身体等。如果您熟悉SAX公司XML解析器接口开始使用mime4j应该没有问题。Mime4j还可以用于构建电子邮件使用消息类,使用此工具mime4j自动处理对字段和正文进行解码,并使用大型临时文件附件。

解析实现

(1)使用QQ邮箱编辑一封邮件发送出去,并到处为本地eml文件,该邮件中的信息(可提取出来的参数)包含有以下几处:

A. 邮件标题:

B. 邮件内容,内容部分可能是纯文本和富文本,富文本包含HTML文件、内容区域的本地图片等;

C. 收件人,可以是多个收件人,收件人区分昵称与实际的邮箱地址;

D. 抄送人,与收件人一致;

E. 密送人,与收件人一致;

F. 附件,可以有多个附件文件;

G. 发送时间,是否存在时区问题;

H. 邮件大小,邮件文件的大小;

I. Message-ID 邮件唯一ID标识;

(2)邮件内容专门构建的略复杂,收件人和抄送人均为多人;邮件为多个;邮件内容包含HTML富文本段落和本地图片文件,参考如下图所示:

(3)导入maven依赖(2023年一月初发布了0.8.9版本),经过坐标的依赖实践,发现导入apache-mime4j-examples坐标可以直接把依赖的几个模块直接给导入,实际应用中需要考虑依赖其它模块,按需排除emamples和commons-logging依赖,参考坐标如下:

<!-- https://mvnrepository.com/artifact/org.apache.james/apache-mime4j-examples -->

<dependency>
    <groupId>org.apache.james</groupId>
    <artifactId>apache-mime4j-examples</artifactId>
    <version>0.8.9</version>
</dependency>

(4)解析实现示例:

package cn.chendd.eml;
 
 /**
  * Eml文件解析数据对象
  *
  * @author chendd
  * @date 2023/2/11 21:40
  */
 @Data
 public class EmlEntry {
 
     /**
      * 原始message对象
      */
     @JSONField(serialize = false)
     private Message message;
 
     /**
      * 消息ID
      */
     private String messageId;
 
     /**
      * 邮件主题
      */
     private String subject;
 
     /**
      * 纯文本邮件内容
      */
     private String textContent;
 
     /**
      * 富文本邮件内容
      */
     private String htmlContent;
 
     /**
      * 邮件附件
      */
     private List<MutableTriple<String , Long , InputStream>> attachments = Lists.newArrayList();
 
     /**
      * 发件人
      */
     private String from;
 
     /**
      * 收件人
      */
     private List<Pair<String , String>> to;
 
     /**
      * 抄送人
      */
     private List<Pair<String , String>> cc;
 
     /**
      * 密送人
      */
     private List<Pair<String , String>> bcc;
 
     /**
      * 邮件时间
      */
     private String dateTime;
 
}
package cn.chendd.eml;
 
 /**
  * 基本的eml文件解析示例
  *
  * @author chendd
  * @date 2023/2/11 19:26
  */
 public class EmlBasicTest {
 
     public static void main(String[] args) {
 
         try (InputStream inputStream = EmlBasicTest.class.getResourceAsStream("/Java解析Eml格式文件示例.eml")) {
             Message message = Message.Builder.of(inputStream).build();
             EmlEntry entry = new EmlEntry();
             entry.setMessage(message);
             entry.setMessageId(message.getMessageId());
             entry.setSubject(message.getSubject());
             entry.setFrom(address2String(message.getFrom()));
             entry.setTo(address2List(message.getTo()));
             entry.setCc(address2List(message.getCc()));
             entry.setBcc(address2List(message.getBcc()));
             TimeZone timeZone = TimeZone.getTimeZone(ZoneId.of("GMT"));
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
             sdf.setTimeZone(timeZone);
             entry.setDateTime(sdf.format(message.getDate()));
             MultipartImpl body = (MultipartImpl) message.getBody();
             List<Entity> bodyParts = body.getBodyParts();
             //邮件附件和内容
             outputContentAndAttachments(bodyParts , entry);
             System.out.println(JSON.toJSONString(entry , true));
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 
     /**
      * 递归处理邮件附件(附件区域附件、内容中的base64图片附件)、邮件内容(纯文本、html富文本)
      * @param bodyParts 邮件内容体
      * @param entry 数据对象
      * @throws IOException 异常处理
      */
     private static void outputContentAndAttachments(List<Entity> bodyParts , EmlEntry entry) throws IOException {
         for (Entity bodyPart : bodyParts) {
             Body bodyContent = bodyPart.getBody();
             String dispositionType = bodyPart.getDispositionType();
             if (ContentDispositionField.DISPOSITION_TYPE_ATTACHMENT.equals(dispositionType)) {
                 //正常的附件文件
                 BinaryBody binaryBody = (BinaryBody) bodyContent;
                 entry.getAttachments().add(MutableTriple.of(bodyPart.getFilename() , binaryBody.size() , binaryBody.getInputStream()));
                 continue;
             }
             if (bodyContent instanceof TextBody) {
                 //纯文本内容
                 TextBody textBody = (TextBody) bodyContent;
                 ContentTypeFieldLenientImpl contentType = (ContentTypeFieldLenientImpl) bodyPart.getHeader().getField(HttpHeaders.CONTENT_TYPE);
                 String mimeType = contentType.getMimeType();
                 //可动态获取内容的编码,按编码转换
                 if (MediaType.PLAIN_TEXT_UTF_8.toString().startsWith(mimeType)) {
                     entry.setTextContent(IOUtils.toString(textBody.getReader()));
                 }
                 if (MediaType.HTML_UTF_8.toString().startsWith(mimeType)) {
                     entry.setHtmlContent(IOUtils.toString(textBody.getReader()));
                 }
             } else if (bodyContent instanceof Multipart) {
                 MultipartImpl multipart = (MultipartImpl) bodyContent;
                 outputContentAndAttachments(multipart.getBodyParts() , entry);
             } else if (bodyContent instanceof BinaryBody) {
                 BinaryBody binaryBody = (BinaryBody) bodyContent;
                 outputContentInAttachment(bodyPart.getHeader(), binaryBody, entry);
             } else {
                 System.err.println("【是否还存在未覆盖到的其它内容类型场景】?");
             }
         }
     }
 
     /**
      * 处理内容中的图片附件
      *
      * @param header      附件头信息对象
      * @param binaryBody  附件对象
      * @param entry 解析数据对象
      */
     private static void outputContentInAttachment(Header header, BinaryBody binaryBody, EmlEntry entry) throws IOException {
         Field contentIdField = header.getField(FieldName.CONTENT_ID);
         Field typeField = header.getField(FieldName.CONTENT_TYPE);
         if (typeField instanceof ContentTypeField) {
             ContentTypeField contentTypeField = (ContentTypeField) typeField;
             if (contentTypeField.getMediaType().startsWith(MediaType.ANY_IMAGE_TYPE.type())) {
                 try (InputStream inputStream = binaryBody.getInputStream()) {
                     String base64 = Base64.getEncoder().encodeToString(IOUtils.toByteArray(inputStream));
                     String cid = StringUtils.substringBetween(contentIdField.getBody(), "<", ">");
                     String content = StringUtils.replace(entry.getHtmlContent(),
                             "cid:" + cid, "data:" + contentTypeField.getMimeType() + ";base64," + base64);
                     entry.setHtmlContent(content);
                 }
             }
         }
     }
 
     /**
      * 转换邮件联系人至String
      * @param addressList 邮件联系人
      * @return String数据
      */
     private static String address2String(MailboxList addressList) {
         if (addressList == null) {
             return StringUtils.EMPTY;
         }
         for (Address address : addressList) {
             return address.toString();
         }
         return StringUtils.EMPTY;
     }
 
     /**
      * 转换邮件联系人至list集合
      * @param addressList 邮件联系人
      * @return list集合
      */
     private static List<Pair<String , String>> address2List(AddressList addressList) {
         List<Pair<String , String>> list = Lists.newArrayList();
         if (addressList == null) {
             return list;
         }
         for (Address address : addressList) {
             Mailbox mailbox = (Mailbox) address;
             list.add(Pair.of(mailbox.getName() , mailbox.getAddress()));
         }
         return list;
     }
 }

解析结果

(解析的JSON结果)

(HTML段落另存为文件)

其它说明

(1)eml格式文件是纯文本的文件,可使用记事本、Notepad++等工具打开,所以当看到它的内容时也可以按需进行自定义解析实现;

(2)实际应用中肯定比这个要复杂的多,本篇只是一个富含多个知识细节的示例,工作中实际处理要复杂的多;

(3)某些场景下的附件名称需要特殊转码,实际的附件名称在邮件的源文件中被分割为了多段,需要合并后转码;

(4)邮件中包含回复邮件、转发邮件等多次邮件来往,需要特殊处理;

(5)邮件的内容部分包含多种内容类型,需要提供多种的解析适配程序(如:某些邮件的签名处有图片签名,混迹在内容区域,需要先解析富文本再解析二进制内容体等);

(6)本篇文章代码仅供参考,切勿直接使用,按内容类型来解析的实现应该是基于工厂模式进行的多种解析适配,示例工程源码见:下方的个人站点文章所述;

(7)了解更多欢迎访问:https://www.chendd.cn/blog/article/1624252901639442434.html

猜你喜欢

转载自blog.csdn.net/haiyangyiba/article/details/129086959