- 在上一篇《JavaMail编程实现邮件客户端-总览》中我们已经说完了邮箱客户端的登录界面、主界面,在主界面上点击OutBox按钮就能够进入发件箱,点击InBox按钮就能进入收件箱。这篇文章中,会详细介绍OutBox和InBox的界面设计以及功能的实现。
OutBox发送邮件.
UI.
- 邮件的发送界面,我们都不陌生,一般在这个界面中我们需要填写三个部分:
- 收件人的邮箱地址;
- 邮件的主题;
- 邮件的正文以及附件(如果有的话).
下面是网易邮箱的发送界面,我们也是基于这种常见的邮箱发送界面进行的OutBox界面设计:
而本项目中,发送邮件的OutBox最终GUI效果如下所示:
界面中有三个待填写的文本输入框,分别对应于收件人邮箱地址、邮件主题和邮件正文。左手边的三个按钮,从上至下的功能依次为:发送编辑好的邮件、退出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);
}
}
发送无附件邮件.
- 当我们编辑好一封邮件——填写了收件人地址、主题以及正文部分以后,例如下图中的状态:
接下来只要点击左侧第一个按钮,就开始进行邮件的发送工作了。那么,客户端程序实际上做了哪些事情呢?在本项目中,有附件的邮件和无附件的邮件是被区别对待的,当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());
到这里,客户端已经完成了从配置环境,到封装邮件信息,再到最后的实际发送邮件的任务,接下来只需要在邮件发送成功后,给用户一个发送成功的信息即可。
稍后我们可以登录到实际的收件方邮箱中查看,是否本项目的第三方客户端真的发送了我们编辑的邮件。下图是Esperanto1949@163.com收件箱中实际收到的邮件内容,可以比较客户端上显示的发送时间和实际收到的邮件的发送时间,确认是同一份邮件。
添加附件.
- 现代很多的邮件中都添加了附件进行传输,那么我们在进行邮件封装的时候,就需要考虑如何表示出附件的数据。同样是MimeMessage的实例对象,但与上面代码中
message.setText(MailMessage.getText());
不同的是,这一次我们不仅仅有Text,我们还有附件。我们使用JavaMail中的MimeMultipart来表示一份带有附件的复杂邮件的主体部分,我们依次向其中添加邮件的正文以及附件(如果有多个的话)。
MIME消息的头字段Content-Type有三种类型:multipart/mixed、multipart/related、multipart/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效果如下:
中间部分由于显示邮件的内容,左侧三个按钮分别用于刷新、查看选中邮件的内容以及下载选中邮件的附件,而右侧的一个按钮用于删除选中的邮件。
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开始).
查看无附件邮件的内容.
- 无附件邮件的发送和查看都是最简单的情况,当用户选中了一封邮件后,我们就可以通过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来查看这封邮件的内容作为对比:
下载邮件中的附件.
- 当我们在查看内容时,如果客户端发现其中有附件,会从中提取出文本消息部分,并且在界面上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官方文档中的解释如下:
而getDisposition()方法在文档中的描述如下,该方法返回的是这一个part所被呈现出来的方式,ATTACHMENT表示它应该被当作附件呈现出来,而INLINE表示它应该被当作某种文本直接显示出来,而null则代表不知道。
所以这部分代码我自己的想法(如果有错,欢迎指出)是,只要有一个BodyPart的Disposition字段指明了它想要的呈现方式,无论是ATTACHMENT还是INLINE,我们都认为这封邮件中含有了附件,所以让has的值为true;后续如果该BodyPart还是一个复杂的multipart,我们就对其递归地调用hasAttachment()方法;而如果该BodyPart既不是multipart,它的Disposition字段也没有指明,我们就获取它的ContentTyep字段(在Http协议消息头中,使用ContentType来表示具体请求中的媒体类型信息),Java文档中对于getContentType()方法的描述如下:
当中提到了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的策略是一头连接着输入流,也就是待下载的附件,另一头连接着本地的某个文件位置,向其中写入数据。下图是一封带有附件的邮件,我们给出第三方客户端和网易邮箱的实际收件箱中的页面,保证该附件是确实存在的。
点击下载附件的JButton,再查看预先指定的保存位置,就能看到被下载好的附件。
删除指定的邮件.
- 当用户选定了一封邮件,并且触发左侧的删除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邮件开发详解_基于java的电子邮件收发系统分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/85495.html