欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

【Java】JavaMail编程实现邮件客户端-OutBox、InBox

程序员文章站 2022-06-22 19:12:07
JavaMail编程实现邮件客户端-OutBox & InBox....
  • 在上一篇《JavaMail编程实现邮件客户端-总览》中我们已经说完了邮箱客户端的登录界面、主界面,在主界面上点击OutBox按钮就能够进入发件箱,点击InBox按钮就能进入收件箱。这篇文章中,会详细介绍OutBox和InBox的界面设计以及功能的实现。
    【Java】JavaMail编程实现邮件客户端-OutBox、InBox

OutBox发送邮件.

UI.

  • 邮件的发送界面,我们都不陌生,一般在这个界面中我们需要填写三个部分:
  1. 收件人的邮箱地址;
  2. 邮件的主题;
  3. 邮件的正文以及附件(如果有的话).

下面是网易邮箱的发送界面,我们也是基于这种常见的邮箱发送界面进行的OutBox界面设计:
【Java】JavaMail编程实现邮件客户端-OutBox、InBox
而本项目中,发送邮件的OutBox最终GUI效果如下所示:
【Java】JavaMail编程实现邮件客户端-OutBox、InBox
界面中有三个待填写的文本输入框,分别对应于收件人邮箱地址、邮件主题和邮件正文。左手边的三个按钮,从上至下的功能依次为:发送编辑好的邮件、退出OutBox和添加附件。退出该界面的代码实现比较容易,只需要使用Java-Swing中提供的API即可:

private void Exit()
{
	int inquire = JOptionPane.showConfirmDialog(ClientSendPage.this,
		"Sure to leave OutBox?","Leave OutBox.",
		JOptionPane.YES_NO_OPTION);
	if(inquire==JOptionPane.YES_OPTION)
	{
		this.dispose();
	}
	else
	{
		this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
	}
}

发送无附件邮件.

  • 当我们编辑好一封邮件——填写了收件人地址、主题以及正文部分以后,例如下图中的状态:
    【Java】JavaMail编程实现邮件客户端-OutBox、InBox
    接下来只要点击左侧第一个按钮,就开始进行邮件的发送工作了。那么,客户端程序实际上做了哪些事情呢?在本项目中,有附件的邮件和无附件的邮件是被区别对待的,当SendButton被触发时,绑定在其上的动作首先判断这封待发邮件有没有附件,再决定如何进行发送。
SendButton.addActionListener(new ActionListener()
{
	public void actionPerformed(ActionEvent e)
	{
		try
		{
			if(hasAttachment)
			{
				SendMailPro();
			}
			else
			{
				SendMail();
			}
		}
		catch(Exception ex)
		{
			ex.printStackTrace();
		}
	}
});

由于我们这里的测试邮件是不带有附件的,所以会调用SendMail()方法进行发送。SendMail()方法中,首先需要进行环境的配置,包括邮件发送协议、邮件服务器的地址以及实际发送使用的端口号,我们前面的设计思路中曾经说过这是属于Session类实例对象的内容。本次项目中我们使用的是网易的邮箱客户端进行开发,所以实际的环境配置代码如下:

Properties pro = new Properties();
pro.put("mail.transport.protocol","smtp");
pro.put("mail.smtp.class","com.sun.mail.smtp.SMTPTransport");
pro.put("mail.smtp.host",SMTPServer);
		
/**SMTP port.*/
pro.put("mail.smtp.port","25");
		
/**Verify account.*/
pro.put("mail.smtp.auth","true");
		
session = Session.getInstance(pro, new Authenticator() 
{
	public PasswordAuthentication getPasswordAuthentication() 
	{ 
		return new PasswordAuthentication(Account, Password); 
	}      
});
		
transport = session.getTransport();

上述代码中,第一部分是完成属性的配置,然后封装成一个Session的对象;第二部分从这个Session对象中创建Transport的实例对象。需要注意的是此时,用户已经完成了邮件的编辑,而客户端已经完成了环境的配置,接下来客户端可以对用户编辑好的邮件数据进行封装了。同意是在设计思路中提到过,信息的封装也需要Session提供支持,而收件人地址、邮件主题以及邮件正文的内容,则可以从界面上的文本编辑框中轻松获得,封装信息的代码如下:

//Create a MimeMessage object.
MimeMessage message = new MimeMessage(NewSession);

//Set sender.
message.setFrom(new InternetAddress(Account));
//Set receiver.
message.setRecipients(Message.RecipientType.TO,
	InternetAddress.parse(Receiver.getText()));

//Set Subject.
message.setSubject(Topic.getText());

//Set mail body.
message.setText(MailMessage.getText());

//SAVE CHANGES.
message.saveChanges();

其中一定不能忽视最后的saveChanges(),该方法用于保存并且生成最终的邮件内容。至此客户端已经完成了邮件的封装任务,下一步就是将其交付给已经获取到的Transport对象,进行传输了,代码如下:

transport.connect();
transport.sendMessage(message, message.getAllRecipients());

到这里,客户端已经完成了从配置环境,到封装邮件信息,再到最后的实际发送邮件的任务,接下来只需要在邮件发送成功后,给用户一个发送成功的信息即可。
【Java】JavaMail编程实现邮件客户端-OutBox、InBox
稍后我们可以登录到实际的收件方邮箱中查看,是否本项目的第三方客户端真的发送了我们编辑的邮件。下图是Esperanto1949@163.com收件箱中实际收到的邮件内容,可以比较客户端上显示的发送时间和实际收到的邮件的发送时间,确认是同一份邮件。
【Java】JavaMail编程实现邮件客户端-OutBox、InBox

添加附件.

  • 现代很多的邮件中都添加了附件进行传输,那么我们在进行邮件封装的时候,就需要考虑如何表示出附件的数据。同样是MimeMessage的实例对象,但与上面代码中
message.setText(MailMessage.getText());

不同的是,这一次我们不仅仅有Text,我们还有附件。我们使用JavaMail中的MimeMultipart来表示一份带有附件的复杂邮件的主体部分,我们依次向其中添加邮件的正文以及附件(如果有多个的话)。

MIME消息的头字段Content-Type有三种类型:multipart/mixedmultipart/relatedmultipart/alternative(一封MIME邮件中的MIME消息可以有这三种组合关系).

  • 其中multipart/mixed表示内容是混合组合类型,内容可以是文本、声音和附件等不同邮件内容的混合体
  • multipart/related表示消息体的内容是关联(依赖)组合类型,例如正文使用HTML代码引用内嵌图片资源等等
  • multipart/alternative表示消息体中的内容是选择组合类型,例如一封邮件的正文同时采用HTML格式和普通文本格式进行表达。这样做的好处在于如果邮件阅读程序不支持HTML格式时,可以采用其中的文本格式进行替换

前面的部分都与简单邮件的封装一致,需要重新编写代码的就是有关封装附件数据的部分,代码展示如下:

//Get mail body text.
MimeBodyPart ContentPart = CreateContent(MailMessage.getText());

//Create mixed MimeMultipart object.
MimeMultipart AllMultiPart  =new MimeMultipart("mixed");

//Add mail body text.
AllMultiPart.addBodyPart(ContentPart);

//Add attachments in FileList.
for(int i=0;i<FileList.size();++i)
{
	AllMultiPart.addBodyPart(FileList.get(i));
}

//setContent() & saveChanges().
message.setContent(AllMultiPart);
message.saveChanges();

关于用户如何选择附件的问题,我们需要用到Java中的JFileChooser,维护一个文件队列来进行多个被选中附件的记录。这部分的代码如下:

private void AppendAttachment() throws Exception
{
	JFileChooser FileChooser = new JFileChooser();
	if(FileChooser.showOpenDialog(ClientSendPage.this)==JFileChooser.APPROVE_OPTION)
	{
		String FileAddr = FileChooser.getSelectedFile().getCanonicalPath();
		if(FileAddr!=null&&FileAddr.length()!=0)
		{
			hasAttachment=true;
			FileName.add(FileAddr);
		}
	}
}

InBox.

UI.

  • 本项目中收件箱的界面设计和发件箱大同小异,在这个界面上,我们可以看到当前收件箱中一共有多少封邮件(由于POP3协议的关系,它无法区分邮件之间的状态,所以不存在已读和未读邮件)。通过一个下拉框,我们可以选择想要查看的邮件并且下载其中的附件(如果有的话)。展示邮件时,我们也是展示三个部分:发送方地址、邮件主题和邮件正文(正文末附加有关附件的信息)。InBox最终的GUI效果如下:
    【Java】JavaMail编程实现邮件客户端-OutBox、InBox
    中间部分由于显示邮件的内容,左侧三个按钮分别用于刷新、查看选中邮件的内容以及下载选中邮件的附件,而右侧的一个按钮用于删除选中的邮件。

JComboBox.

  • 此处我们使用下拉框来展示当前收件箱中存在的邮件,JComboBox并没有很复杂的语法,实现的代码如下所示:
MailList = new JComboBox();	
MailList.setBounds(740, 30, 180, 50);
MailList.setMaximumRowCount(5);
		
for(int i=0;i<number;++i)
{
	MailList.addItem("Mail-No."+(i+1));
}
		
MailList.setSelectedIndex(0);

这里的两个方法setMaximunRowCount(int x)是指下列的视图中最多显示几个完整的item,我们从上面的下拉框效果图中也可以看出这一点,而setSelectedIndex(int x)则是设置默认选中的item的序列号(从0开始).
【Java】JavaMail编程实现邮件客户端-OutBox、InBox

查看无附件邮件的内容.

  • 无附件邮件的发送和查看都是最简单的情况,当用户选中了一封邮件后,我们就可以通过ViewButton来查看其内容。在设计思路中我们说过可以从Session的实例对象中获取Store的实例对象,而Store的实例化对象表示了某种邮件接收协议(例如POP3协议)的接收对象。当客户端接收邮件时,只需要通过Store对象调用其接受方法,就能够从指定的邮件服务器(例如前面说到的pop.163.com)中获得邮件数据,后续再将其封装在Message对象中。在查看邮件内容时,我们首先进入Store对象中的【inobx】,也就是收件箱文件夹。而后根据用户选中的位置,读取相应的邮件数据封装成Message对象,最后只需要从Message对象中读取邮件的内容显示到用户界面上即可,包括发送方地址、邮件主题和邮件正文。另外需要注意的是,JTextField和JTextArea都是可以设置成不可编辑状态的,通过setEditable(false)即可。查看邮件内容的代码展示如下:
//Open floder with 'READ_WRITE' right.
Folder folder = store.getFolder("inbox");
folder.open(Folder.READ_WRITE);

//Create MimeMessage object.
int Mail_Index = MailList.getSelectedIndex();
MimeMessage ThisMessage = (MimeMessage)((folder.getMessages())[Mail_Index]);

//Set sender.
String sender = String.valueOf((ThisMessage.getFrom())[0]);
from.setText(sender);

//Set topic.
topic.setText(ThisMessage.getSubject());

//Set text.
String textBody = String.valueOf(ThisMessage.getContent());
body.setText(textBody);

//Clse floder.
folder.close(true);

下图是当前Megatron1949@163.com邮箱中的第1封邮件的内容,后续我们通过InBox来查看这封邮件的内容作为对比:
【Java】JavaMail编程实现邮件客户端-OutBox、InBox

【Java】JavaMail编程实现邮件客户端-OutBox、InBox

下载邮件中的附件.

  • 当我们在查看内容时,如果客户端发现其中有附件,会从中提取出文本消息部分,并且在界面上Body显示部分的末尾附加上一行提示,实际的代码如下:
if(hasAttachment(ThisMessage))
{
	StringBuffer textbody = new StringBuffer();
	//Get text content.
	GetTextBody(ThisMessage,textbody);
	body.setText(textbody.toString()+"\n\nNOTE:This mail has ATTACHMENT.");
}
else
{
	String textBody = String.valueOf(ThisMessage.getContent());
	body.setText(textBody);
}
  • 代码中的GetTextBody()方法是为了获取邮件中Content中的的文本部分,它会对接收到的参数进行递归获取文本部分的操作。如果获取到了文本部分,就添加到StringBuffer的实例中,如果发现是复杂数据体,就一层一层深入获取文本部分。具体的代码如下:
private StringBuffer GetTextBody(Part part,StringBuffer textbody) throws Exception
{
	boolean hasTextAttach = part.getContentType().indexOf("name")>0;
	//text:Append directly.
	if(part.isMimeType("text/*")&&!hasTextAttach)
	{
		textbody.append(part.getContent().toString());
	}
	//message:getContent().
	else if(part.isMimeType("message/rfc822"))
	{
		GetTextBody((Part)part.getContent(),textbody);
	}
	//multipart:get every part.
	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);
			GetTextBody(bodypart, textbody);
		}
	}

	return textbody;	
}
  • 对于一个封装好的Message对象,我们需要判断其中是否含有附件,这是hasAttachment()方法的目的。hasAttachment()方法的完整代码如下所示:
private boolean hasAttachment(Part part)throws Exception
{
	boolean has = 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)))
			{
				has = true;
			}
			else if(bodyPart.isMimeType("multipart/*"))
			{
				has = hasAttachment(bodyPart);
			}
			else
			{
				String contentType = bodyPart.getContentType();
				if(contentType.indexOf("application")!=-1)
				{
					has = true;
				}
				if(contentType.indexOf("name")!=-1)
				{
					has = true;
				}
			}
			if(has)
			{
				break;
			}
		}
	}
	else if(part.isMimeType("message/rfc822"))
	{
		has = hasAttachment((Part)part.getContent());
	}
	return has;
}

这段代码中,我们首先针对那些是MimeMessage类型的邮件,这是第一个if条件表达式要求匹配到multipart/*的结果。进入if分支后说明这一邮件由多个BodyPart组成,我们依次考察其中的每一个BodyPart。后续我们对于每一个BodyPart中的Disposition字段进行判断,该字段的值可以是null、ATTACHMENT或者INLINE.后两个预定义值在Java官方文档中的解释如下:
【Java】JavaMail编程实现邮件客户端-OutBox、InBox
而getDisposition()方法在文档中的描述如下,该方法返回的是这一个part所被呈现出来的方式,ATTACHMENT表示它应该被当作附件呈现出来,而INLINE表示它应该被当作某种文本直接显示出来,而null则代表不知道。
【Java】JavaMail编程实现邮件客户端-OutBox、InBox
所以这部分代码我自己的想法(如果有错,欢迎指出)是,只要有一个BodyPart的Disposition字段指明了它想要的呈现方式,无论是ATTACHMENT还是INLINE,我们都认为这封邮件中含有了附件,所以让has的值为true;后续如果该BodyPart还是一个复杂的multipart,我们就对其递归地调用hasAttachment()方法;而如果该BodyPart既不是multipart,它的Disposition字段也没有指明,我们就获取它的ContentTyep字段(在Http协议消息头中,使用ContentType来表示具体请求中的媒体类型信息),Java文档中对于getContentType()方法的描述如下:
【Java】JavaMail编程实现邮件客户端-OutBox、InBox
当中提到了MIME typing system,关于MIME类型的完整列举,可以参看MIME参考。在这部分代码中,我们使用getContentType()方法获得了表示BodyPart的MIME类型的字符串,而后我们在该字符串中查找"application"和"name"两个字串,如果存在,就认为该消息中含有附件,令has的值为true。回到最外层的if分支,如果消息的类型是"message/rfc822",我们就对该消息的内容调用hasAttackment()方法。

  • 实际下载邮件中的附件并保存到本地。当用户查看邮件内容时,发现邮件正文的末尾有一句客户端的提示"This mail has ATTACHMENT.",此时用户触发下载附件按钮后,就开始进行附件的下载。下载附件的逻辑过程很清晰,我们对于那些确实拥有附件的邮件,执行下载操作;而如果是一封本没有附件的邮件,我们就给出错误信息提示用户这封邮件并没有附件。实际的下载操作中,我们还是需要将复杂体邮件multipart一层一层地剥开,对它的每一个BodyPart进行下载操作,而如果BodyPart还是一个复杂体multipart,我们就对其递归调用下载函数。其中最核心的操作,是我们找到了被封装的附件,将其下载到本地的某个位置,也就是项目中的SaveFile()方法,其代码如下:
private void SaveFile(InputStream is,String savePosition,String fileName) throws Exception
{
	BufferedInputStream bis = new BufferedInputStream(is);
	BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(
		new File(savePosition+fileName)));
	int index=-1;
	while((index=bis.read())!=-1)
	{
		bos.write(index);
		bos.flush();
	}
	bos.close();
	bis.close();
}

SaveFile的策略是一头连接着输入流,也就是待下载的附件,另一头连接着本地的某个文件位置,向其中写入数据。下图是一封带有附件的邮件,我们给出第三方客户端和网易邮箱的实际收件箱中的页面,保证该附件是确实存在的。
【Java】JavaMail编程实现邮件客户端-OutBox、InBox
【Java】JavaMail编程实现邮件客户端-OutBox、InBox
点击下载附件的JButton,再查看预先指定的保存位置,就能看到被下载好的附件。
【Java】JavaMail编程实现邮件客户端-OutBox、InBox
【Java】JavaMail编程实现邮件客户端-OutBox、InBox

删除指定的邮件.

  • 当用户选定了一封邮件,并且触发左侧的删除JButton,就可以删除掉该邮件。删除操作实际上只是给对这封邮件的标识进行了处理,将其修改为"DELETED",这样从store对象中打开的收件箱Floder对象就能够对已经标记为DELETED的邮件进行清理。
private void DeleteMail(int index) throws Exception
{
	Folder folder = store.getFolder(folderName);
	if(folder==null)
	{
		throw new Exception(folderName+" does not exist.");
	}
	folder.open(Folder.READ_WRITE);
	int inquire = JOptionPane.showConfirmDialog(ClientCheckPage.this,
			"Sure to delete Mail-No."+(index+1)+" ?","Delete Mail.",
			JOptionPane.YES_NO_OPTION);
	if(inquire==JOptionPane.YES_OPTION)
	{
		Message DeleteMessage = (folder.getMessages())[index];
		DeleteMessage.setFlag(Flags.Flag.DELETED, true);
		JOptionPane.showMessageDialog(ClientCheckPage.this, "Mail-No."+(index+1)+
			" has been deleted.");
	}
	else
	{
		this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
	}
	folder.close(true);
}

需要注意的是,最后一定要执行folder.close(true),否则表示收件箱的folder对象无法使删除操作生效。现在我们删除掉Megatron邮箱中的第5份邮件,再点击刷新JButton:
【Java】JavaMail编程实现邮件客户端-OutBox、InBox
【Java】JavaMail编程实现邮件客户端-OutBox、InBox
此时登录到网易邮箱的页面查看,发现该邮件确实已经被删除:
【Java】JavaMail编程实现邮件客户端-OutBox、InBox

本文地址:https://blog.csdn.net/weixin_44246009/article/details/107464872

相关标签: Java