通过JavaMail下载邮件附件

2022-06-21 00:00:00 下载 邮件附件 JavaMail

通过JavaMail下载邮件附件

  • 需求
    • JavaMail
      • 工具类
      • 附件类
      • 使用
      • 遇到的一些意外

需求

因为某些特定的业务需要,要从邮件中获取附件。花了一些时间,最终业务需求实现了,但是也遇到一些坑,所以记录一下。

JavaMail

使用JavaMail来实现邮件的一系列操作(发送、接收),我使用的版本是1.6.2(maven仓库里有),不多说,上代码

工具类

package com.xxx.xxx;

import com.sun.mail.imap.IMAPFolder;
import com.sun.mail.imap.IMAPStore;
import com.sun.mail.pop3.POP3Folder;
import com.sun.mail.pop3.POP3Store;
import org.castor.util.Base64Decoder;

import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.*;
import javax.mail.internet.*;
import javax.mail.search.*;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.Security;
import java.util.Date;
import java.util.List;
import java.util.Properties;

/**
 * @author jiangyw
 * @date 2019/1/7 19:29
 * <p>
 * 处理邮件工具类
 * 接收/发送服务器根据不同邮箱有不同的地址
 * 在邮箱设置里都可找到
 */
public class MailUtil {
    // 发送服务器
    private static final String SEND_SMTP_HOST = "smtp.exmail.qq.com";
    private static final int SEND_SMTP_PORT = 465;

    // 接收服务器
    private static final String RECEIVE_IMAP_HOST = "imap.exmail.qq.com";
    private static final int RECEIVE_IMAP_PORT = 993;

    private static final String RECEIVE_POP_HOST = "pop.exmail.qq.com";
    private static final int RECEIVE_POP_PORT = 110;

    // 账号
    private String user;
    // 密码
    private String password;

    private IMAPFolder imapFolder = null;
    private IMAPStore imapStore = null;

    private POP3Store pop3Store = null;
    private POP3Folder pop3Folder = null;

    public MailUtil() {
        this("your email address", "your email password");
    }

    private MailUtil(String user, String password) {
        this.user = user;
        this.password = password;
    }


    /**
     * 发送邮件
     *
     * @param toEmail 收件人邮箱地址
     * @param subject 邮件标题
     * @param content 邮件内容 可以是html内容
     */
    public void send(String toEmail, String subject, String content) {
        Session session = loadSendSession();
        // session.setDebug(true);
        // 创建邮件消息
        MimeMessage message = new MimeMessage(session);
        try {
            // 设置发件人
            message.setFrom(new InternetAddress(user));
            Address[] a = new Address[1];
            a[0] = new InternetAddress(user);
            message.setReplyTo(a);
            // 设置收件人
            InternetAddress to = new InternetAddress(toEmail);
            message.setRecipient(MimeMessage.RecipientType.TO, to);
            // 设置邮件标题
            message.setSubject(subject);
            // 设置邮件的内容体
            message.setContent(content, "text/html;charset=UTF-8");
            // 发送邮件
            Transport.send(message);
        } catch (MessagingException e) {
            e.printStackTrace();
            String err = e.getMessage();
            // 在这里处理message内容, 格式是固定的
            System.out.println(err);
        }
    }


    /**
     * 发送邮件 带附件
     *
     * @param toEmail    收件人邮箱地址
     * @param subject    邮件标题
     * @param content    邮件内容 可以是html内容
     * @param attachPath 附件路径
     */
    public void send(String toEmail, String subject, String content, String attachPath) {
        Session session = loadSendSession();

        MimeMessage mm = new MimeMessage(session);
        try {
            //发件人
            mm.setFrom(new InternetAddress(user));
            //收件人
            mm.setRecipient(Message.RecipientType.TO, new InternetAddress(toEmail)); // 设置收件人
            // mm.setRecipient(Message.RecipientType.CC, new
            // InternetAddress("XXXX@qq.com")); //设置抄送人
            //标题
            mm.setSubject(subject);
            //内容
            Multipart multipart = new MimeMultipart();
            //body部分
            BodyPart contentPart = new MimeBodyPart();
            contentPart.setContent(content, "text/html;charset=utf-8");
            multipart.addBodyPart(contentPart);

            //附件部分
            BodyPart attachPart = new MimeBodyPart();
            FileDataSource fileDataSource = new FileDataSource(attachPath);
            attachPart.setDataHandler(new DataHandler(fileDataSource));
            attachPart.setFileName(MimeUtility.encodeText(fileDataSource.getName()));
            multipart.addBodyPart(attachPart);

            mm.setContent(multipart);
            Transport.send(mm);
        } catch (Exception e) {
            e.printStackTrace();
            String err = e.getMessage();
            // 在这里处理message内容, 格式是固定的
            System.out.println(err);
        }

    }


    private Session loadSendSession() {
        try {
            // 配置发送邮件的环境属性
            final Properties props = new Properties();
            // 表示SMTP发送邮件,需要进行身份验证
            props.put("mail.smtp.auth", "true");
            props.put("mail.smtp.host", SEND_SMTP_HOST);
            // props.put("mail.smtp.port", SEND_SMTP_PORT);
            // 如果使用ssl,则去掉使用25端口的配置,进行如下配置,
            props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
            props.put("mail.smtp.socketFactory.port", SEND_SMTP_PORT);
            props.put("mail.smtp.port", SEND_SMTP_PORT);
            // 发件人的账号
            props.put("mail.user", user);
            // 访问SMTP服务时需要提供的密码
            props.put("mail.password", password);
            // 构建授权信息,用于进行SMTP进行身份验证
            Authenticator authenticator = new Authenticator() {
                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    // 用户名、密码
                    String userName = props.getProperty("mail.user");
                    String password = props.getProperty("mail.password");
                    return new PasswordAuthentication(userName, password);
                }
            };
            // 使用环境属性和授权信息,创建邮件会话
            return Session.getInstance(props, authenticator);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("mail session is null");
        }
        return null;
    }

    public void saveFile(List<MailAttachment> list) throws IOException {
        for (MailAttachment mailAttachment : list) {
            InputStream inputStream = mailAttachment.getInputStream();
        	FileOutputStream outputStream = new FileOutputStream(new File("C:\\Users\\jiangyw\\Desktop\\test.pdf"));
        	int len;
     	 	byte[] bytes = new byte[1024];
       		while ((len = inputStream.read(bytes)) != -1) {
	            outputStream.write(bytes, 0, len);
	            outputStream.flush();
       		}
            inputStream.close();
        	outputStream.close();
        }

    }

    /**
     * 获取附件
     * 只获取附件里的
     * 邮件内容里的附件(图片等)忽略
     * @param part 邮件中多个组合体中的其中一个组合体
     * @param list 附件容器
     * @throws UnsupportedEncodingException
     * @throws MessagingException
     * @throws FileNotFoundException
     * @throws IOException
     */
    public void getAttachment(Part part, List<MailAttachment> list) 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 disposition = bodyPart.getDisposition();
                if (disposition != null && (disposition.equalsIgnoreCase(Part.ATTACHMENT) || disposition.equalsIgnoreCase(Part.INLINE))) {

                    InputStream is = bodyPart.getInputStream();
                    // 附件名通过MimeUtility解码,否则是乱码
                    String name = MimeUtility.decodeText(bodyPart.getFileName());
                    list.add(new MailAttachment(name, is));


                } else if (bodyPart.isMimeType("multipart/*")) {
                    getAttachment(bodyPart, list);
                } else {
                    String contentType = bodyPart.getContentType();
                    if (contentType.contains("name") || contentType.contains("application")) {

                    }
                }
            }
        } else if (part.isMimeType("message/rfc822")) {
            getAttachment((Part) part.getContent(), list);
        }
    }

    /**
     * 判断邮件中是否包含附件
     *
     * @return 邮件中是否存在附件
     * @throws MessagingException
     * @throws IOException
     */
    public boolean hasAttachment(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 = hasAttachment(bodyPart);
                } else {
                    String contentType = bodyPart.getContentType();
                    if (contentType.contains("application")) {
                        flag = true;
                    }

                    if (contentType.contains("name")) {
                        flag = true;
                    }
                }
                if (flag) break;
            }
        } else if (part.isMimeType("message/rfc822")) {
            flag = hasAttachment((Part) part.getContent());
        }
        return flag;
    }

    /**
     * 获得邮件文本内容
     *
     * @param part        邮件体
     * @param textContent 存储邮件文本内容的字符串
     * @param htmlContent 存储邮件html内容的字符串
     * @throws MessagingException
     * @throws IOException
     */
    public void getMailTextContent(Part part, StringBuffer textContent, StringBuffer htmlContent) throws MessagingException, IOException {
        //如果是文本类型的附件,通过getContent方法可以取到文本内容,但这不是我们需要的结果,所以在这里要做判断
        boolean isContainTextAttach = part.getContentType().indexOf("name") > 0;
        if (part.isMimeType("text/*") && !isContainTextAttach) {
            String contentType = part.getContentType();
            if (contentType.startsWith("TEXT/PLAIN") || contentType.startsWith("text/plain")) {
                textContent.append(part.getContent().toString());
            }
            if (contentType.startsWith("TEXT/HTML") || contentType.startsWith("text/html")) {
                htmlContent.append(part.getContent().toString());
            }
        } else if (part.isMimeType("message/rfc822")) {
            getMailTextContent((Part) part.getContent(), textContent, htmlContent);
        } 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, textContent, htmlContent);
            }
        }
    }

    public String getSubject(Message message) throws MessagingException {
        return message.getSubject();
    }

    /**
     * 获取邮件信息
     * IMAP协议
     * 下载附件的时候巨慢
     * 如果需要获取附件的时候不推荐使用
     * 该协议下只能查询receivedDate
     * sentDate都为null
     *
     * @throws MessagingException
     */
    public Message[] getImapMessages() throws MessagingException {
        String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
        Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
        System.setProperty("mail.mime.splitlongparameters", "false");
        Properties props = System.getProperties();
        props.setProperty("mail.imap.socketFactory.class", SSL_FACTORY);
        props.setProperty("mail.imap.socketFactory.port", "993");
        props.setProperty("mail.imapStore.protocol", "imap");
        props.setProperty("mail.imap.host", RECEIVE_IMAP_HOST);
        props.setProperty("mail.imap.port", "993");
        props.setProperty("mail.imap.auth.login.disable", "true");
        Session session = Session.getDefaultInstance(props, null);
        session.setDebug(false);

        imapStore = (IMAPStore) session.getStore("imap");  // 使用imap会话机制,连接服务器
        imapStore.connect(RECEIVE_IMAP_HOST, RECEIVE_IMAP_PORT, user, password);
        imapFolder = (IMAPFolder) imapStore.getFolder("INBOX"); //收件箱

        imapFolder.open(Folder.READ_WRITE);

        // 获取前一天的邮件信息
        SearchTerm endTerm = new ReceivedDateTerm(ComparisonTerm.LE, new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000L));
        SearchTerm startTerm = new ReceivedDateTerm(ComparisonTerm.GE, new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000L));
        SearchTerm searchTerm = new AndTerm(startTerm, endTerm);

        return imapFolder.search(searchTerm);
    }

    /**
     * 通过pop3协议获取邮件信息
     * pop3协议下只能通过sentDate来查询
     * receivedDate都为null
     *
     * @return Message[]
     * @throws MessagingException
     */
    public Message[] getPopMessages() throws MessagingException {
        Properties props = new Properties();
        props.setProperty("mail.imapStore.protocol", "pop3");       // 使用pop3协议
        props.setProperty("mail.pop3.port", "110");           // 端口
        props.setProperty("mail.pop3.host", RECEIVE_POP_HOST);       // pop3服务器
        Session session = Session.getInstance(props);
        pop3Store = (POP3Store) session.getStore("pop3");
        pop3Store.connect(RECEIVE_POP_HOST, RECEIVE_POP_PORT, user, password);

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

        // 获取前一天的邮件信息
        SearchTerm endTerm = new SentDateTerm(ComparisonTerm.LE, new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000L));
        SearchTerm startTerm = new SentDateTerm(ComparisonTerm.GE, new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000L));
        SearchTerm searchTerm = new AndTerm(startTerm, endTerm);
//         得到收件箱中的所有邮件,并解析
        return pop3Folder.search(searchTerm);
    }

    /**
     * 关闭folder和store资源
     *
     * @throws MessagingException
     */
    public void close() throws MessagingException {
        if (imapFolder != null) {
            imapFolder.close(false);
        }
        if (imapStore != null) {
            imapStore.close();
        }
        if (pop3Folder != null) {
            pop3Folder.close(false);
        }
        if (pop3Store != null) {
            pop3Store.close();
        }
    }


}

附件类

很简单的容器类:

package com.xxx.xxx;

import java.io.InputStream;

/**
 * @author jiangyw
 * @date 2019/1/10 21:14
 *
 * 邮件附件类
 */
public class MailAttachment {
    private String name;
    private InputStream inputStream;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public InputStream getInputStream() {
        return inputStream;
    }

    public void setInputStream(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    public MailAttachment(String name, InputStream inputStream) {
        this.name = name;
        this.inputStream = inputStream;
    }
}

使用

public void readMail() throws MessagingException {
        long startTime = System.currentTimeMillis();
        MailUtil mailUtil = new MailUtil();
        Message[] messages = mailUtil.getPopMessages();

        for (Message message : messages) {
            String subject = mailUtil.getSubject(message);
            try {
               
                    List<MailAttachment> mailAttachments = new ArrayList<>();
                    mailUtil.getAttachment(message, mailAttachments);
                    saveAttachment(mailAttachments);
                
                }
            } catch (Exception ignore) {
               
            }
        }
        mailUtil.close();
        System.out.println("解析邮件共耗时:" + (System.currentTimeMillis() - startTime) + "毫秒");
    }

    private void saveAttachment(List<MailAttachment> list) throws IOException {
        for (MailAttachment mailAttachment : list) {
            InputStream inputStream = mailAttachment.getInputStream();
            FileOutputStream outputStream = new FileOutputStream(new File( "your path");
            int len;
            byte[] bytes = new byte[1024 * 1024];
            while ((len = inputStream.read(bytes)) != -1) {
                outputStream.write(bytes, 0, len);
                outputStream.flush();
            }
            inputStream.close();
            outputStream.close();
        }
    }

遇到的一些意外

  1. 下载附件速度慢到令人发指!!!(看图)
    《通过JavaMail下载邮件附件》
    两个一共不到4M的附件,下载总共用了1000多秒!!!
    解决方法:不要用IMAP协议接收邮件信息,用POP3协议,换了协议之后的速度(看图)
    《通过JavaMail下载邮件附件》
    速度差了两个数量级!!!
  2. 在代码里也注释了,用IMAP协议时没有SentDate,用POP3协议时没有ReceivedDate;这两个协议具体的区别百度一下你就知道,我是因为要获取前一天的邮件,所以是根据日期来搜索。
  3. 附件名乱码,这个其实很简单,用JavaMail自带的MimeUtility解码一下就好。
    原文作者:DuNineunique
    原文地址: https://blog.csdn.net/DuNineunique/article/details/86308154
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。

相关文章