前言:完成使用Java在收发电子邮件之前,让我们来看看传统邮件是如何发送的:传统邮件是通过邮局发送的,然后从一个邮局到另一个邮局,最后到达用户的邮箱。
┌──────────┐ ┌──────────┐ │PostOffice│ │PostOffice│ .───. ┌─────┐ ├──────────┤ ├──────────┤ ( ( ) │═══ ?│───>│ ┌─┐ ┌┐┌┐ │───>│ ┌─┐ ┌┐┌┐ │───> `─┬─' └─────┘ │ │?│ └┘└┘ │ │ │?│ └┘└┘ │ │ └─┴─┴──────┘ └─┴─┴──────┘ │
电子邮件的发送过程也类似,但电子邮件来自用户电脑的电子邮件软件,如Outlook,将其发送到邮件服务器,可通过多个邮件服务器中转,最终到达对方的邮件服务器,收件人可通过软件接收邮件:
┌─────────┐ ┌─────────┐ ┌─────────┐ │?????????│ │?????????│ │?????????│ ┌───────┐ ├─────────┤ ├─────────┤ ├─────────┤ ┌───────┐ │???????│ │?????????│ │?????????│ │?????????│ │???????│ ├───────┤ ├─────────┤ ├─────────┤ ├─────────┤ ├───────┤ │ │───>│O ???????│───>│O ???????│───>│O ???????│<───│ │ └───────┘ └─────────┘ └─────────┘ └─────────┘ └───────┘ MUA MTA MTA MDA MUA
理解几个概念:
MUA:Mail User Agent邮件代理(邮件软件)为用户服务
MTA:Mail Transfer Agent 邮件中转代理(邮件服务器)
MDA:Mail Delivery Agent代理邮件到达(最终到达的邮件服务器)
一旦电子邮件到达MDA,不再动了。事实上,电子邮件通常存储在MDA在服务器硬盘上,等待收件人通过软件或登录浏览器查看电子邮件。
准备SMTP登录信息
在发送邮件之前,我们必须首先确定它MTA邮件服务器地址和端口号。邮件服务器地址通常是smtp.example.com,邮件服务提供商确定端口号是25、465还是587。
常用的邮件服务提供商SMTP信息: ●QQ邮箱:SMTP服务器是smtp.qq.com,端口是465/587 ●163邮箱:SMTP服务器是smtp.163.com,端口是465 ●126邮箱:SMTP服务器是smtp.126.com,端口是25 ●Gmail邮箱:SMTP服务器是smtp.gmail.com,端口是465/587 准备好SMTP登录信息后,我们必须首先检查JavaMail相关的依赖Jar包javax.mail-1.6.2.jar加入当前项目。
然后,我们通过JavaMail API连接到SMTP以25端口为例,连接服务器SMTP服务器需要准备一个Properties对象,填写相关信息。最后获取Session例如,如果服务器需要认证,则需要输入Authenticator返回指定的用户名和密码。当我们得到它时Session实例后,打开调试模式可以看到SMTP通信的详细内容便于调试。
我们把获取Session实例代码包装在JavaMailUtils作为一种创造Session通过静态方法对象的工具类creatSession()方便我们快速创建Session对象。实现代码如下:
public class JavaMailUtils { public static Session creatSession() { // 服务器地址: String smtp = "smtp.126.com"; // 登录用户名: String username = "xxxxxxxxxxxx@126.com"; // 登录口令: String password = "xxxxxxxxxxxxxxxxxxxx"; // 连接到SMTP服务器25端口: Properties props = new Properties(); props.put("mail.smtp.host", smtp); // SMTP主机名 props.put("mail.smtp.port", "25"); // 主机端口号 props.put("mail.smtp.auth", "true"); // 是否需要用户认证 props.put("mail.smtp.starttls.enable", "true"); // 启用TLS加密 // 获取Session实例: //参数1:SMTP连接服务器的信息 //参数2:用户认证对象(Authenticator匿名实现类接口) Session session = Session.getInstance(props, new Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(username, password); } }); // 设置debug调试方便: session.setDebug(true); return session; } }
发送邮件
当我们发送时,我们需要构建一个Message对象,然后调用Transport.send(Message)可完成发送:绝大多数邮件服务器要求发送方地址与登录用户名一致,否则发送将失败。
try { Session session = JavaMailUtils.creatSession(); MimeMessage message = new MimeMessage(session); // 设置发送方地址: message.setFrom(new InternetAddress("xxxxxxxxxxxxxx@126.com")); // 设置接收方地址: TO主要收件人 CC抄送人 message.setRecipient(Message.RecipientType.TO, new InternetAddress("xxxxxxxxxxxxx@qq.com")); // 设置邮件主题: message.setSubject("这是一封测试邮件", "UTF-8"); // 设置邮件文本: message.setText("<b>爱你</b>孤身走暗巷...", "UTF-8","html");////邮件正文包含在邮件正文中"html"标签(控制文本格式) // 发送: Transport.send(message); } catch (AddressException e) { e.printStackTrace(); } catch (MessagingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
填写真实地址并运行上述代码。我们可以在控制台上看到它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.126.com, user=Lenovo, password=<null> DEBUG SMTP: useEhlo true, useAuth true DEBUG SMTP: trying to connect to host "smtp.126.com", port 25, isSSL false 220 126.com Anti-spam GT for Coremail System (126com[20140526]) DEBUG SMTP: connected to host "smtp.126.com", port: 25 EHLO LAPTOP-DMM2VL3R.mshome.net 250-mail 250-PIPELINING 250-AUTH LOGIN PLAIN XOAUTH2 250-AUTH=LOGIN PLAIN XOAUTH2 250-coremail 1Uxr2xKj7kG0xkI7xGrU7I0s8FY2U3Uj8Cz28x1UUUUU7Ic2I0Y2UFvE1abUCa0xDrUUUUj
250-STARTTLS
250-ID
250 8BITMIME
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "AUTH", arg "LOGIN PLAIN XOAUTH2"
DEBUG SMTP: Found extension "AUTH=LOGIN", arg "PLAIN XOAUTH2"
DEBUG SMTP: Found extension "coremail", arg "1Uxr2xKj7kG0xkI17xGrU7I0s8FY2U3Uj8Cz28x1UUUUU7Ic2I0Y2UFvE1abUCa0xDrUUUUj"
DEBUG SMTP: Found extension "STARTTLS", arg ""
DEBUG SMTP: Found extension "ID", arg ""
DEBUG SMTP: Found extension "8BITMIME", arg ""
STARTTLS
220 Ready to start TLS
EHLO LAPTOP-DMM2VL3R.mshome.net
250-mail
250-PIPELINING
250-AUTH LOGIN PLAIN XOAUTH2
250-AUTH=LOGIN PLAIN XOAUTH2
250-coremail 1Uxr2xKj7kG0xkI17xGrU7I0s8FY2U3Uj8Cz28x1UUUUU7Ic2I0Y2UrxbFmDUCa0xDrUUUUj
250-STARTTLS
250-ID
250 8BITMIME
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "AUTH", arg "LOGIN PLAIN XOAUTH2"
DEBUG SMTP: Found extension "AUTH=LOGIN", arg "PLAIN XOAUTH2"
DEBUG SMTP: Found extension "coremail", arg "1Uxr2xKj7kG0xkI17xGrU7I0s8FY2U3Uj8Cz28x1UUUUU7Ic2I0Y2UrxbFmDUCa0xDrUUUUj"
DEBUG SMTP: Found extension "STARTTLS", arg ""
DEBUG SMTP: Found extension "ID", arg ""
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: protocolConnect login, host=smtp.126.com, user=tyqiqiiqiii@126.com, 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
MAIL FROM:<tyqiqiiqiii@126.com>
250 Mail OK
RCPT TO:<1601671085@qq.com>
250 Mail OK
DEBUG SMTP: Verified Addresses
DEBUG SMTP: 1601671085@qq.com
DATA
354 End data with <CR><LF>.<CR><LF>
Date: Sun, 17 Jul 2022 16:58:47 +0800 (CST)
From: tyqiqiiqiii@126.com
To: 1601671085@qq.com
Message-ID: <2128227771.0.1658048327389@LAPTOP-DMM2VL3R.mshome.net>
Subject: =?UTF-8?B?6L+Z5piv5LiA5bCB5rWL6K+V6YKu5Lu2?=
MIME-Version: 1.0
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: base64
PGI+54ix5L2gPC9iPuWtpOi6q+i1sOaal+W3ty4uLg==
.
250 Mail OK queued as smtp1,C8mowACHnydGz9NieECmGw--.58834S3 1658048329
DEBUG SMTP: message successfully delivered to mail server
QUIT
221 Bye
从上面的调试信息可以看出,SMTP协议是一个请求-响应协议,客户端总是发送命令,然后等待服务器响应。服务器响应总是以数字开头,后面的信息才是用于调试的文本。这些响应码已经被定义在SMTP协议中了,查看具体的响应码就可以知道出错原因。
发送附件
要在电子邮件中携带附件,我们就不能直接调用message.setText()方法,而是要构造一个Multipart对象:
一个Multipart对象可以添加若干个BodyPart,其中第一个BodyPart是文本,即邮件正文,后面的BodyPart是附件。BodyPart依靠setContent()决定添加的内容,如果添加文本,用setContent("...", "text/plain;charset=utf-8")添加纯文本,或者用setContent("...", "text/html;charset=utf-8")添加HTML文本。如果添加附件,需要设置文件名(不一定和真实文件名一致),并且添加一个DataHandler(),传入文件的MIME类型。二进制文件可以用application/octet-stream,Word文档则是application/msword。 最后,通过setContent()把Multipart添加到Message中,即可发送。
// 创建MimeMessage邮件信息对象
// ...略
// 创建Multipart复合对象
Multipart multipart = new MimeMultipart();
// 添加text:
BodyPart textpart = new MimeBodyPart();
textpart.setContent(body, "text/html;charset=utf-8");
multipart.addBodyPart(textpart);
// 添加image:
BodyPart imagepart = new MimeBodyPart();
imagepart.setFileName(附件名称);
imagepart.setDataHandler(new DataHandler(new ByteArrayDataSource(文件流字节数组, "application/octet-stream")));
multipart.addBodyPart(imagepart);
// 设置邮件内容为multipart:
message.setContent(multipart);
发送内嵌图片的HTML邮件
如果需要在HTML邮件中内嵌图片,可以选择在邮件中加入<img src="http://example.com/test.jpg">,这样的外部图片链接通常会被邮件客户端过滤,并提示用户显示图片并不安全。只有内嵌的图片才能正常在邮件中显示。所以,这种方式并不推荐。 推荐将内嵌图片作为一个附件嵌入邮件,即邮件本身也是Multipart,但需要做一点额外的处理:
//邮件内容"复合"对象
Multipart multipart=new MimeMultipart();
//正文
BodyPart textPart = new MimeBodyPart();
StringBuilder body=new StringBuilder();
body.append("<h1>猫</h1>");
body.append("<img src=\"cid:cat\"/>");//通过内容id引用附件图片
//参数1:正文内容
//参数2:内容类型;字符编码集
textPart.setContent(body.toString(),"text/html;charset=utf-8");
//附件
BodyPart imagePart = new MimeBodyPart();
imagePart.setFileName("cat.jpg");//设置附件文件的显示名称
//数据处理对象(读取附件文件从本地磁盘进行读取)
imagePart.setDataHandler(new DataHandler(new ByteArrayDataSource(Files.readAllBytes(Paths.get("E:\\picture\\8504221.jpg")), "application/octet-stream")));
imagePart.setHeader("Content-ID","<cat>");//设置内容id
//添加至邮件内容
multipart.addBodyPart(textPart);//添加正文
multipart.addBodyPart(imagePart);//添加附件
//设置邮件内容
message.setContent(multipart);
以上是最近学习中收获知识的一点小总结,如果对你有帮助,请留下你的点赞和关注,谢谢!