【Java基础】网络编程-发送和接收Email


Email就是电子邮件。电子邮件的应用已经有几十年的历史了,我们熟悉的邮箱地址比如[email protected],邮件软件比如 Foxmail都是用来收发邮件的。

发送邮件

传统的邮件是如何发送的

传统的邮件是通过邮局投递,然后从一个邮局到另一个邮局,最终到达用户的邮箱:
在这里插入图片描述

电子邮件是如何发送的

电子邮件的发送过程也是类似的,只不过是电子邮件是从用户电脑的邮件软件,例如FoxMail,发送到邮件服务器上,可能经过若干个邮件服务器的中转,最终到达对方邮件服务器上,收件方就可以用软件接收邮件
在这里插入图片描述

MUA/MTA/MDT和SMTP协议

  1. 我们把类似FoxMail这样的邮件软件称为MUA:Mail User Agent,用户邮件代理;

  2. 邮件服务器则称为MTA:Mail Transfer Agent,意思是邮件中转的代理

  3. 最终到达的邮件服务器称为MDA:Mail Delivery Agent,意思是邮件到达的代理。电子邮件一旦到达MDA,就不再动了。实际上,电子邮件通常就存储在MDA服务器的硬盘上,然后等收件人通过软件或者登陆浏览器查看邮件。

  4. MTA邮件服务器和MDA邮件总代理服务器通常是现成的,我们不关心这些服务器内部是如何运行的。要发送邮件,我们关心的是如何编写一个MUA的软件,把邮件发送到MTA邮件服务器上。

  5. MUA到MTA邮件服务器发送邮件的协议就是SMTP协议,它是Simple Mail Transport Protocol的缩写,使用 标准端口25,也可以使用加密端口465或587

  6. SMTP协议是一个建立在TCP之上的协议,任何程序发送邮件都必须遵守SMTP协议。

  7. 使用Java程序发送邮件时,我们无需关心SMTP协议的底层原理,只需要使用JavaMail这个标准API就可以直接发送邮件。

    扫描二维码关注公众号,回复: 11382440 查看本文章

Java中使用JavaEmail发送邮件

准备SMTP登录信息

  • 发送邮件前,我们首先要确定作为MTA邮件服务器地址端口号
  • 邮件服务器地址通常是smtp.example.com,端口号由邮件服务商确定使用25、465还是587
  • 以下是一些常用邮件服务商的SMTP信息:
    • QQ邮箱:SMTP服务器是smtp.qq.com,端口是465/587
    • 163邮箱:SMTP服务器是smtp.163.com,端口是465
    • Gmail邮箱:SMTP服务器是smtp.gmail.com,端口是`465/587。
  • 有了SMTP服务器的域名和端口号,我们还需要SMTP服务器的登录信息通常是使用自己的邮件地址作为用户名登录口令用户口令或者一个独立设置的SMTP口令

假设我们准备使用自己的邮件地址[email protected]小明发送邮件,已知小明的邮件地址是[email protected]

  1. 使用maven依赖
    <dependency>
        <groupId>javax.mail</groupId>
        <artifactId>javax.mail-api</artifactId>
        <version>1.6.2</version>
    </dependency>
    <dependency>
        <groupId>com.sun.mail</groupId>
        <artifactId>javax.mail</artifactId>
        <version>1.6.2</version>
    </dependency>
  1. 配置SMTP登录信息
        // 服务器地址:
        String smtp = "smtp.163.com";
        // 登录用户名:
        String username = "[email protected]";
        // 登录口令:
        String password = "qq775825800";
        //主机端口号
        String port = "465";

        // 连接到SMTP服务器587端口:
        Properties props = new Properties();
        props.put("mail.smtp.host", smtp); // SMTP主机名
        props.put("mail.smtp.port", port); // 主机端口号
        props.put("mail.smtp.auth", "true"); // 是否需要用户认证
        props.put("mail.smtp.starttls.enable", "true"); // 启用TLS加密
        //解决因为163邮箱非开发端口465报错
        props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");

        // 获取Session实例:
        Session session = Session.getInstance(props, new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(username, password);
            }
        });


        // 设置debug模式便于调试:
        session.setDebug(true);
  • 465端口为例,连接SMTP服务器时,需要准备一个Properties对象,填入相关信息。最后获取Session实例时如果服务器需要认证,还需要传入一个Authenticator对象,并返回指定的用户名和口令

  • 当我们获取到Session实例后,打开调试模式可以看到SMTP通信的详细内容,便于调试。

发送邮件

  1. 发送邮件时,我们需要构造一个Message对象,然后调用Transport.send(Message)即可完成发送
        //---------------设置发送邮件正文-----------------------
        MimeMessage message = new MimeMessage(session);
        
        // 设置发送方地址:
        message.setFrom(new InternetAddress("[email protected]"));
    
        // 设置接收方地址:
        message.setRecipient(Message.RecipientType.TO, new InternetAddress("[email protected]"));
        // 设置邮件主题:
        message.setSubject("Hello", "UTF-8");
        // 设置邮件正文:
        message.setText("Hi qq877728715...", "UTF-8");
        // 发送:
        Transport.send(message);

绝大多数邮件服务器要求发送方地址和登录用户名必须一致,否则发送将失败

  1. 填入真实的地址,运行上述代码,我们可以在控制台看到JavaMail打印的调试信息:
DEBUG: setDebug: JavaMail version 1.6.2
DEBUG: getProvider() returning javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Oracle]
DEBUG SMTP: need username and password for authentication
DEBUG SMTP: protocolConnect returning false, host=smtp.163.com, user=87772, password=<null>
DEBUG SMTP: useEhlo true, useAuth true
#开始尝试连接smtp.163.com
DEBUG SMTP: trying to connect to host "smtp.163.com", port 465, isSSL false
220 163.com Anti-spam GT for Coremail System (163com[20141201])
DEBUG SMTP: connected to host "smtp.163.com", port: 465
#发送命令EHLO:
EHLO DESKTOP-GE36VVD
#SMTP服务器响应250:
250-mail
250-PIPELINING
250-AUTH LOGIN PLAIN
250-AUTH=LOGIN PLAIN
250-coremail 1Uxr2xKj7kG0xkI17xGrU7I0s8FY2U3Uj8Cz28x1UUUUU7Ic2I0Y2Ur3wGB5UCa0xDrUUUUj
#发送命令STARTTLS
250-STARTTLS
250 8BITMIME
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "AUTH", arg "LOGIN PLAIN"
DEBUG SMTP: Found extension "AUTH=LOGIN", arg "PLAIN"
DEBUG SMTP: Found extension "coremail", arg "1Uxr2xKj7kG0xkI17xGrU7I0s8FY2U3Uj8Cz28x1UUUUU7Ic2I0Y2Ur3wGB5UCa0xDrUUUUj"
DEBUG SMTP: Found extension "STARTTLS", arg ""
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: STARTTLS requested but already using SSL
#尝试登录
DEBUG SMTP: protocolConnect login, host=smtp.163.com, user=[email protected], password=<non-null>
DEBUG SMTP: Attempt to authenticate using mechanisms: LOGIN PLAIN DIGEST-MD5 NTLM XOAUTH2 
DEBUG SMTP: Using mechanism LOGIN
DEBUG SMTP: AUTH LOGIN command trace suppressed
#登录成功
DEBUG SMTP: AUTH LOGIN succeeded
DEBUG SMTP: use8bit false
#开发发送邮件,设置FROM
MAIL FROM:<[email protected]>
250 Mail OK
#设置TO
RCPT TO:<[email protected]>
250 Mail OK
DEBUG SMTP: Verified Addresses
DEBUG SMTP:   [email protected]
#发送邮件数据
DATA
#服务器响应354
354 End data with <CR><LF>.<CR><LF>
#真正的邮件数据
Date: Thu, 30 Jan 2020 14:01:07 +0800 (CST)
From: [email protected]
To: [email protected]
Message-ID: <2114664380.0.1580364067964@DESKTOP-GE36VVD>
#邮件主题
Subject: Hello
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 7bit
#邮件正文
Hi qq877728715...
.
#邮件数据发送完成后,以\r\n.\r\n结束,服务器响应250表示发送成功:
250 Mail OK queued as smtp7,C8CowABnZwMocTJesciMIw--.9151S2 1580364073
DEBUG SMTP: message successfully delivered to mail server
#发送QUIT命令
QUIT
221 Bye

从上面的调试信息可以看出,SMTP协议是一个请求-响应协议,客户端总是发送命令,然后等待服务器响应。服务器响应总是以数字开头,后面的信息才是用于调试的文本。 这些响应码已经被定义在SMTP协议中了,查看具体的响应码就可以知道出错原因。如果一切顺利,对方将收到一封文本格式的电子邮件:

发送HTML邮件

发送HTML邮件和文本邮件是类似的,只需要把:

message.setText(body, "UTF-8");

改为:

message.setText(body, "UTF-8", "html");

在这里插入图片描述

  • 传入的body是类似<h1>Hello</h1><p>Hi, xxx</p>这样的HTML字符串即可。
  • HTML邮件可以在邮件客户端直接显示为网页格式:

发送附件

要在电子邮件中携带附件,我们就不能直接调用message.setText()方法,而是要构造一个Multipart对象

Multipart multipart = new MimeMultipart();
// 添加text:第一个BodyPart是文本,即邮件正文,后面的BodyPart是附件
BodyPart textpart = new MimeBodyPart();
textpart.setContent(body, "text/html;charset=utf-8");
multipart.addBodyPart(textpart);

// 添加image:
BodyPart imagepart = new MimeBodyPart();
imagepart.setFileName(fileName);//设置文件名
imagepart.setDataHandler(new DataHandler(new ByteArrayDataSource(inputStream, "application/octet-stream")));
multipart.addBodyPart(imagepart);

// 设置邮件内容为multipart:
message.setContent(multipart);
  • 一个Multipart对象可以添加若干个BodyPart,其中第一个BodyPart是文本,即邮件正文后面的BodyPart是附件。

  • BodyPart依靠setContent()决定添加的内容

    • 添加文本,用setContent("…", “text/plain;charset=utf-8”)添加纯文本,
    • 添加HTML文本用setContent("…", “text/html;charset=utf-8”)。
    • 添加附件,需要设置文件名(不一定和真实文件名一致),并且添加一个DataHandler()传入文件的MIME类型。二进制文件可以用application/octet-stream,Word文档则是application/msword
  • 最后,通过setContent()把Multipart添加到Message 中,即可发送。

带附件的邮件在客户端会被提示下载:
在这里插入图片描述

发送内嵌图片的HTML邮件

有些童鞋可能注意到,HTML邮件中可以内嵌图片,这是怎么做到的?

  • 如果给一个<img src="http://example.com/test.jpg">,这样的 外部图片链接通常会被邮件客户端过滤,并提示用户显示图片并不安全。只有内嵌的图片才能正常在邮件中显示

内嵌图片实际上也是一个附件,即邮件本身也是Multipart,但需要做一点额外的处理:

Multipart multipart = new MimeMultipart();

// 添加text:
BodyPart textpart = new MimeBodyPart();
textpart.setContent("<h1>Hello</h1><p><img src=\"cid:img01\"></p>", "text/html;charset=utf-8");
multipart.addBodyPart(textpart);

// 添加image:
BodyPart imagepart = new MimeBodyPart();
imagepart.setFileName(fileName);
imagepart.setDataHandler(new DataHandler(new ByteArrayDataSource(input, "image/jpeg")));

// 与HTML的<img src="cid:img01">关联:
imagepart.setHeader("Content-ID", "<img01>");
multipart.addBodyPart(imagepart);

在HTML邮件中引用图片时,需要设定一个ID,用类似<img src=\"cid:img01\">引用,然后,在添加图片作为BodyPart时,除了要正确设置MIME类型(根据图片类型使用image/jpeg或image/png),还需要设置一个Header:

imagepart.setHeader("Content-ID", "<img01>");

这个ID和HTML中引用的ID对应起来,邮件客户端就可以正常显示内嵌图片:
在这里插入图片描述

常见问题

  • 如果用户名或口令错误,会导致535登录失败:
DEBUG SMTP: AUTH LOGIN failed
Exception in thread "main" javax.mail.AuthenticationFailedException: 535 5.7.3 Authentication unsuccessful [HK0PR03CA0105.apcprd03.prod.outlook.com]
  • 如果登录用户和发件人不一致,会导致554拒绝发送错误:
DEBUG SMTP: MessagingException while sending, THROW: 
com.sun.mail.smtp.SMTPSendFailedException: 554 5.2.0 STOREDRV.Submission.Exception:SendAsDeniedException.MapiExceptionSendAsDenied;
  • 有些时候,如果邮件主题和正文过于简单,会导致554被识别为垃圾邮件的错误:
DEBUG SMTP: MessagingException while sending, THROW: 
com.sun.mail.smtp.SMTPSendFailedException: 554 DT:SPM
  • 发送163邮件时报错:Could not connect to SMTP host: smtp.163.com, port: 465, response: -1
    • 方法一:加多以下这句 props.put("mail.smtp.ssl.enable", true);
    • 方法二:修改发送端口将465端口改为25端口
    • http://blog.sina.com.cn/s/blog_5ceb51480102x1no.html
      https://blog.csdn.net/cry1049208942/article/details/97183552
      https://blog.csdn.net/hao134838/article/details/85761003

发送邮件完整代码

import javax.activation.DataHandler;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.util.ByteArrayDataSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

/**
 * @Description TODO 发送邮件
 * @Author JianPeng OuYang
 * @Date 2020/1/30 13:45
 * @Version v1.0
 */
public class SendEmail {
    public static void main(String[] args) throws MessagingException, IOException {
        // 服务器地址:
        String smtp = "smtp.163.com";
        // 登录用户名:
        String username = "[email protected]";
        // 登录口令:
        String password = "***********";
        //主机端口号
        String port = "25";//25为开放端口,465为非开放端口

        // 连接到SMTP服务器587端口:
        Properties props = new Properties();
        props.put("mail.smtp.host", smtp); // SMTP主机名
        props.put("mail.smtp.port", port); // 主机端口号
        props.put("mail.smtp.auth", "true"); // 是否需要用户认证
        props.put("mail.smtp.starttls.enable", "true"); // 启用TLS加密
        //解决因为163邮箱非开发端口465报错
        //props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");

        // 获取Session实例:
        Session session = Session.getInstance(props, new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(username, password);
            }
        });


        // 设置debug模式便于调试:
        session.setDebug(true);


        //---------------设置发送邮件正文-----------------------
        MimeMessage message = new MimeMessage(session);

        // 设置发送方地址:
        message.setFrom(new InternetAddress("[email protected]"));

        // 设置接收方地址:
        message.setRecipient(Message.RecipientType.TO, new InternetAddress("[email protected]"));
        // 设置邮件主题:
        message.setSubject("你好兄弟,吃饭了吗?", "UTF-8");
        // 设置邮件正文:
        // -------------------1.发送文本
        // message.setText("从上面的调试信息可以看出,**SMTP协议是一个请求-响应协议,客户端总是发送命令,然后等待服务器响应。服务器响应总是以数字开头,后面的信息才是用于调试的文本。**  `这些响应码已经被定义在SMTP协议中了,查看具体的响应码就可以知道出错原因。`如果一切顺利,对方将收到一封文本格式的电子邮件:", "UTF-8");

        // -------------------2.发送html文本
        // message.setText("<ol><li><h1><strong>星期一</strong><br></h1></li><li><h1><strong>星期二</strong></h1></li><li><h1><strong>星期三</strong></h1></li></ol>", "UTF-8", "html");

        // -------------------3.发送html文本+附件
        Multipart multipart = new MimeMultipart();
        //---------添加text:第一个BodyPart是文本,即邮件正文,后面的BodyPart是附件
        BodyPart textpart = new MimeBodyPart();
        textpart.setContent("<h1>Hello</h1><p><img src=\"cid:img01\"></p>", "text/html;charset=utf-8");
        //添加附件到multipart中
        multipart.addBodyPart(textpart);

        //---------添加image用于在正文中引用当前图片
        BodyPart imagepart = new MimeBodyPart();
        imagepart.setFileName("a.jpg");
        imagepart.setDataHandler(new DataHandler(new ByteArrayDataSource(new FileInputStream(new File("a.jpg")), "image/jpeg")));
        //----------与HTML的<img src="cid:img01">关联:
        imagepart.setHeader("Content-ID", "<img01>");
        //添加附件到multipart中
        multipart.addBodyPart(imagepart);

        // 添加附件1:
        BodyPart filePart = new MimeBodyPart();
        filePart.setFileName("aaaRandom.txt");//设置文件名
        //把数据源添加到附件中
        filePart.setDataHandler(new DataHandler(new ByteArrayDataSource(new FileInputStream(new File("aaaRandom.txt")), "application/octet-stream")));
        //-----------添加指定数据到文本文件中
        //txtPart.setContent("添加指定数据到文本文件中","text/html;charset=UTF-8");
        //添加附件到multipart中
        multipart.addBodyPart(filePart);

        // 设置邮件内容为multipart:
        message.setContent(multipart);

        // 发送:
        Transport.send(message);
    }
}

效果:发送txt附件且在正文中引用图片
在这里插入图片描述

小结

  1. 使用JavaMail API发送邮件本质上是一个MUA软件通过SMTP协议发送邮件至MTA服务器;
  2. 打开调试模式可以看到详细的SMTP交互信息;
  3. 某些邮件服务商需要开启SMTP,并需要独立的SMTP登录密码。

接收邮件

发送邮件在上面已经讲过了,客户端总是通过SMTP协议把邮件发送给MTA(邮件中转的代理服务器

  1. 接收Email则相反,因为邮件最终到达收件人的MDA服务器(邮件总代理服务器),所以接收邮件是收件人用自己的客户端把邮件从MDA服务器抓取到本地的过程。

  2. 接收邮件使用最广泛的协议是 POP3:Post Office Protocol version 3 ,它也是一个建立在TCP连接之上的协议POP3服务器的标准端口是110如果整个会话需要加密,那么使用加密端口995

  3. 另一种接收邮件的协议是 IMAP:Internet Mail Access Protocol ,它使用标准端口143和加密端口993

  4. IMAP和POP3的主要区别是: IMAP协议在本地的所有操作都会自动同步到服务器上,并且,IMAP可以允许用户在邮件服务器的收件箱中创建文件夹

JavaMail也提供了IMAP协议的支持。因为POP3和IMAP的使用方式非常类似,因此我们只介绍POP3的用法。

Java中使用JavaEmail发送邮件

使用POP3收取Email时,我们无需关心POP3协议底层,因为JavaMail提供了高层接口。首先需要连接到Store对象

// 准备登录信息:
String host = "pop3.example.com";
int port = 995;
String username = "[email protected]";
String password = "password";

Properties props = new Properties();
props.setProperty("mail.store.protocol", "pop3"); // 协议名称
props.setProperty("mail.pop3.host", host);// POP3主机名
props.setProperty("mail.pop3.port", String.valueOf(port)); // 端口号
// 启动SSL:
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.put("mail.smtp.socketFactory.port", String.valueOf(port));

// 连接到Store:
URLName url = new URLName("pop3", host, post, "", username, password);
Session session = Session.getInstance(props, null);
session.setDebug(true); // 显示调试信息
Store store = new POP3SSLStore(session, url);
store.connect();

一个Store对象表示整个邮箱的存储,要收取邮件,我们需要通过Store访问指定的Folder(文件夹)通常是INBOX表示收件箱

// 获取收件箱:
Folder folder = store.getFolder("INBOX");

// 以读写方式打开:
folder.open(Folder.READ_WRITE);

// 打印邮件总数/新邮件数量/未读数量/已删除数量:
System.out.println("Total messages: " + folder.getMessageCount());
System.out.println("New messages: " + folder.getNewMessageCount());
System.out.println("Unread messages: " + folder.getUnreadMessageCount());
System.out.println("Deleted messages: " + folder.getDeletedMessageCount());

// 获取每一封邮件:
Message[] messages = folder.getMessages();
for (Message message : messages) {
    // 打印每一封邮件:
    printMessage((MimeMessage) message);
}

当我们获取到一个Message对象时,可以强制转型为MimeMessage,然后打印出邮件主题、发件人、收件人等信息

void printMessage(MimeMessage msg) throws IOException, MessagingException {
    // 邮件主题:
    System.out.println("Subject: " + MimeUtility.decodeText(msg.getSubject()));
    // 发件人:
    Address[] froms = msg.getFrom();
    InternetAddress address = (InternetAddress) froms[0];
    String personal = address.getPersonal();
    String from = personal == null ? address.getAddress() : (MimeUtility.decodeText(personal) + " <" + address.getAddress() + ">");
    System.out.println("From: " + from);
    // 继续打印收件人...
}

比较麻烦的是获取邮件的正文一个MimeMessage对象也是一个Multipart对象它可能只包含一个文本,也可能是一个Multipart对象,即由几个Multipart构成,因此,需要递归地解析出完整的正文

String getBody(Part part) throws MessagingException, IOException {
    if (part.isMimeType("text/*")) {
        // Part是文本:
        return part.getContent().toString();
    }
    if (part.isMimeType("multipart/*")) {
        // Part是一个Multipart对象:
        Multipart multipart = (Multipart) part.getContent();
        // 循环解析每个子Part:
        for (int i = 0; i < multipart.getCount(); i++) {
            BodyPart bodyPart = multipart.getBodyPart(i);
            String body = getBody(bodyPart);
            if (!body.isEmpty()) {
                return body;
            }
        }
    }
    return "";
}

最后记得关闭Folder和Store

folder.close(true); // 传入true表示删除操作会同步到服务器上(即删除服务器收件箱的邮件)
store.close();

完整代码1

import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeUtility;
import java.io.IOException;
import java.util.Properties;

/**
 * @Description TODO
 * @Author JianPeng OuYang
 * @Date 2020/1/30 15:44
 * @Version v1.0
 */
public class RecieveEmail {
    /**
     *  因为现在使用的是163邮箱 而163的 pop地址是 pop3.163.com  端口是 110
     *  比如使用好未来企业邮箱 就需要换成 好未来邮箱的 pop服务器地址 pop.263.net  和   端口 110
     */
    public static void main(String[] args) throws MessagingException, IOException {
        // 准备登录信息:
        String host = "pop3.163.com";
        int port = 995;
        String username = "[email protected]";
        String password = "*****163邮箱授权码*****";

        Properties props = new Properties();
        props.setProperty("mail.store.protocol", "pop3"); // 协议名称
        props.setProperty("mail.pop3.host", host);// POP3主机名
        props.setProperty("mail.pop3.port", String.valueOf(port)); // 端口号
        // 启动SSL:
        props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        props.put("mail.smtp.socketFactory.port", String.valueOf(port));

        // 连接到Store:
        URLName url = new URLName("pop3", host, port, "", username, password);
        Session session = Session.getInstance(props, null);
        session.setDebug(true); // 显示调试信息
        Store store = new POP3SSLStore(session, url);
        store.connect();


        // 获取收件箱:
        Folder folder = store.getFolder("INBOX");

        // 以读写方式打开:
        folder.open(Folder.READ_WRITE);

        // 打印邮件总数/新邮件数量/未读数量/已删除数量:
        System.out.println("邮件总数: " + folder.getMessageCount());
        System.out.println("新邮件数量: " + folder.getNewMessageCount());
        System.out.println("未读数量: " + folder.getUnreadMessageCount());
        System.out.println("已删除数量: " + folder.getDeletedMessageCount());

        // 获取每一封邮件:
        Message[] messages = folder.getMessages();
        for (Message message : messages) {
            // 打印每一封邮件:
            printMessage((MimeMessage) message);
        }
    }

    static void printMessage(MimeMessage msg) throws IOException, MessagingException {
        // 邮件主题:
        System.out.println("Subject: " + MimeUtility.decodeText(msg.getSubject()));
        // 发件人:
        Address[] froms = msg.getFrom();
        InternetAddress address = (InternetAddress) froms[0];
        String personal = address.getPersonal();
        String from = personal == null ? address.getAddress() : (MimeUtility.decodeText(personal) + " <" + address.getAddress() + ">");
        System.out.println("From: " + from);
        // 继续打印收件人...
    }

    String getBody(Part part) throws MessagingException, IOException {
        if (part.isMimeType("text/*")) {
            // Part是文本:
            return part.getContent().toString();
        }
        if (part.isMimeType("multipart/*")) {
            // Part是一个Multipart对象:
            Multipart multipart = (Multipart) part.getContent();
            // 循环解析每个子Part:
            for (int i = 0; i < multipart.getCount(); i++) {
                BodyPart bodyPart = multipart.getBodyPart(i);
                String body = getBody(bodyPart);
                if (!body.isEmpty()) {
                    return body;
                }
            }
        }
        return "";
    }
}

执行结果

DEBUG: setDebug: JavaMail version 1.6.2
DEBUG POP3: mail.pop3.rsetbeforequit: false
DEBUG POP3: mail.pop3.disabletop: false
DEBUG POP3: mail.pop3.forgettopheaders: false
DEBUG POP3: mail.pop3.cachewriteto: false
DEBUG POP3: mail.pop3.filecache.enable: false
DEBUG POP3: mail.pop3.keepmessagecontent: false
DEBUG POP3: mail.pop3.starttls.enable: false
DEBUG POP3: mail.pop3.starttls.required: false
DEBUG POP3: mail.pop3.finalizecleanclose: false
DEBUG POP3: mail.pop3.apop.enable: false
DEBUG POP3: mail.pop3.disablecapa: false
DEBUG POP3: connecting to host "pop3.163.com", port 995, isSSL true
+OK Welcome to coremail Mail Pop3 Server (163coms[10774b260cc7a37d26d71b52404dcf5cs])
CAPA
+OK Capability list follows
TOP
USER
PIPELINING
UIDL
LANG
UTF8
SASL PLAIN
STLS
.
DEBUG POP3: PIPELINING enabled
DEBUG POP3: authentication command trace suppressed
DEBUG POP3: authentication command succeeded
STAT
+OK 1 3312
邮件总数: 1
NOOP
+OK core mail
新邮件数量: 0
NOOP
+OK core mail
未读数量: 1
NOOP
+OK core mail
已删除数量: 0
NOOP
+OK core mail
TOP 1 0
+OK 3312 octets
Received: from mail163 (unknown [10.110.6.32])
	by trans1 (Coremail) with SMTP id gMCowADXXEK+ojJeDyu4BQ--.34489S2;
	Thu, 30 Jan 2020 17:32:46 +0800 (CST)
From: =?UTF-8?B?572R5piT6YKu5Lu25Lit5b+D?= <[email protected]>
Sender: [email protected]
To: m17603050524 <[email protected]>
Message-ID: <1794674068.331356.1580376766821.JavaMail.mail@service.netease.com>
Subject: =?UTF-8?B?6YKu5Lu25bey6KKr5a6i5oi356uv5oiQ5Yqf?=
 =?UTF-8?B?5pS25Y+W5bm25Zyo5pyN5Yqh5Zmo5LiK5Yig6Zmk?=
MIME-Version: 1.0
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
X-CM-TRANSID:gMCowADXXEK+ojJeDyu4BQ--.34489S2
X-Coremail-Antispam: 1Uf129KBjDUn29KB7ZKAUJUUUUU529EdanIXcx71UUUUU7v73
	VFW2AGmfu7bjvjm3AaLaJ3UbIYCTnIWIevJa73UjIFyTuYvj4RKdgAUUUUU
X-Originating-IP: [10.110.6.32]
Date: Thu, 30 Jan 2020 17:32:46 +0800 (CST)

.
Subject: 邮件已被客户端成功收取并在服务器上删除
From: 网易邮件中心 <[email protected]>

完整代码2

import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;

/**
 * 使用POP3协议接收邮件 
 */
public class POP3ReceiveMailTest {

    public static void main(String[] args) throws Exception {
        resceive();
    }

    /**
     * 接收邮件 
     */
    public static void resceive() throws Exception {
        /**
         * 因为现在使用的是163邮箱 而163的 pop地址是pop3.163.com 端口是110
         * 比如使用好未来企业邮箱 就需要换成 好未来邮箱的 pop服务器地址 pop.263.net  和   端口 110
         */
        String userName= "[email protected]";   // /163邮箱号码
        String passWord= "************8";   // 163邮箱授权码
        String port = "110";   // 端口号
        String servicePath = "pop3.163.com";   // 服务器地址


        // 准备连接服务器的会话信息  
        Properties props = new Properties();
        props.setProperty("mail.store.protocol", "pop3");       // 使用pop3协议  
        props.setProperty("mail.pop3.port", port);           // 端口
        props.setProperty("mail.pop3.host", servicePath);       // pop3服务器  

        // 创建Session实例对象  
        Session session = Session.getInstance(props);
        Store store = session.getStore("pop3");
        store.connect(userName, passWord); //163邮箱程序登录属于第三方登录所以这里的密码是163给的授权密码而并非普通的登录密码


        // 获得收件箱  
        Folder folder = store.getFolder("INBOX");
        /* Folder.READ_ONLY:只读权限
         * Folder.READ_WRITE:可读可写(可以修改邮件的状态)
         */
        folder.open(Folder.READ_WRITE); //打开收件箱  

        // 由于POP3协议无法获知邮件的状态,所以getUnreadMessageCount得到的是收件箱的邮件总数  
        System.out.println("未读邮件数: " + folder.getUnreadMessageCount());

        // 由于POP3协议无法获知邮件的状态,所以下面得到的结果始终都是为0  
        System.out.println("删除邮件数: " + folder.getDeletedMessageCount());
        System.out.println("新邮件: " + folder.getNewMessageCount());

        // 获得收件箱中的邮件总数  
        System.out.println("邮件总数: " + folder.getMessageCount());

        // 得到收件箱中的所有邮件,并解析  
        Message[] messages = folder.getMessages();
        parseMessage(messages);

        //得到收件箱中的所有邮件并且删除邮件
        //deleteMessage(messages);

        //释放资源  
        folder.close(true);
        store.close();
    }

    /**
     * 解析邮件 
     * @param messages 要解析的邮件列表 
     */
    public static void parseMessage(Message... messages) throws MessagingException, IOException {
        if (messages == null || messages.length < 1) {
            throw new MessagingException("未找到要解析的邮件!");

        }

        // 解析所有邮件  
        for (int i = 0, count = messages.length; i < count; i++) {
            MimeMessage msg = (MimeMessage) messages[i];
            System.out.println("------------------解析第" + msg.getMessageNumber() + "封邮件-------------------- ");
            System.out.println("主题: " + getSubject(msg));
            System.out.println("发件人: " + getFrom(msg));
            System.out.println("收件人:" + getReceiveAddress(msg, null));
            System.out.println("发送时间:" + getSentDate(msg, null));
            System.out.println("是否已读:" + isSeen(msg));
            System.out.println("邮件优先级:" + getPriority(msg));
            System.out.println("是否需要回执:" + isReplySign(msg));
            System.out.println("邮件大小:" + msg.getSize() * 1024 + "kb");
            boolean isContainerAttachment = isContainAttachment(msg);
            System.out.println("是否包含附件:" + isContainerAttachment);
            if (isContainerAttachment) {
                saveAttachment(msg, "f:\\mailTest\\" + msg.getSubject() + "_" + i + "_"); //保存附件
            }
            StringBuffer content = new StringBuffer(30);
            getMailTextContent(msg, content);
            System.out.println("邮件正文:" + (content.length() > 100 ? content.substring(0, 100) + "..." : content));
            System.out.println("------------------第" + msg.getMessageNumber() + "封邮件解析结束-------------------- ");
            System.out.println();

        }
    }


    /**
     * 解析邮件 
     * @param messages 要解析的邮件列表 
     */
    public static void deleteMessage(Message... messages) throws MessagingException, IOException {
        if (messages == null || messages.length < 1) {
            throw new MessagingException("未找到要解析的邮件!");

        }

        // 解析所有邮件  
        for (int i = 0, count = messages.length; i < count; i++) {

            /**
             *   邮件删除      
             */
            Message message = messages[i];
            String subject = message.getSubject();
            // set the DELETE flag to true
            message.setFlag(Flags.Flag.DELETED, true);
            System.out.println("Marked DELETE for message: " + subject);


        }
    }

    /**
     * 获得邮件主题 
     * @param msg 邮件内容 
     * @return 解码后的邮件主题
     */
    public static String getSubject(MimeMessage msg) throws UnsupportedEncodingException, MessagingException {
        return MimeUtility.decodeText(msg.getSubject());
    }

    /**
     * 获得邮件发件人 
     * @param msg 邮件内容 
     * @return 姓名 <Email地址> 
     * @throws MessagingException
     * @throws UnsupportedEncodingException
     */
    public static String getFrom(MimeMessage msg) throws MessagingException, UnsupportedEncodingException {
        String from = "";
        Address[] froms = msg.getFrom();
        if (froms.length < 1) {
            throw new MessagingException("没有发件人!");
        }

        InternetAddress address = (InternetAddress) froms[0];
        String person = address.getPersonal();
        if (person != null) {
            person = MimeUtility.decodeText(person) + " ";
        } else {
            person = "";
        }
        from = person + "<" + address.getAddress() + ">";

        return from;
    }

    /**
     * 根据收件人类型,获取邮件收件人、抄送和密送地址。如果收件人类型为空,则获得所有的收件人 
     * <p>Message.RecipientType.TO  收件人</p> 
     * <p>Message.RecipientType.CC  抄送</p> 
     * <p>Message.RecipientType.BCC 密送</p> 
     * @param msg 邮件内容 
     * @param type 收件人类型 
     * @return 收件人1 <邮件地址1>, 收件人2 <邮件地址2>, ... 
     * @throws MessagingException
     */
    public static String getReceiveAddress(MimeMessage msg, Message.RecipientType type) throws MessagingException {
        StringBuffer receiveAddress = new StringBuffer();
        Address[] addresss = null;
        if (type == null) {
            addresss = msg.getAllRecipients();
        } else {
            addresss = msg.getRecipients(type);
        }

        if (addresss == null || addresss.length < 1) {
            throw new MessagingException("没有收件人!");
        }

        for (Address address : addresss) {
            InternetAddress internetAddress = (InternetAddress) address;
            receiveAddress.append(internetAddress.toUnicodeString()).append(",");
        }

        receiveAddress.deleteCharAt(receiveAddress.length() - 1); //删除最后一个逗号

        return receiveAddress.toString();
    }

    /**
     * 获得邮件发送时间 
     * @param msg 邮件内容 
     * @return yyyy年mm月dd日 星期X HH:mm 
     * @throws MessagingException
     */
    public static String getSentDate(MimeMessage msg, String pattern) throws MessagingException {
        Date receivedDate = msg.getSentDate();
        if (receivedDate == null) {
            return "";

        }

        if (pattern == null || "".equals(pattern)) {
            pattern = "yyyy年MM月dd日 E HH:mm ";
        }

        return new SimpleDateFormat(pattern).format(receivedDate);
    }

    /**
     * 判断邮件中是否包含附件 
     * @return 邮件中存在附件返回true,不存在返回false
     * @throws MessagingException
     * @throws IOException
     */
    public static boolean isContainAttachment(Part part) throws MessagingException, IOException {
        boolean flag = false;
        if (part.isMimeType("multipart/*")) {
            MimeMultipart multipart = (MimeMultipart) part.getContent();
            int partCount = multipart.getCount();
            for (int i = 0; i < partCount; i++) {
                BodyPart bodyPart = multipart.getBodyPart(i);
                String disp = bodyPart.getDisposition();
                if (disp != null && (disp.equalsIgnoreCase(Part.ATTACHMENT) || disp.equalsIgnoreCase(Part.INLINE))) {
                    flag = true;
                } else if (bodyPart.isMimeType("multipart/*")) {
                    flag = isContainAttachment(bodyPart);
                } else {
                    String contentType = bodyPart.getContentType();
                    if (contentType.indexOf("application") != -1) {
                        flag = true;
                    }

                    if (contentType.indexOf("name") != -1) {
                        flag = true;
                    }
                }

                if (flag) {
                    break;
                }
            }
        } else if (part.isMimeType("message/rfc822")) {
            flag = isContainAttachment((Part) part.getContent());
        }
        return flag;
    }

    /**
     * 判断邮件是否已读  
     * @param msg 邮件内容  
     * @return 如果邮件已读返回true, 否则返回false
     * @throws MessagingException
     */
    public static boolean isSeen(MimeMessage msg) throws MessagingException {
        return msg.getFlags().contains(Flags.Flag.SEEN);
    }

    /**
     * 判断邮件是否需要阅读回执 
     * @param msg 邮件内容 
     * @return 需要回执返回true, 否则返回false
     * @throws MessagingException
     */
    public static boolean isReplySign(MimeMessage msg) throws MessagingException {
        boolean replySign = false;
        String[] headers = msg.getHeader("Disposition-Notification-To");

        if (headers != null) {
            replySign = true;
        }

        return replySign;
    }

    /**
     * 获得邮件的优先级 
     * @param msg 邮件内容 
     * @return 1(High):紧急  3:普通(Normal)  5:低(Low) 
     * @throws MessagingException
     */
    public static String getPriority(MimeMessage msg) throws MessagingException {
        String priority = "普通";
        String[] headers = msg.getHeader("X-Priority");
        if (headers != null) {
            String headerPriority = headers[0];
            if (headerPriority.indexOf("1") != -1 || headerPriority.indexOf("High") != -1) {
                priority = "紧急";
            } else if (headerPriority.indexOf("5") != -1 || headerPriority.indexOf("Low") != -1) {
                priority = "低";
            } else {
                priority = "普通";

            }
        }
        return priority;
    }

    /**
     * 获得邮件文本内容 
     * @param part 邮件体 
     * @param content 存储邮件文本内容的字符串 
     * @throws MessagingException
     * @throws IOException
     */
    public static void getMailTextContent(Part part, StringBuffer content) throws MessagingException, IOException {
        //如果是文本类型的附件,通过getContent方法可以取到文本内容,但这不是我们需要的结果,所以在这里要做判断  
        boolean isContainTextAttach = part.getContentType().indexOf("name") > 0;
        if (part.isMimeType("text/*") && !isContainTextAttach) {
            content.append(part.getContent().toString());
        } else if (part.isMimeType("message/rfc822")) {
            getMailTextContent((Part) part.getContent(), content);
        } else if (part.isMimeType("multipart/*")) {
            Multipart multipart = (Multipart) part.getContent();
            int partCount = multipart.getCount();
            for (int i = 0; i < partCount; i++) {
                BodyPart bodyPart = multipart.getBodyPart(i);
                getMailTextContent(bodyPart, content);
            }
        }
    }

    /**
     * 保存附件  
     * @param part 邮件中多个组合体中的其中一个组合体  
     * @param destDir  附件保存目录  
     * @throws UnsupportedEncodingException
     * @throws MessagingException
     * @throws FileNotFoundException
     * @throws IOException
     */
    public static void saveAttachment(Part part, String destDir) throws UnsupportedEncodingException, MessagingException,
            FileNotFoundException, IOException {
        if (part.isMimeType("multipart/*")) {
            Multipart multipart = (Multipart) part.getContent();    //复杂体邮件  
            //复杂体邮件包含多个邮件体  
            int partCount = multipart.getCount();
            for (int i = 0; i < partCount; i++) {
                //获得复杂体邮件中其中一个邮件体  
                BodyPart bodyPart = multipart.getBodyPart(i);
                //某一个邮件体也有可能是由多个邮件体组成的复杂体  
                String disp = bodyPart.getDisposition();
                if (disp != null && (disp.equalsIgnoreCase(Part.ATTACHMENT) || disp.equalsIgnoreCase(Part.INLINE))) {
                    InputStream is = bodyPart.getInputStream();
                    saveFile(is, destDir, decodeText(bodyPart.getFileName()));
                } else if (bodyPart.isMimeType("multipart/*")) {
                    saveAttachment(bodyPart, destDir);
                } else {
                    String contentType = bodyPart.getContentType();
                    if (contentType.indexOf("name") != -1 || contentType.indexOf("application") != -1) {
                        saveFile(bodyPart.getInputStream(), destDir, decodeText(bodyPart.getFileName()));
                    }
                }
            }
        } else if (part.isMimeType("message/rfc822")) {
            saveAttachment((Part) part.getContent(), destDir);
        }
    }

    /**
     * 读取输入流中的数据保存至指定目录  
     * @param is 输入流  
     * @param fileName 文件名  
     * @param destDir 文件存储目录  
     * @throws FileNotFoundException
     * @throws IOException
     */
    private static void saveFile(InputStream is, String destDir, String fileName)
            throws FileNotFoundException, IOException {
        BufferedInputStream bis = new BufferedInputStream(is);
        BufferedOutputStream bos = new BufferedOutputStream(
                new FileOutputStream(new File(destDir + fileName)));
        int len = -1;
        while ((len = bis.read()) != -1) {
            bos.write(len);
            bos.flush();
        }
        bos.close();
        bis.close();
    }

    /**
     * 文本解码 
     * @param encodeText 解码MimeUtility.encodeText(String text)方法编码后的文本 
     * @return 解码后的文本
     * @throws UnsupportedEncodingException
     */
    public static String decodeText(String encodeText) throws UnsupportedEncodingException {
        if (encodeText == null || "".equals(encodeText)) {
            return "";
        } else {
            return MimeUtility.decodeText(encodeText);
        }
    }
}

执行结果:
在这里插入图片描述

小结

  1. 使用Java接收Email时,可以用POP3协议IMAP协议
  2. 使用POP3协议时,需要用Maven引入JavaMail依赖,并确定POP3服务器的域名/端口/是否使用SSL等,然后调用相关API接收Email。
  3. 设置debug模式可以查看通信详细内容,便于排查错误。

猜你喜欢

转载自blog.csdn.net/qq877728715/article/details/104110233