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

2023-11-13

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

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

映射方案:

  1. 在发送邮件通知时在Header中指定自定义的Message_Id,作为唯一标示,本系统中采用UUID。
  2. 定时任务扫描服务器邮箱的收件箱,本系统我们搜索收件箱中前30分钟内的主题为:“来自postmaster@net.cn的退信”,的退信邮件。
  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 = "来自postmaster@net.cn的退信";
 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 }

 

转载于:https://www.cnblogs.com/watson-ljf/p/9701129.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JavaMail读取收件箱退信邮件/分析邮件附件获取Message_Id 的相关文章

随机推荐

  • windows 下的composer 可能遇到(Loading composer repositories with package information)

    最容易遇到下面这个问题 Loading composer repositories with package information Updating dependencies including require dev 在dos界面下输入
  • Unity Inputfield获得和失去焦点

    获取焦点 public InputField inputField void Start inputField ActivateInputField 失去焦点 public InputField inputField void Start
  • electron-egg: 新一代桌面应用开发框架

    当前桌面软件技术有哪些 语言 技术 优点 缺点 C wpf 专业的桌面软件技术 功能强大 学习成本高 Java swing javaFx 跨平台和语言流行 GUI库少 界面不美观 C Qt 跨平台 功能和类库丰富 学习成本高 Swift 无
  • 用Python画笑脸

    开心一下 喵 很早之前画的 放上来做个纪念吧 代码如下 from turtle import screensize 600 600 speed 10 def Arc initial degree step rotate rangeNum s
  • cmake

    ubuntu系统当库安装后 需要包含头文件一般在 usr local include 比如 include directories usr local include ImageMagick 7 这样就能调用各种功能头文件 但还要包含 so
  • 数据结构基本概念及算法分析

    文章目录 1 数据结构基本概念 1 1 基本概念和术语 1 1 1 数据 1 1 2 数据元素 1 1 3 数据项 1 1 4 数据对象 1 1 5 数据结构 1 2 逻辑结构与物理结构 1 2 1 逻辑结构 我们最需要关注的问题 1 2
  • 从在浏览器的输入框输入一个网址,到看到网页的内容,这个过程中发生了什么?

    https www cnblogs com ouyang99 p 10284271 html 从在浏览器的输入框输入一个网址 到看到网页的内容 这个过程中发生了什么 当在浏览器地址栏输入网址 如 www baidu com后浏览器是怎么把最
  • 硬件设计27之RS232

    串口通讯 串口通讯 Serial Communication 是一种设备间非常常用的串行通讯方式 因为它简单便捷 大部分电子设备都支持该通讯方式 电子工程师在调试设备时也经常使用该通讯方式输出调试信息 在计算机科学里 大部分复杂的问题都可以
  • js实现简单的视频播放

    功能1 播放暂停切换 思路 准备一个播放的图片和一个暂停的图片 利用标杆思想 设置一个flag变量 播放的时候将flag设置为false 暂停的时候设置为true 如果flag为true则播放同时改变为播放图片 为false则暂停同时改变为
  • 游游的排列构造

    示例1 输入 5 2 输出 3 1 5 2 4 示例2 输入 5 3 输出 2 1 4 3 5 include
  • C语言一行一行读取文件

    C语言中 使用fgets函数可以一行行读du取文件 1 fgets函数 原型 char fgets char buf int bufsize FILE stream 功能 从文件结构体指针stream中读取数据 每次读取一行 说明 读取的数
  • MySQL常用的文本文件导出导入方式总结

    目录 一 导出 1 1 mysql命令导出文本文件 1 2 select into outfile导出文本文件 1 3 mysqldump导出文本文件 二 导入 2 1 mysqlimport导入文本文件 2 2 LOAD DATA INF
  • 笔记记录--基于ccpd数据集利用Paddle OCR训练车牌检测模型

    目录 1 环境搭建 2 数据集划分 3 训练模型 4 推理测试 1 环境搭建 安装Paddle OCR参考 创建环境 conda create n paddle env python 3 8 conda activate paddle en
  • Zookeeper报错Will not attempt to authenticate using SASL解决办法

    Will not attempt to authenticate using SASL unknown error 经过查资料 这个问题与zookeeper里面的zoo cfg配置有关 在程序填写的zookeper的路径 一定与zoo cf
  • CSS网页设计》》

    这是跟着老师做的一个小案例 小小的有了一点成就感 下次努力 div class header div class logo img src img logo png alt logo div div class nav u u div di
  • Linux设备驱动的软件架构思想与设备驱动的基础内容总结

    Linux设备驱动的软件架构思想与设备驱动的基础内容总结 Linux是一个兼容性特别强的一个系统 而兼容性的实现与驱动强大的适应性密不可分 而这个具体的实现是离不开 总线bus和类class的管理方式 Linux使用bus统一的管理一系列相
  • Tomcat 部署方式

    Tomcat中三种部署项目的方法 第一种方法 在tomcat中的conf目录中 在server xml中的
  • 思科三层交换机IPv6静态和默认路由配置

    基础配置 SWA Switch gt ena Switch conf t Switch config host SWA SWA config vlan 10 SWA config vlan vlan 100 SWA config vlan
  • echarts ——timeLine组件

    echarts timeLine组件问题 带timeLine 组件的动态图例 从官网社区中的图例非常完美 拷贝到自己的项目中 下面的1月 2月 只显示0 1 2 3 刚开始以为是获取数据的方法有误 项目空闲期再回看代码发现问题所在 地址 h
  • JavaMail读取收件箱退信邮件/分析邮件附件获取Message_Id

    需求描述 公司最近有个项目邮件通知功能 但是客户上传的邮件地址并不一定存在 以及其他的各种问题 所有希望发送通知后有个回执 及时发现地址存在问题的邮箱 需求分析 经过分析JavaMail可以读取收件箱邮件 我们可以通过对应通知的退信来回写通