1. 程式人生 > >JavaMail讀取收件箱退信郵件/分析郵件附件獲取Message_Id

JavaMail讀取收件箱退信郵件/分析郵件附件獲取Message_Id

isp ngxin 自定義 work pro nec tst aging 任務

需求描述:公司最近有個項目郵件通知功能,但是客戶上傳的郵件地址並不一定存在,以及其他的各種問題。所有希望發送通知後有個回執,及時發現地址存在問題的郵箱。

需求分析:經過分析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