Java实现邮件发送与接收功能详解

邮件协议

SMTP

SMTP (Simple Mail Transfer Protocol),即简单邮件传输协议

默认端口是25,通过SSL协议加密之后的默认端口是465

用户必须首先设置 SMTP 服务器,然后才能配置电子邮件客户端与其连接。完成此操作后,用户按下电子邮件上的“发送”按钮,并在客户端和服务器之间建立 SMTP 连接以允许发送电子邮件。SMTP 连接建立在传输控制协议 (TCP)连接之上。

SMTP发件服务器,如smtp.163.com、smtp.qq.com


POP3

POP3是Post Office Protocol 3的简称,即邮局协议的第3个版本。

默认端口是110,通过SSL协议加密之后的默认端口是995

POP3允许用户从服务器上把邮件存储到本地主机(即自己的计算机)上,但是对邮件的操作并不会反馈到邮箱服务器上。

POP3收件服务器,如pop.163.com、pop.qq.com


IMAP

IMAP (Internet Mail Access Protocol),即交互式邮件存取协议,是一个应用层协议,

默认端口是143,通过SSL协议加密之后的默认端口是993

开启了IMAP后,您在电子邮件客户端收取的邮件仍然保留在服务器上,同时在客户端上的操作都会反馈到服务器上,如:删除邮件,标记已读等,服务器上的邮件也会做相应的动作。

IMAP收件服务器,如imap.163.com、imap.qq.com


邮箱授权码

现在邮箱大多为邮箱客户端设置了独立密码或授权码(即通过Smtp方式发送邮件密码处不是填邮箱登录密码,而是要填授权码)

以163邮箱为例,开通过程如下:

进去后,点击开启。开启完成后状态变为”已开启“


java 发邮件

JavaMail

JavaMail是Sun发布的用来处理email的API。它可以方便地执行一些常用的邮件传输。

JavaMail提供的是一些标准的邮件管理接口,有如下几个核心组件。

  • javax.mail.Session:表示整个邮件的会话,所有的类都要通过session才可用。
  • javax.mail.Message:Message类表示的是邮件传递的内容。
  • javax.mail.Address:当确定好Session和Message之后,就可以通过Address进行发送地址的指定。
  • javax.mail.Authenticator:使用此类可以通过用户名和密码保护资源。
  • javax.mail.Transport:在消息发送的最后一步使用此类,此类的功能是使用指定的语言发送消息。
  • javax.mail.Store:此类主要是进行信息的读、写等操作,也可以通过此类读取文件夹中的邮件。
  • javax.mail.Folder:用于对邮件进行分级管理。
  • 虽然JavaMail是Sun的API之一,但它还没有被加在标准的java开发工具包中(Java Development Kit),使用JavaMail进行邮件的开发,因此如果需要使用的话你需要首先从Oracle官网上下载JavaMail的开发包。

    我们可以在pom文件中引入如下依赖

    <dependency> 
       <groupId>javax.mail</groupId> 
       <artifactId>mail</artifactId> 
       <version>1.4.7</version> 
    </dependency> 

    由于我使用commons-email做邮件发送,而且它会自动引入javax.mail依赖,就不单独引入了


    commons-email

    commons-email它构建在Java Mail API 之上,主要是为了简化它。

    pom文件依赖

        <dependency>
             <groupId>org.apache.commons</groupId>
              <artifactId>commons-email</artifactId>
              <version>1.5</version>
        </dependency>

    Commons-Email的核心是Email类,它是一个抽象类,提供了发送邮件的核心功能。具体有以下几个实现类

  • SimpleEmail:发送简单邮件,即纯文本邮件
  • MultiPartEmail:发送带附件的邮件
  • HtmlEmail:发送超文本邮件
  • ImageHtmlEmail:发送图文混排的超文本邮件
  • HtmlEmail
    HtmlEmail类通常用来发送html格式的邮件,他也支持邮件携带普通文本、附件和内嵌图片。
    ImageHtmlEmail
    ImageHtmlEmail类通常是用来发送Html格式并内嵌图片的邮件,它拥有所有HtmlEmail的功能,但是图片主要是以html内嵌的为主。

    下面开始,是使用Commons Email发送邮件的举例


    SimpleEmail

    SimpleEmail:发送简单邮件,即纯文本邮件

     public static void main(String[] args) throws EmailException {
         Email email = new SimpleEmail();
         //设置邮箱服务器,这里我使用的163邮箱
         email.setHostName("smtp.163.com");
         //填写邮件服务器端口:465和25选填
         email.setSmtpPort(465);
         //开启debug日志
         //email.setDebug(true);
         //设置用户名(邮箱)和授权码(授权码是用于登录第三方邮件客户端的专用密码)
         //邮箱开启授权只需要登陆邮件,在里边设置一下就行了.
         email.setAuthenticator(new DefaultAuthenticator("your_email@163.com", "your_auth_password"));
         //开启ssl连接
         email.setSSLOnConnect(true);
         //填写发送者的邮箱
         email.setFrom("your_email@163.com");
         //填写发送日期
         email.setSentDate(new Date());
         //填写邮件标题
         email.setSubject("TestMail-1月20号");
         //邮件内容
         email.setMsg("This is a test mail ... :-)");
         //填写接收者的邮箱,我是发给了自己的qq邮箱
         email.addTo("target_email_address@qq.com");
         //发送
         email.send();
     }

    执行结果:


    MultiPartEmail

    MultiPartEmail类通常用来发送流媒体类型的邮件,允许附件和文本类型数据一起发送。

    public static void main(String[] args) throws EmailException {
        MultiPartEmail email = new MultiPartEmail();
        //设置邮箱服务器,在这里我使用的163邮箱
        email.setHostName("smtp.163.com");
        //填写邮件服务器端口:465和25选填
        email.setSmtpPort(465);
        //开启debug日志
        //email.setDebug(true);
        //设置用户名(邮箱)和授权码(授权码是用于登录第三方邮件客户端的专用密码)
        //邮箱开启授权只需要登陆邮件,在里边设置一下就行了.
        email.setAuthenticator(new DefaultAuthenticator("your_email@163.com", "your_auth_password"));
        //开启ssl连接
        email.setSSLOnConnect(true);
        //填写发送者的邮箱
        email.setFrom("your_email@163.com");
        //填写发送日期
        email.setSentDate(new Date());
        //填写邮件标题
        email.setSubject("TestMail-Attachment-1月20号");
        //邮件内容
        email.setMsg("我的附件发给你了.");
        //填写接收者的邮箱,我是发给了自己的qq邮箱
        email.addTo("target_email_address@qq.com");
        //email.addCc("抄送人邮箱");
        // 添加附件
        EmailAttachment attachment = new EmailAttachment();
        //填写附件位置
        attachment.setPath("C:\\Users\\Administrator\\Desktop\\问题收集.txt");
        attachment.setDisposition(EmailAttachment.ATTACHMENT);
        //填写附件描述
        attachment.setDescription("附件描述");
        //填写附件名称,要与上面一致填写路径中的附件名称一致,要不然收到附件的时候会有问题
        attachment.setName("问题收集.txt");
        email.attach(attachment);
        //发送
        email.send();
    }

    执行结果:


    java接收邮件

    POP3接收邮件

    接收邮件常用的协议有pop3,imap和exchange。exchange是微软的邮箱协议,Jakarta Mail暂不支持。

    JavaMail API中定义了一个java.mail.Store类,应用程序调用这个类的方法就可以获得用户邮箱中的各个邮件夹的信息。

    javaMail中的使用Folder对象表示邮件夹,通过Folder对象的方法应用程序进而又可以获得该邮件夹中的所有邮件信息。

    而每封邮件信息JavaMail又分别使用了一个Message对象进行封装

    public static void main(String[] args) throws Exception {
        // 创建一个有具体连接信息的Properties对象
        Properties prop = new Properties();
        //prop.setProperty("mail.debug", "true");
        prop.setProperty("mail.store.protocol", "pop3");
        //pop3服务器,我这里使用163邮箱
        prop.setProperty("mail.pop3.host", "pop.163.com");
        //pop默认110端口
        prop.setProperty("mail.pop3.port", "110");
        // 1、创建session
        Session session = Session.getInstance(prop);
        // 2、通过session得到Store对象
        Store store = session.getStore();
        // 3、连上邮件服务器
        store.connect("your_email@163.com", "your_auth_password");
        // 4、获得邮箱内的邮件夹
        Folder folder = store.getFolder("INBOX");
        folder.open(Folder.READ_ONLY);
        // 由于POP3协议无法获知邮件的状态,所以getUnreadMessageCount得到的是收件箱的邮件总数
        System.out.println("未读邮件数: " + folder.getUnreadMessageCount());
        // 获得邮件夹Folder内的所有邮件Message对象
        Message[] messages = folder.getMessages();
        for (int i = 0; i < messages.length; i++) {
            String subject = messages[i].getSubject();
            String from = (messages[i].getFrom()[0]).toString();
            System.out.println("第 " + (i + 1) + "封邮件的主题:" + subject);
            //还可以从Message对象中获取: 发件人、收件人、发送时间、邮件正文...
        }
        // 5、关闭
        folder.close(false);
        store.close();
    }

    1、由于POP3协议无法获知邮件的状态,所以getUnreadMessageCount()获取未读邮件数得到的是收件箱的邮件总数

    2、通过POP3协议获得的Store对象调用这个方法时,邮件夹名称只能指定为"INBOX"。

    执行结果:


    IMAP接收邮件

    同样需要先开启授权码

    邮箱的收件服务器地址,可以是smtp.qq.com吗??

    final static String USER = "your_email@163.com"; // 用户名
    final static String PASSWORD = "your_auth_password"; // 密码
    public final static String MAIL_SERVER_HOST = "imap.163.com"; // 邮箱服务器
    public static void main(String[] args) throws Exception {
        // 创建一个有具体连接信息的Properties对象
        Properties prop = new Properties();
          prop.setProperty("mail.debug", "true");
        //指定接收的邮件协议
        prop.setProperty("mail.store.protocol", "imap");
        prop.setProperty("mail.imap.host", MAIL_SERVER_HOST);
        prop.setProperty("mail.imap.port", "143");
        // 1、创建session
        Session session = Session.getInstance(prop);
        // 2、通过session得到Store对象
        Store store = session.getStore("imaps");
        // 3、连上邮件服务器
        store.connect(MAIL_SERVER_HOST, USER, PASSWORD);
        // 4、获得邮箱内的邮件夹 POP协议的话,这里只能是INBOX
        Folder folder = store.getFolder("INBOX");
        System.out.println("INBOX exist:" + folder.exists());
        //
        Folder defaultFolder = store.getDefaultFolder();
        Folder[] folders = defaultFolder.list("*");
        for (Folder folder1 : folders) {
            IMAPFolder imapFolder = (IMAPFolder) folder1;
            //javamail中使用id命令校验chekOpened,去掉
            imapFolder.doCommand(new IMAPFolder.ProtocolCommand() {
                @Override
                public Object doCommand(IMAPProtocol p) throws ProtocolException {
                    p.id("FUTONG");
                    return null;
                }
            });
        }
        //以只读方式打开收件箱
        folder.open(Folder.READ_ONLY);
        System.out.println("邮件总数:" + folder.getMessageCount());
        //由于POP3协议无法获知邮件的状态,所以getUnreadMessageCount()得到的是收件箱的邮件总数
        System.out.println("未读邮件数:" + folder.getUnreadMessageCount());
        // 获得邮件夹Folder内的所有邮件Message对象
        Message[] messages = folder.getMessages();
        for (int i = 0; i < messages.length; i++) {
            String subject = messages[i].getSubject();
            String from = (messages[i].getFrom()[0]).toString();
            System.out.println("第 " + (i + 1) + "封邮件的主题:" + subject);
            System.out.println("第 " + (i + 1) + "读写标识:" + messages[i].getFlags().contains(Flags.Flag.SEEN));
        }
        // 5、关闭
        folder.close(false);
        store.close();
    }

    执行结果:


    邮件内容解析

    根据MimeType类型的不同执行不同的操作,一步一步的解析

        if (message.isMimeType("TEXT/*")) { // 仅包含正文的简单邮件
            System.out.println("邮件正文: " + message.getContent());
        } else {
            parseMessage((MimeMultipart) message.getContent()); // 解析稍复杂邮件
        }
    
    
    /**
     * 解析邮件
     */
    public static void parseMessage (MimeMultipart part) throws MessagingException, IOException {
        byte[] bytes = new byte[1024];
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
     
        for (int i = 0; i < part.getCount(); i++) {
            BodyPart body = part.getBodyPart(i);
            if (body.isMimeType("text/html")) {
                System.out.println("html格式正文: " + (String) body.getContent());
            } else if (body.isMimeType("text/plain")) {
                System.out.println("纯文本格式正文: " + (String) body.getContent());
            } else if (body.isMimeType("multipart/*")) {
                MimeMultipart multipart = (MimeMultipart) body.getContent();
                parseMessage(multipart);
            } else { // 附件
                InputStream inputStream = body.getDataHandler().getInputStream();
                int len = 0;
                while( (len = inputStream.read(bytes)) != -1 ){
                    outStream.write(bytes, 0, len);
                }
                inputStream.close();
                byte[] data = outStream.toByteArray();
                String fileName = body.getFileName();
                File tempFile = new File(FileUtils.getTempDirectoryPath() + File.separator + System.currentTimeMillis() + fileName );
                FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
                fileOutputStream.write(data);
                fileOutputStream.close();
                System.out.println("邮件附件本地路径: " + tempFile.getAbsolutePath());
            }
        }
    }
     

    附件下载

    // 获得邮件夹Folder内的所有邮件Message对象
    Message[] messages = folder.getMessages();
    for (int i = 0; i < messages.length; i++) {
        String subject = messages[i].getSubject();
        String from = (messages[i].getFrom()[0]).toString();
        System.out.println("第 " + (i + 1) + "封邮件的主题:" + subject);
        System.out.println("第 " + (i + 1) + "封邮件的发件人地址:" + from);
        System.out.println("第 " + (i + 1) + "读写标识:" + messages[i].getFlags().contains(Flags.Flag.SEEN));
        Part mp = (Part) messages[i];
        boolean containAttach = isContainAttach(mp);
        if (containAttach) {
            //包含附件,进行附件下载
            System.out.println("第 " + (i + 1) + "封邮件,包含附件");
            saveAttachMent(mp, "C:\\Users\\Administrator\\Desktop\\Attach");
        }
    }

    这个不一定是static方法,我是为了在main方法里测试,才这么写的

        /**
         *  * 判断此邮件是否包含附件
         *
         * @throws MessagingException
         */
        public static boolean isContainAttach(Part part) throws Exception {
            boolean attachflag = false;
            String contentType = part.getContentType();
            if (part.isMimeType("multipart/*")) {
                Multipart mp = (Multipart) part.getContent();
                // 获取附件名称可能包含多个附件
                for (int j = 0; j < mp.getCount(); j++) {
                    BodyPart mpart = mp.getBodyPart(j);
                    String disposition = mpart.getDescription();
                    if ((disposition != null)
                            && ((disposition.equals(Part.ATTACHMENT)) || (disposition
                            .equals(Part.INLINE)))) {
                        attachflag = true;
                    } else if (mpart.isMimeType("multipart/*")) {
                        attachflag = isContainAttach((Part) mpart);
                    } else {
                        String contype = mpart.getContentType();
                        if (contype.toLowerCase().indexOf("application") != -1)
                            attachflag = true;
                        if (contype.toLowerCase().indexOf("name") != -1)
                            attachflag = true;
                    }
                }
            } else if (part.isMimeType("message/rfc822")) {
                attachflag = isContainAttach((Part) part.getContent());
            }
            return attachflag;
        }
    
    
        /**
         *  * 【保存附件】
         *
         * @throws Exception
         * @throws IOException
         * @throws MessagingException
         * @throws Exception
         */
        public static void saveAttachMent(Part part, String saveAttachPath) throws Exception {
            String fileName = "";
            if (part.isMimeType("multipart/*")) {
                Multipart mp = (Multipart) part.getContent();
                for (int j = 0; j < mp.getCount(); j++) {
                    BodyPart mpart = mp.getBodyPart(j);
                    String disposition = mpart.getDescription();
                    if ((disposition != null)
                            && ((disposition.equals(Part.ATTACHMENT)) || (disposition
                            .equals(Part.INLINE)))) {
                        fileName = mpart.getFileName();
                        if (fileName.toLowerCase().indexOf("GBK") != -1) {
                            fileName = MimeUtility.decodeText(fileName);
                        }
                        saveFile(fileName, mpart.getInputStream(), saveAttachPath);
                    } else if (mpart.isMimeType("multipart/*")) {
                        fileName = mpart.getFileName();
                    } else {
                        fileName = mpart.getFileName();
                        if ((fileName != null)) {
                            fileName = MimeUtility.decodeText(fileName);
                            saveFile(fileName, mpart.getInputStream(), saveAttachPath);
                        }
                    }
                }
            } else if (part.isMimeType("message/rfc822")) {
                saveAttachMent((Part) part.getContent(), saveAttachPath);
            }
        }
    
        /**
         *  * 【真正的保存附件到指定目录里】
         */
        private static void saveFile(String fileName, InputStream in, String storedir) throws Exception {
            String osName = System.getProperty("os.name");
            String separator = "";
            if (osName == null)
                osName = "";
            if (osName.toLowerCase().indexOf("win") != -1) {
                // 如果是window 操作系统
                separator = "/";
                if (storedir == null || storedir.equals(""))
                    storedir = "c:\tmp";
            } else {
                // 如果是其他的系统
                separator = "/";
                storedir = "/tmp";
            }
            File strorefile = new File(storedir + separator + fileName);
            BufferedOutputStream bos = null;
            BufferedInputStream bis = null;
            try {
                bos = new BufferedOutputStream(new FileOutputStream(strorefile));
                bis = new BufferedInputStream(in);
                int c;
                while ((c = bis.read()) != -1) {
                    bos.write(c);
                    bos.flush();
                }
            } catch (Exception e) {
                // TODO: handle exception
            } finally {
                bos.close();
                bis.close();
            }
        }

    作者:£小羽毛

    物联沃分享整理
    物联沃-IOTWORD物联网 » Java实现邮件发送与接收功能详解

    发表回复