JavaMail读取收件箱退信邮件/分析邮件附件获取Message_Id

Posted Watson-ljf

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaMail读取收件箱退信邮件/分析邮件附件获取Message_Id相关的知识,希望对你有一定的参考价值。

需求描述:公司最近有个项目邮件通知功能,但是客户上传的邮件地址并不一定存在,以及其他的各种问题。所有希望发送通知后有个回执,及时发现地址存在问题的邮箱。

需求分析:经过分析JavaMail可以读取收件箱邮件,我们可以通过对应通知的退信来回写通知状态。那么问题来了,发送通知和退信如何建立映射?经过调研,最终确定采用以下方案解决。

映射方案:

  1. 在发送邮件通知时在Header中指定自定义的Message_Id,作为唯一标示,本系统中采用UUID。
  2. 定时任务扫描服务器邮箱的收件箱,本系统我们搜索收件箱中前30分钟内的主题为:“来自[email protected]的退信”,的退信邮件。
  3. 分析退信附件,退信关联邮件信息存在附件中,我们需要的Message_Id也在其中,解析附件获取Message_Id回写通知状态。

核心代码:

邮件搜索

  1 package com.yinghuo.yingxinxin.notification.service;
  2 
  3 import com.yinghuo.yingxinxin.notification.domain.PayrollNotificationEntity;
  4 import com.yinghuo.yingxinxin.notification.domain.valobj.EmailNotificationStatus;
  5 import com.yinghuo.yingxinxin.notification.repository.NotificationRepository;
  6 import com.yinghuo.yingxinxin.notification.util.DateUtil;
  7 import com.yinghuo.yingxinxin.notification.util.EmailUtil;
  8 import com.yinghuo.yingxinxin.notification.util.StringUtil;
  9 import lombok.Data;
 10 import lombok.extern.slf4j.Slf4j;
 11 import org.apache.commons.lang.exception.ExceptionUtils;
 12 import org.springframework.boot.context.properties.ConfigurationProperties;
 13 import org.springframework.stereotype.Service;
 14 import org.springframework.transaction.annotation.Transactional;
 15 
 16 import javax.mail.*;
 17 import javax.mail.search.AndTerm;
 18 import javax.mail.search.ComparisonTerm;
 19 import javax.mail.search.SearchTerm;
 20 import javax.mail.search.SentDateTerm;
 21 import javax.mail.search.SubjectTerm;
 22 import java.util.Arrays;
 23 import java.util.Calendar;
 24 import java.util.Date;
 25 import java.util.Properties;
 26 
 27 @Service
 28 @Slf4j
 29 @Data
 30 @ConfigurationProperties(prefix = "spring.mail")
 31 public class EmailBounceScanService {
 32     private final static String subjectKeyword = "来自[email protected]的退信";
 33 
 34     private String popHost;
 35     private String username;
 36     private String password;
 37     private Integer timeOffset;
 38     private final NotificationRepository payrollSendRecordRepository;
 39 
 40     private Properties buildInboxProperties() {
 41         Properties properties = new Properties();
 42         properties.setProperty("mail.store.protocol", "pop3");
 43         properties.setProperty("mail.pop3.host", popHost);
 44         properties.setProperty("mail.pop3.auth", "true");
 45         properties.setProperty("mail.pop3.default-encoding", "UTF-8");
 46         return properties;
 47     }
 48 
 49     public void searchInboxEmail() {
 50         Session session = Session.getInstance(this.buildInboxProperties());
 51         Store store = null;
 52         Folder receiveFolder = null;
 53         try {
 54             store = session.getStore("pop3");
 55             store.connect(username, password);
 56             receiveFolder = store.getFolder("inbox");
 57             receiveFolder.open(Folder.READ_ONLY);
 58 
 59             int messageCount = receiveFolder.getMessageCount();
 60             if (messageCount > 0) {
 61                 Date now = Calendar.getInstance().getTime();
 62                 Date timeOffsetAgo = DateUtil.nextXMinute(now, timeOffset);
 63                 SearchTerm comparisonTermGe = new SentDateTerm(ComparisonTerm.GE, timeOffsetAgo);
 64                 SearchTerm search = new AndTerm(new SubjectTerm(subjectKeyword), comparisonTermGe);
 65 
 66                 Message[] messages = receiveFolder.search(search);
 67                 if (messages.length == 0) {
 68                     log.info("No bounce email was found.");
 69                     return;
 70                 }
 71                 this.messageHandler(messages);
 72             }
 73         } catch (MessagingException e) {
 74             log.error("Exception in searchInboxEmail {}", ExceptionUtils.getFullStackTrace(e));
 75             e.printStackTrace();
 76         } finally {
 77             try {
 78                 if (receiveFolder != null) {
 79                     receiveFolder.close(true);
 80                 }
 81                 if (store != null) {
 82                     store.close();
 83                 }
 84             } catch (MessagingException e) {
 85                 log.error("Exception in searchInboxEmail {}", ExceptionUtils.getFullStackTrace(e));
 86                 e.printStackTrace();
 87             }
 88         }
 89     }
 90 
 91     @Transactional
 92     public void messageHandler(Message[] messageArray) {
 93         Arrays.stream(messageArray).filter(EmailUtil::isContainAttachment).forEach((message -> {
 94             String messageId = null;
 95             try {
 96                 messageId = EmailUtil.getMessageId(message);
 97             } catch (Exception e) {
 98                 log.error("getMessageId:", ExceptionUtils.getFullStackTrace(e));
 99                 e.printStackTrace();
100             }
101             if (StringUtil.isEmpty(messageId)) return;
102 
103             PayrollNotificationEntity payrollNotificationEntity = payrollSendRecordRepository.findFirstByMessageId(messageId);
104             if (payrollNotificationEntity == null || EmailNotificationStatus.BOUNCE.getStatus() == payrollNotificationEntity.getStatus()) {
105                 log.warn("not found payrollNotificationEntity by messageId:{}", messageId);
106                 return;
107             }
108 
109             payrollNotificationEntity.setStatus(EmailNotificationStatus.BOUNCE.getStatus());
110             payrollNotificationEntity.setErrorMessage(EmailNotificationStatus.BOUNCE.getErrorMessage());
111             payrollSendRecordRepository.save(payrollNotificationEntity);
112         }));
113     }
114 }

 

附件解析

 1 package com.yinghuo.yingxinxin.notification.util;
 2 
 3 import lombok.extern.slf4j.Slf4j;
 4 
 5 import javax.mail.BodyPart;
 6 import javax.mail.MessagingException;
 7 import javax.mail.Multipart;
 8 import javax.mail.Part;
 9 import java.io.BufferedReader;
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.io.InputStreamReader;
13 
14 @Slf4j
15 public final class EmailUtil {
16     private static final String multipart = "multipart/*";
17 
18     public static String getMessageId(Part part) throws Exception {
19         if (!part.isMimeType(multipart)) {
20             return "";
21         }
22 
23         Multipart multipart = (Multipart) part.getContent();
24         for (int i = 0; i < multipart.getCount(); i++) {
25             BodyPart bodyPart = multipart.getBodyPart(i);
26 
27             if (part.isMimeType("message/rfc822")) {
28                 return getMessageId((Part) part.getContent());
29             }
30             InputStream inputStream = bodyPart.getInputStream();
31 
32             try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {
33                 String strLine;
34                 while ((strLine = br.readLine()) != null) {
35                     if (strLine.startsWith("Message_Id:")) {
36                         String[] split = strLine.split("Message_Id:");
37                         return split.length > 1 ? split[1].trim() : null;
38                     }
39                 }
40             }
41         }
42 
43         return "";
44     }
45 
46     public static boolean isContainAttachment(Part part) {
47         boolean attachFlag = false;
48         try {
49             if (part.isMimeType(multipart)) {
50                 Multipart mp = (Multipart) part.getContent();
51                 for (int i = 0; i < mp.getCount(); i++) {
52                     BodyPart mpart = mp.getBodyPart(i);
53                     String disposition = mpart.getDisposition();
54                     if ((disposition != null) && ((disposition.equals(Part.ATTACHMENT)) || (disposition.equals(Part.INLINE))))
55                         attachFlag = true;
56                     else if (mpart.isMimeType(multipart)) {
57                         attachFlag = isContainAttachment((Part) mpart);
58                     } else {
59                         String contype = mpart.getContentType();
60                         if (contype.toLowerCase().contains("application"))
61                             attachFlag = true;
62                         if (contype.toLowerCase().contains("name"))
63                             attachFlag = true;
64                     }
65                 }
66             } else if (part.isMimeType("message/rfc822")) {
67                 attachFlag = isContainAttachment((Part) part.getContent());
68             }
69         } catch (MessagingException | IOException e) {
70             e.printStackTrace();
71         }
72         return attachFlag;
73     }
74 }

 

以上是关于JavaMail读取收件箱退信邮件/分析邮件附件获取Message_Id的主要内容,如果未能解决你的问题,请参考以下文章

EXCHANGE 附件问题!

[Java] JavaMail 发送 html 格式带附件的邮件

无法使用 JavaMail 读取 Outlook 邮件,而 Gmail 可以工作

[Java] JavaMail 发送带图片的 html 格式的邮件

JavaMail 读取消息

读取电子邮件的文本文件转换为 Javamail MimeMessage