【Java】JavaMail的使用详解,看完真的明白了

总体介绍:

公司近期接到需求,由于安卓和IOS客户端在转发邮件时会重新下载一遍老邮件里附件,这个过程比较耗时,体验不佳,现在需要实现客户端走服务器来转发,免去下载的过程
我们先了解一下两种协议格式:

SMTP:邮件发送协议 ssl对应端口465 非ssl对应端口25
IMAP:收邮件协议 ssl对应端口993 非ssl对应端口143

以上描述的是邮箱服务器的默认端口 可自定义或变更 但主流端口就是以上这么几个

附上JavaMail官网地址,遇到问题时在里面会发现很多灵感:
https://javaee.github.io/javamail/docs/api/

Maven引用

<dependency>
            <groupId>com.sun.mail</groupId>
            <artifactId>javax.mail</artifactId>
            <version>1.6.2</version>
</dependency>

JavaMail邮件格式介绍

如果想了解邮件如何发送,我们必须了解邮件消息的正文结构
我们先了解一下发邮件时的流程:

//创建消息对象
MimeMessage message = new MimeMessage(session);
//创建消息正文
MimeMultipart mimmultiPart = new MimeMultipart();
//创建消息体
MimeBodyPart mimeBodyPart = new MimeBodyPart();
//将消息体包裹在正文中
mimmultiPart.addBodyPart(mimeBodyPart);
//将正文包裹在消息对象中
message.setContent(mimmultiPart )
//发邮件协议是smtp  收邮件协议是imap
Transport transport = session.getTransport("smtp");
//发送邮件
transport.sendMessage(mimeMessage,"某收件人");

MimeMessage 包含了 MimeMultipart,
MimeMultipart 包含了 MimeBodyPart
这里面比较绝妙的就是
MimeBodyPart 其实反过来也可以包含MimeMultipart 后面我们将看到
以下4种格式均为笔者通过FoxMail发送邮件,并通过JavaMail去找邮件而来的结果

①纯文本

邮件正文仅带有文本文字

--- MimeMutiPart("ALTERNATIVE")
          MimeBodyPart0("TEXT/PLAIN")
          MimeBodyPart1("TEXT/HTML")

使用FoxMail发邮件时,尽管是最简单的文本,也是一个MimeMutiPart包含了两个MimeBodyPart,一个对应纯文本,另一个对应html

②纯文本带附件

邮件正文包含文本文字+附件

--- MimeMutiPart("MIXED")
|------MimeBodyPart0 ---- MimeMutiPart("ALTERNATIVE")
|            MimeBodyPart0("TEXT/PLAIN")
|            MimeBodyPart1("TEXT/HTML")
|------MimeBodyPart1 (""APPLICATION/OCTET-STREAM")

文本的内容仍然是一个MutiPart包含了两个BodyPart 只不过此部分被包含进了MimeBodyPart0
附件的部分是一个独立的MimeBodyPart1

③富文本

邮件正文包含富文本文字

--- MimeMutiPart("RELATED")
|------MimeBodyPart0 ---- MimeMutiPart("ALTERNATIVE")
|            MimeBodyPart0("TEXT/PLAIN")
|            MimeBodyPart1("TEXT/HTML")
|------MimeBodyPart1("IMAGE/GIF")
|------MimeBodyPart2("IMAGE/GIF")
|------MimeBodyPart3("IMAGE/GIF")

文本的内容还是相同的,一个MutiPart包含了两个BodyPart 此MutiPart被包含进了
MimeBodyPart0
MimeBodyPart1、MimeBodyPart2、MimeBodyPart3属于”INLINE”的附件,而非attach的附件属于富文本中的图片和表情等资源

④富文本带附件

邮件正文包含富文本文字+附件

--- MimeMutiPart("MIXED")
|------MimeBodyPart0 ---- MimeMutiPart("RELATED")
|            MimeBodyPart0 ---- MimeMutiPart("ALTERNATIVE")
|                 MimeBodyPart0("TEXT/PLAIN")
|                 MimeBodyPart1("TEXT/HTML")
|            MimeBodyPart1("IMAGE/GIF")
|            MimeBodyPart2("IMAGE/GIF")
|            MimeBodyPart3("IMAGE/GIF")
|------MimeBodyPart1 ("APPLICATION/OCTET-STREAM")
|------MimeBodyPart2 ("APPLICATION/OCTET-STREAM")
|------MimeBodyPart3 ("APPLICATION/OCTET-STREAM")

这是最复杂的一种,也是可以基本满足我们日常需求的一种,最里面的一层还是ALTERNATIVE的MutiPart包含了两个文本类型,同层级包含着”INLINE”的附件
最外层是一些真正的附件

以上四种类型基本上涵盖了我们日常使用的格式,不难发现MimeMutiPart一共有三种类型:

1.MIXED:包含附件的文档
2.ALTERNATIVE:

包含text文档和html文档,为什么是两种 一般邮箱客户端比如FoxMail 可以切换阅读模式
一个是超文本阅读格式 对应html
一个是纯文本(去掉文中图片和表情)格式 对应text

3.RELATED:图文(富文本)

发送邮件代码样例

首先,邮箱服务都是基于Session会话开始,首先我们构建一个Session

public final static String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
public final static String DEFAULT_FACTORY = "javax.net.DefaultSocketFactory";
public static Session generSession(Dto_MainForward dto_mainForward,MailServers mailinfo) throws NoSuchProviderException, GeneralSecurityException {
        Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
        MailSSLSocketFactory socketFactory= new MailSSLSocketFactory();
        socketFactory.setTrustAllHosts(true);
        Properties props = System.getProperties();
        props.put("mail.imaps.ssl.socketFactory", socketFactory);
        //此处规定连接邮箱的方式是https还是http 默认一种即可
        props.setProperty("mail.imap.socketFactory.class", mailinfo.getSslable() ? SSL_FACTORY : DEFAULT_FACTORY);
        //对应邮箱服务器的端口 ssl对应465 普通对应25 视实际情况而定
        props.setProperty("mail.imap.socketFactory.port", mailinfo.getMailboxPort().toString());
        props.setProperty("mail.store.protocol", "imap");
        //收信时邮箱服务器地址 比如qq邮箱对应imap.exmail.qq.com
        props.setProperty("mail.imap.host", mailinfo.getMailbox());
        //收信使用的端口  ssl对应993 普通对应143 视实际情况而定
        props.setProperty("mail.imap.port", mailinfo.getMailboxPort().toString());
        props.setProperty("mail.imap.auth.login.disable", "true");
        props.setProperty("mail.imap.partialfetch", "false");
        props.setProperty("mail.imaps.partialfetch", "false");
        Session session = Session.getInstance(props, null);
        //是否开启debug模式 如果设置成true 你将得到大量邮件log信息帮助调试
        session.setDebug(false);
        return session;
    }

构建纯文本无附件的邮件:

public void SendJustText(Session session) throws MessagingException {
        MimeMessage message = new MimeMessage(session);
        //邮件标题
        message.setSubject("纯文本");
        //邮件发送人
        message.setFrom(new InternetAddress(MailService.senderMail));
        //邮件接收人     message.setRecipients(Message.RecipientType.TO,InternetAddress.parse(MailService.senderMail));
        //构建最外层的MimeMutipart
        MimeMultipart mimeMultipart = new MimeMultipart("alternative");
        //构建邮件正文Body
        MimeBodyPart mimeBodyPart = new MimeBodyPart();
        mimeBodyPart.setContent("我是纯文本正文","TEXT/PLAIN; charset=GB2312");
        //将body追加到Mutipart
        mimeMultipart.addBodyPart(mimeBodyPart);
        message.setContent(mimeMultipart);
        //连接邮箱服务器
        Transport transport = session.getTransport("smtp");
        //可以看成自己的邮箱登录 用时自行替换参数
        transport.connect(MailService.mailHost, MailService.senderMail, MailService.password);
        //发送消息
        transport.sendMessage(message, message.getAllRecipients());
    }

构建纯文本带附件的邮件:

--- MimeMutiPart("MIXED")
|------MimeBodyPart0 ---- MimeMutiPart("ALTERNATIVE")
|            MimeBodyPart0("TEXT/PLAIN")
|            MimeBodyPart1("TEXT/HTML")
|------MimeBodyPart1 (""APPLICATION/OCTET-STREAM")
|------MimeBodyPart2 (""APPLICATION/OCTET-STREAM")
public void SendAttachText(Session session) throws MessagingException, IOException {
        MimeMessage message = new MimeMessage(session);
        //邮件标题
        message.setSubject("纯文本带附件");
        //邮件发送人
        message.setFrom(new InternetAddress(MailService.senderMail));
        //邮件接收人
        message.setRecipients(Message.RecipientType.TO,InternetAddress.parse(MailService.senderMail));

        //构建最外层的MimeMutipart
        MimeMultipart mimeMultipart = new MimeMultipart("mixed");
        //构建邮件正文Body
        MimeBodyPart mimeBodyPart = new MimeBodyPart();
        mimeBodyPart.setContent("我是纯文本带附件正文","TEXT/PLAIN; charset=GB2312");
        //将body追加到Mutipart
        mimeMultipart.addBodyPart(mimeBodyPart);
        //添加第一个附件
        mimeMultipart.addBodyPart(getAttchment1());
        //添加第二个附件
        mimeMultipart.addBodyPart(getAttchment2());
        message.setContent(mimeMultipart);
        //连接邮箱服务器
        Transport transport = session.getTransport("smtp");
        //可以看成自己的邮箱登录
        transport.connect(MailService.mailHost, MailService.senderMail, MailService.password);
        //发送消息
        transport.sendMessage(message, message.getAllRecipients());
    }


    /**
     * 构建附件1
     * @return
     * @throws IOException
     * @throws MessagingException
     */
    private MimeBodyPart getAttchment1() throws IOException, MessagingException {
        MimeBodyPart attachPart = new MimeBodyPart();
        File file = new File("C:\\Users\\xxx\\Desktop\\spring初始化.txt");
        attachPart.attachFile(file);
        //解决中文乱码问题
        attachPart.setFileName(MimeUtility.encodeText(file.getName()));
        return attachPart;
    }
    /**
     * 构建附件2
     * @return
     * @throws IOException
     * @throws MessagingException
     */
    private MimeBodyPart getAttchment2() throws IOException, MessagingException {
        MimeBodyPart attachPart = new MimeBodyPart();
        File file = new File("C:\\Users\\xxx\\Desktop\\长春农商行问题修复\\操作说明.txt");
        attachPart.attachFile(file);
        //解决中文乱码问题
        attachPart.setFileName(MimeUtility.encodeText(file.getName()));
        return attachPart;
    }

这里我们会发现一个问题:

一般来讲邮件附件不是从本地获取 而是服务器接收客户端发过来的文件,这样如何写呢?于是我们的getAttchment1方法就变成了这样:
    /**
     * 构建附件1
     * @return
     * @throws IOException
     * @throws MessagingException
     */
   private MimeBodyPart getAttchment1(MultipartFile attachFile) throws IOException, MessagingException {
        if (attachFile != null) {
            MimeBodyPart attachPart = new MimeBodyPart();
            ByteArrayDataSource byteArrayDataSource = new ByteArrayDataSource(multipartFile.getInputStream(),multipartFile.getContentType());
                //为文件流设置文件名称
                byteArrayDataSource.setName(multipartFile.getOriginalFilename());
                DataHandler dataHandler = new DataHandler(new ByteArrayDataSource(multipartFile.getInputStream(),multipartFile.getContentType()));
            attachPart.setDataHandler(new DataHandler(dataSource));
            //这里文件源名称由客户端上传 一般都是经过url编码的 我们先解码 再转成邮箱的编码    
            attachPart.setFileName(MimeUtility.encodeText(URLDecoder.decode(attachFile.getOriginalFilename(), "utf-8")));
        }
    }

构建图文混合无附件的邮件:

--- MimeMutiPart("RELATED")
|------MimeBodyPart0 ---- MimeMutiPart("ALTERNATIVE")
|            MimeBodyPart0("TEXT/PLAIN")
|            MimeBodyPart1("TEXT/HTML")
|------MimeBodyPart1("IMAGE/GIF")
|------MimeBodyPart2("IMAGE/GIF")
|------MimeBodyPart3("IMAGE/GIF")
public void SendJianShuPicText(Session session) throws MessagingException {
        MimeMessage message = new MimeMessage(session);
        //邮件标题
        message.setSubject("富文本无附件");
        //邮件发送人
        message.setFrom(new InternetAddress(MailService.senderMail));
        //邮件接收人
        message.setRecipients(Message.RecipientType.TO,InternetAddress.parse(MailService.senderMail));
        //构建最外层的MimeMutipart
        MimeMultipart mimeMultipart = new MimeMultipart("related");
        //将文本的bodyPart追加到Mutipart
        mimeMultipart.addBodyPart(getAlterBodyPart());
        message.setContent(mimeMultipart);
        //连接邮箱服务器
        Transport transport = session.getTransport("smtp");
        //可以看成自己的邮箱登录
        transport.connect(MailService.mailHost, MailService.senderMail, MailService.password);
        //发送消息
        transport.sendMessage(message, message.getAllRecipients());
    }

    public MimeBodyPart getAlterBodyPart() throws MessagingException {
        //要返回的alterBodyPart
        MimeBodyPart alterBodyPart = new MimeBodyPart();
        //定义alterBodyPart下的mutiPart
        MimeMultipart alterMutiPart = new MimeMultipart("ALTERNATIVE");
        //定义文本bodyPart
        MimeBodyPart textBodyPart = new MimeBodyPart();
        textBodyPart.setContent("我是富文本无附件正文","TEXT/PLAIN; charset=GB2312");
        //定义html的bodyPart
        MimeBodyPart htmlBodyPart =new MimeBodyPart();
        htmlBodyPart.setContent("<html><div><h3>我是富文本无附件正文</h3></div></br><img src='cid:lenglengliang1'/></br><strong>我的冷冷凉2</strong></br><img src='cid:lenglengliang2'/></br>不下地的冷冷凉</html>","TEXT/HTML; charset=GB2312");
        //添加文本的bodyPart
        alterMutiPart.addBodyPart(textBodyPart);
        //添加html的bodyPart
        alterMutiPart.addBodyPart(htmlBodyPart);
        //定义inline附件的bodyPart
        MimeBodyPart inlineBodyPart = new MimeBodyPart();
        DataSource dataSource = new FileDataSource(new File("D:\\一些图片\\无聊的图片\\未来战士\\timg.jpg"));
        inlineBodyPart.setDataHandler(new DataHandler(dataSource));
        inlineBodyPart.setDisposition(MimeBodyPart.INLINE);
        inlineBodyPart.setContentID("<lenglengliang1>");
        //添加inline的bodyPart
        alterMutiPart.addBodyPart(inlineBodyPart);

        MimeBodyPart inlineBodyPart2 = new MimeBodyPart();
        DataSource dataSource2 = new FileDataSource(new File("D:\\一些图片\\无聊的图片\\未来战士\\timg (2).jpg"));
        inlineBodyPart2.setDataHandler(new DataHandler(dataSource2));
        inlineBodyPart2.setDisposition(MimeBodyPart.INLINE);
        inlineBodyPart2.setContentID("<lenglengliang2>");
        //添加inline的bodyPart
        alterMutiPart.addBodyPart(inlineBodyPart2);
        //装载mutiPart
        alterBodyPart.setContent(alterMutiPart);
        return alterBodyPart;
    }

将图(不管是图片还是一些表情)添加到正文的逻辑是:
1.首先保证html文本中包含img标签 富文本在邮箱中显示的其实就是一个html页面
2.图片和<img src=”” />的关联关系就是以下代码创建的

 htmlBodyPart.setContent("<html><img src='cid:lenglengliang1'/></html>","TEXT/HTML; charset=GB2312");
 inlineBodyPart.setContentID("<lenglengliang1>");
html中的src 必须包含cid: 这是rfc标准格式
bodyPart.setContentID 并不包含”cid:”,但却有 “<>” 标签 这是邮件的标准格式,如果不带,发送时可能没问题,但是这个邮件被安卓或者ios端转发解析时可能出现问题,他们的标准库判断了”<>”
PS:项目中笔者曾经为图文中的图发送后变成了附件而苦恼了很久,原因就是这个cid src中需要带但setContentID时却不需要带

构建图文混合带附件的邮件:

这是最复杂的也是最常用的

--- MimeMutiPart("MIXED")
|------MimeBodyPart0 ---- MimeMutiPart("RELATED")
|            MimeBodyPart0 ---- MimeMutiPart("ALTERNATIVE")
|                 MimeBodyPart0("TEXT/PLAIN")
|                 MimeBodyPart1("TEXT/HTML")
|            MimeBodyPart1("IMAGE/GIF")
|            MimeBodyPart2("IMAGE/GIF")
|            MimeBodyPart3("IMAGE/GIF")
|------MimeBodyPart1 ("APPLICATION/OCTET-STREAM")
|------MimeBodyPart2 ("APPLICATION/OCTET-STREAM")
|------MimeBodyPart3 ("APPLICATION/OCTET-STREAM")
public void SendJianShuAttachPicText(Session session) throws MessagingException, IOException {
        MimeMessage message = new MimeMessage(session);
        //邮件标题
        message.setSubject("富文本带附件");
        //邮件发送人
        message.setFrom(new InternetAddress(MailService.senderMail));
        //邮件接收人
        message.setRecipients(Message.RecipientType.TO,InternetAddress.parse(MailService.senderMail));
        //构建最外层的MimeMutipart
        MimeMultipart mimeMultipart = new MimeMultipart("related");
        //将文本的bodyPart追加到Mutipart
        mimeMultipart.addBodyPart(getAlterBodyPart());
        message.setContent(mimeMultipart);
        //连接邮箱服务器
        Transport transport = session.getTransport("smtp");
        //可以看成自己的邮箱登录
        transport.connect(MailService.mailHost, MailService.senderMail, MailService.password);
        //发送消息
        transport.sendMessage(message, message.getAllRecipients());
    }

    public MimeBodyPart getAlterBodyPart() throws MessagingException, IOException {
        //要返回的alterBodyPart
        MimeBodyPart alterBodyPart = new MimeBodyPart();
        //定义alterBodyPart下的mutiPart
        MimeMultipart alterMutiPart = new MimeMultipart("ALTERNATIVE");
        //定义文本bodyPart
        MimeBodyPart textBodyPart = new MimeBodyPart();
        textBodyPart.setContent("我是富文本带附件正文","TEXT/PLAIN; charset=GB2312");
        //定义html的bodyPart
        MimeBodyPart htmlBodyPart =new MimeBodyPart();
        htmlBodyPart.setContent("<html><div><h3>我是富文本带附件正文</h3></div></br><img src='cid:lenglengliang1'/></br><strong>我的冷冷凉2</strong></br><img src='cid:lenglengliang2'/></br>不下地的冷冷凉</html>","TEXT/HTML; charset=GB2312");
        //添加文本的bodyPart
        alterMutiPart.addBodyPart(textBodyPart);
        //添加html的bodyPart
        alterMutiPart.addBodyPart(htmlBodyPart);
        //定义inline附件的bodyPart
        MimeBodyPart inlineBodyPart = new MimeBodyPart();
        DataSource dataSource = new FileDataSource(new File("D:\\一些图片\\无聊的图片\\未来战士\\timg.jpg"));
        inlineBodyPart.setDataHandler(new DataHandler(dataSource));
        inlineBodyPart.setDisposition(MimeBodyPart.INLINE);
        inlineBodyPart.setContentID("<lenglengliang1>");
        //添加inline的bodyPart
        alterMutiPart.addBodyPart(inlineBodyPart);

        MimeBodyPart inlineBodyPart2 = new MimeBodyPart();
        DataSource dataSource2 = new FileDataSource(new File("D:\\一些图片\\无聊的图片\\未来战士\\timg (2).jpg"));
        inlineBodyPart2.setDataHandler(new DataHandler(dataSource2));
        inlineBodyPart2.setDisposition(MimeBodyPart.INLINE);
        inlineBodyPart2.setContentID("<lenglengliang2>");
        //添加inline的bodyPart
        alterMutiPart.addBodyPart(inlineBodyPart2);
        //添加第一个附件
        alterMutiPart.addBodyPart(getAttchment1());
        //添加第二个附件
        alterMutiPart.addBodyPart(getAttchment2());
        //添加第三个附件
        alterMutiPart.addBodyPart(getAttchment3());
        //装载mutiPart
        alterBodyPart.setContent(alterMutiPart);
        return alterBodyPart;
    }

    /**
     * 构建附件1
     * @return
     * @throws IOException
     * @throws MessagingException
     */
    private MimeBodyPart getAttchment1() throws IOException, MessagingException {
        MimeBodyPart attachPart = new MimeBodyPart();
        File file = new File("C:\\Users\\yangzhi\\Desktop\\spring初始化.txt");
        attachPart.attachFile(file);
        attachPart.setFileName(MimeUtility.encodeText(file.getName()));
        attachPart.setDisposition(MimeBodyPart.ATTACHMENT);
        return attachPart;
    }

    /**
     * 构建附件2
     * @return
     * @throws IOException
     * @throws MessagingException
     */
    private MimeBodyPart getAttchment2() throws IOException, MessagingException {
        MimeBodyPart attachPart = new MimeBodyPart();
        File file = new File("C:\\Users\\yangzhi\\Desktop\\转发富文本(ceomg).eml");
        attachPart.attachFile(file);
        attachPart.setFileName(MimeUtility.encodeText(file.getName()));
        attachPart.setDisposition(MimeBodyPart.ATTACHMENT);
        return attachPart;
    }
    /**
     * 构建附件3
     * @return
     * @throws IOException
     * @throws MessagingException
     */
    private MimeBodyPart getAttchment3() throws IOException, MessagingException {
        MimeBodyPart attachPart = new MimeBodyPart();
        File file = new File("C:\\Users\\yangzhi\\Desktop\\邮箱开发准备\\邮箱id比对.txt");
        attachPart.attachFile(file);
        attachPart.setFileName(MimeUtility.encodeText(file.getName()));
        attachPart.setDisposition(MimeBodyPart.ATTACHMENT);
        return attachPart;
    }

邮件的结构精美的地方就是MutiPart和BodyPart的相互转换,文本虽多,但很多点都可以封装,为了方便看笔者就直接上的流水账,邮件的4个基本结构是构建我们代码思路的前提,感谢观看,如果此文对您有帮助 请点个小红心支持一下,后续的收邮件以及邮件的回执、一些坑会单独拿出来分享

图片[1]-【Java】JavaMail的使用详解,看完真的明白了-北冥博客

作者:会转圈儿的冷冷凉
链接:https://www.jianshu.com/p/3a118a81da3c
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

温馨提示:本文最后更新于2021-08-11,若文件或内容有错误或已失效,请在下方留言
© 版权声明
THE END
喜欢就支持一下吧
点赞0赞赏
分享
评论 抢沙发