JavaMail接收邮件
1、邮件协议介绍
SMTP SMTP 的全称是“Simple Mail Transfer Protocol”,即简单邮件传输协议。它是一组用于从源地址到目的地址传输邮件的规范,通过它来控制邮件的中转方式。SMTP 协议属于 TCP/IP 协议簇,它帮助每台计算机在发送或中转信件时找到下一个目的地。SMTP 服务器就是遵循 SMTP 协议的发送邮件服务器。SMTP 认证,简单地说就是要求必须在提供了账户名和密码之后才可以登录 SMTP 服务器,这就使得那些垃圾邮件的散播者无可乘之机。增加 SMTP 认证的目的是为了使用户避免受到垃圾邮件的侵扰。POP3
POP3是Post Office Protocol 3的简称,即邮局协议的第3个版本,它规定怎样将个人计算机连接到Internet的邮件服务器和下载电子邮件的电子协议。它是因特网电子邮件的第一个离线协议标准,POP3允许用户从服务器上把邮件存储到本地主机(即自己的计算机)上,同时删除保存在邮件服务器上的邮件,而POP3服务器则是遵循POP3协议的接收邮件服务器,用来接收电子邮件的。
IMAP
IMAP全称是Internet Mail Access Protocol,即交互式邮件存取协议,它是跟POP3类似邮件访问标准协议之一。不同的是,开启了IMAP后,您在电子邮件客户端收取的邮件仍然保留在服务器上,同时在客户端上的操作都会反馈到服务器上,如:删除邮件,标记已读等,服务器上的邮件也会做相应的动作。所以无论从浏览器登录邮箱或者客户端软件登录邮箱,看到的邮件以及状态都是一致的。 个人邮箱的协议配置页面:
a、使用javax.mail的坐标依赖包(导包时看清楚是不是javax.mail的)
<!--JavaMail基本包--><dependency><groupId>javax.mail</groupId><artifactId>mail</artifactId><version>1.4.7</version></dependency><!--邮件发送的扩展包--><dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><version>1.1.1</version></dependency>
注:坐标必须为两个,因为javax.mail没有携带依赖包javax.activation(发送)
b、使用Jakarta Mail发送邮件(和javax.mail使用方式基本一样,不过它内部携带了javax.activation依赖包)
c、使用还有一种方式是使用SpringBoot集成的功能:<dependency><groupId>com.sun.mail</groupId><artifactId>jakarta.mail</artifactId><version>1.6.7</version></dependency>
目前我使用的是javax.mail,因为项目本身已安装。不过建议使用Jakarta Mail的包,因为javax.mail貌似已经停更。 下面主要针对javax.mail接收邮件进行讲解。<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId></dependency>
3、接收邮件
3.0、邮件接收过程
创建一个 Session 对象。 Session 对象创建一个 Store 对象,用来保存邮件。 Store 对象连接邮件服务器。 Store 对象创建一个 Message 对象 ( 也就是邮件内容 ) 。 Store 对象获取邮箱的邮件。3.1、配置
private final String protocol;private final String host;private final String port;private final String user;private final String password;public MailReceiveClient(String protocol, String host, String port, String user, String password) {this.protocol = protocol;this.host = host;this.port = port;this.user = user;this.password = password;}
3.2、使用Session类定义配置信息
javax.mail.Session类用于定义整个应用程序所需的环境信息,以及收集客户端与邮件服务器建立网络连接的会话信息,例如邮件服务器的主机名、端口号、采用的邮件发送和接收协议等。Session 对象根据这些信息构建用于邮件收发的Transport和Store对象,以及为客户端创建Message对象时提供信息支持。这里的Properties properties = new Properties();properties.put("mail.store.protocol", protocol);properties.put("mail." + protocol + ".host", host);properties.put("mail." + protocol + ".port", port);Session emailSession = Session.getInstance(properties);
protocol
值得是协议,对应的是"POP3"或者"IMAP"
3.3、调用Store类接收
javax.mail.Store类是接收邮件的核心API类,它的实例对象代表实现了某个邮件接收协议的邮件接收对象,例如POP3协议,客户端程序接收邮件时,只需要使用邮件接收API得到Store对象,然后调用Store对象的接收方法,就可以从指定的POP3服务器获得邮件数据,并把这些邮件数据封装到表示邮件的 Message 对象中// 从Session实例中获取Store对象emailStore = emailSession.getStore();emailStore.connect(user, password);// 打开文件夹,这里默认打开收件箱// 注意,如果想打开其他文件夹,必须都在inbox前缀下:[INBOX.Junk, INBOX.Drafts, INBOX.Sent, INBOX.Trash, INBOX.Finished]emailFolder = emailStore.getFolder("INBOX");emailFolder.open(Folder.READ_WRITE);
3.4、Message类创建和解析邮件内容
javax.mail.Message类是创建和解析邮件的核心API,这是一个抽象类,通常使用它的子类javax.mail.internet.MimeMessage类。它的实例对象表示一份电子邮件。客户端程序发送邮件时,首先使用创建邮件的JavaMail API创建出封装了邮件数据的Message对象,然后把这个对象传递给邮件发送API(Transport 类) 发送,客户端程序接收邮件时,邮件接收API把接收到的邮件数据封装在Message类的实例中,客户端程序在使用邮件解析API从这个对象中解析收到的邮件数据。 代码:Flag.seen表示已读。private List<Mail> getNewMails(Folder emailFolder) throws MessagingException {List<Mail> mails = new ArrayList<>();for (Message message : emailFolder.getMessages()) {if (!message.getFlags().contains(Flags.Flag.SEEN)) {message.setFlags(new Flags(Flags.Flag.SEEN), true);Mail mail = MailMapper.map(message, user);mails.add(mail);}}return mails;}
定义Mail的Bean类,包含以下内容: Subject 邮件主题 Content 邮件内容 From 发送者 Recipient 接收者 代码:
public final class Mail {private final String subject;private final String content;private final String from;private final String recipient;public Mail(String subject, String content, String from, String recipient) {this.subject = subject;this.content = content;this.from = from;this.recipient = recipient;}public String getSubject() {return subject;}public String getContent() {return content;}public String getFrom() {return from;}public String getRecipient() {return recipient;}}
Mapper方法(定义解析内容的方法): 代码:
import org.owasp.encoder.Encode;import javax.mail.Address;import javax.mail.Message;import javax.mail.MessagingException;import java.io.IOException;import java.util.Arrays;public final class MailMapper {private MailMapper() {}public static Mail map(Message message, String user) {String subject = Encode.forHtml(getSubject(message));String content = Encode.forHtml(getContent(message));String from = Encode.forHtml(getFrom(message));String recipient = Encode.forHtml(getRecipient(message, user));return new Mail(subject, content, from, recipient);}private static String getSubject(Message message) {try {return message.getSubject();} catch (MessagingException e) {throw new RuntimeException(e);}}private static String getContent(Message message) {try {Object content = message.getContent();if (content == null) {return null;}return content.toString();} catch (IOException | MessagingException e) {throw new RuntimeException(e);}}private static String getFrom(Message message) {try {Address[] from = message.getFrom();if (from.length == 0 || from[0] == null) {return null;}return from[0].toString();} catch (MessagingException e) {throw new RuntimeException(e);}}private static String getRecipient(Message message, String user) {try {return Arrays.stream(message.getAllRecipients()).map(Address::toString).filter(it -> it.startsWith(user)).findFirst().orElse(user);} catch (MessagingException e) {throw new RuntimeException(e);}}}
4、获取附件
上面内容只是获取文本内容,如果需要获取附件的话看下面。 首先看Message类结构:-
MimeMessage类:代表整封邮件。 -
MimeBodyPart类:代表邮件的一个 MIME 信息。 -
MimeMultipart类:代表一个由多个 MIME 信息组合成的组合 MIME 信息。
MIME(Multipurpose Internet Mail Extensions)
:即多用途因特网邮件扩展标准。它不是邮件传输协议。但对传输内容的消息、附件及其它的内容定义了格式。
邮件的内容为:
Received: from xmbg7.mail.qq.com (unknown [101.91.43.54])
by mx47 (Coremail) with SMTP id YcCowAAHhSLsVZxhew5GCw--.44623S3;
Tue, 23 Nov 2021 10:46:04 +0800 (CST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=vip.qq.com;
s=s201512; t=1637635564;
bh=hx6m189GKLvHwYK7EPgjUY6W63aDJzkYLaZobsCH5fw=;
h=From:To:Subject:Date;
b=enOyaq4hkcY0sVHuLe3O6f0wz9gctOI9knFJ438sldmBa4gFpX7I6Ucv9Npe0BtTz
+89KfHw6wUpfiBmgo119MRTkR8m1gE8BXyFCUgdq2qaYHPkf0sNAaZDtDGan7rRQuG
oQmP+mXeqP3/KHtJNAMYEAJQF03qYipFKcPXA964=
X-QQ-FEAT: zaIfg0hwV2qA1LHh5sNQ3yDC93UmKYlF+NlagoYC+rE=
X-QQ-SSF: 000100000000004000000000000000Z
X-QQ-XMAILINFO: NBrlLnjHQiaZ3bWTg2+kaq2WcXqqqVbSme9UoRGcm/EacxcEzzBQa78nxQndrm eF/UAg21TqvTk30iPgxBkFSCSf9NIRTG3wpo2TUtMU4b/2LEU+Be3z3i/hOQaoe09Y2PYIfBfkona FEzXSl8LNQHYStRGXPoD/TwYeiQfXWnMIOnEU3EgJpJ5uznkMMFN6wldar9WyhOYPE3h2nLLSuSN4RT4Prw9m7uF/IJoSE5jMEI2hO3EE7YetoenF7EULwI+s5KyKyH9Y+hMDbVsZv5mnQ+aBLJJSsBJbaRb4Z7Di6PZfKS9MN2IQvcKNzNlgQ5mIhSiwLIlukwMz5W+XnOVp7gLIn4HXK5b4WmqclfbrHV0b1qSM/HdMWzjT29cV1ZM7A2+uiCPFR9Tf1V0EDOhJGN+GqCgV4Z8ETjrWz47C4E3B993M+HiNQj1PY03UhD+8DsrsGqZqVTKoRP3SQKrZSygkyrYpBkevCoRex26JH67dHQxRXUGgCci1UZ8IA9/kMa28JQ+hfM0Cly0ZQtW32QAJSPUcHdrhxkfWQo3mfeVHg6NEp18arwXhEPp4Io8PTcmCGq6G0k5ZsjMVXaUtNDRUFEVQVo2jxint/dyQoGoxsNVkUdqYijW+VTMSdMkC4wnuEqjZ5a+8aiD1uMGm/eEkwW2qF4tZi7XZ5MKHKvvze1HSPsol0cuP6og8hbc490IH1Lz4Slz/HSTWL95IwkqvniKJtVKldTpa+0GYEY1dPIxKAmwahttxUgWv39mZ4VJgxL5Ozb+IX4tZ4JyTsjx4sk8RCHkEQVHsXZn+nSrQwPvlmMlaQ5sp/N7WJt7yf2TpaHTWhtTek3P08MNRmkPqMgJSKCrn+3t/TJq8HdAKQi2NY0B0Mnseh5S7pWEu3i5xh1Rh
X-HAS-ATTACH: no
X-QQ-BUSINESS-ORIGIN: 2
X-Originating-IP: 122.97.149.60
X-QQ-STYLE:
X-QQ-mid: webmail623t1637635564t3630643
From: "=?gb18030?B?sai/vLTL0KO1xLH4?=" <[email protected]>
To: "=?gb18030?B?YW50bGFkZGll?=" <[email protected]>
Subject: =?gb18030?B?suLK1L3TytXTyrz+KM7euL28/imi2Q==?=
Mime-Version: 1.0
Content-Type: multipart/alternative;
boundary="----=_NextPart_619C55EB_107CACC0_425E3EAC"
Content-Transfer-Encoding: 8Bit
Date: Tue, 23 Nov 2021 10:46:03 +0800
X-Priority: 3
Message-ID: <[email protected]>
X-QQ-MIME: TCMime 1.0 by Tencent
X-Mailer: QQMail 2.x
X-QQ-Mailer: QQMail 2.x
X-CM-TRANSID:YcCowAAHhSLsVZxhew5GCw--.44623S3
Authentication-Results: mx47; spf=pass [email protected]
om; dkim=pass [email protected]
X-Coremail-Antispam: 1Uf129KBjDUn29KB7ZKAUJUUUUU529EdanIXcx71UUUUU7v73
VFW2AGmfu7bjvjm3AaLaJ3UbIYCTnIWIevJa73UjIFyTuYvjxU4vPfDUUUU
This is a multi-part message in MIME format.
------=_NextPart_619C55EB_107CACC0_425E3EACContent-Type: text/plain;
charset="gb18030"
Content-Transfer-Encoding: base64 SSdtIGdvaW5nIHRvIGRvIGEgbWFpbGJveCBhY2NlcHRhbmNlIHRlc3QuztLSqtf20ru49tPK
z+S908rVsuLK1A== ------=_NextPart_619C55EB_107CACC0_425E3EAC
Content-Type: text/html;
charset="gb18030"
Content-Transfer-Encoding: base64 PG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNo
YXJzZXQ9R0IxODAzMCI+PHNwYW4gc3R5bGU9ImZvbnQtZmFtaWx5OiBQaW5nRmFuZ1NDLVJl
Z3VsYXIsICZxdW90O01pY3Jvc29mdCBZYWhlaSZxdW90OywgJnF1b3Q7XFw1RkFFyO3Rxbra
JnF1b3Q7LCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDE2cHg7Ij5JJ20gZ29pbmcgdG8gZG8g
YSBtYWlsYm94IGFjY2VwdGFuY2UgdGVzdC48L3NwYW4+PGRpdj48Zm9udCBmYWNlPSJQaW5n
RmFuZ1NDLVJlZ3VsYXIsIE1pY3Jvc29mdCBZYWhlaSwgXFw1RkFFyO3RxbraLCBzYW5zLXNl
cmlmIj48c3BhbiBzdHlsZT0iZm9udC1zaXplOiAxNnB4OyI+ztLSqtf20ru49tPKz+S908rV
suLK1Dwvc3Bhbj48L2ZvbnQ+PC9kaXY+
------=_NextPart_619C55EB_107CACC0_425E3EAC--
获取附件的代码:
for (Message message : messages) {// 处理邮件内容if (message.isMimeType("multipart/*")) {Multipart multipart = (Multipart) message.getContent();// 遍历内容主体for (int i = 0; i < multipart.getCount(); i++) {BodyPart bodyPart = multipart.getBodyPart(i);if (Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition())) {// 获取附件文件名,注意使用decodeText防止中文乱码String fileName = MimeUtility.decodeText(bodyPart.getFileName());// 保存附件到本地saveAttachment(bodyPart.getInputStream(), fileName);}}}}
保存本地方法,分块读取并写入:
5、总结几个坑public void saveAttachment(InputStream inputStream, String fileName) throws SecurityException, IOException {File file = new File(fileName);FileOutputStream outputStream = new FileOutputStream(file);int blockSize = 4096;byte[] buffer = new byte[blockSize];int bytesRead;while ((bytesRead = inputStream.read(buffer)) != -1) {outputStream.write(buffer, 0, bytesRead);}outputStream.close();inputStream.close();}
5.1 检查好邮箱服务设置,尤其端口号和是否加密,大部分服务并有没有开通IMAP协议
5.2 记得捕获异常,并用finally断开连接 5.3 在结束后记得也要关闭文件夹和连接 emailFolder.close(false); // 迁移文件夹的时候这里一定要false,否则失效 emailStore.close(); 5.4 注意读取邮件的顺序,并且读取没有索引,每次都是从头开始,所以我们程序在读取完后都会迁移到新的文件夹 5.5 Store类实例可以重复使用,避免重复连接。