JavaMail了解

JavaMail基础了解


发送邮件:SMTP服务器
接受邮件:POP3/IMAP服务器
javamail api:
message实例化代表一封邮件,邮件的核心api
session定义整个邮件发送的环境信息
transport发送邮件的api
store接受邮件的api,从session中获得对象
mimeMultipart: 指定当前邮件发送的方式:纯文本、html、图片、文件.multiPart/mixed的范围最大
multiPart/mixed(附件)>multiPart/related(内嵌资源)>multiPart/alternative(纯文本/超文本):所以可以使用mixed代替所有模式
MimeBodyPart:mimeMultipart包含MimeBodyPart,在MimeBodyPart中设置发送的信息。然后将mimeMultipart放到message中。发送。。。
发送方式:TO:收件人 BCC: 密送 CC:抄送

JavaMail(JAVA邮件服务)API详解
一、JavaMail API简介
JavaMail API是读取、撰写、发送电子信息的可选包。我们可用它来建立如Eudora、Foxmail、MS Outlook Express一般的邮件用户代理程序(Mail User Agent,简称MUA)。而不是像sendmail或者其它的邮件传输代理(Mail Transfer Agent,简称MTA)程序那样可以传送、递送、转发邮件。从另外一个角度来看,我们这些电子邮件用户日常用MUA程序来读写邮件,而MUA依赖着MTA处理邮件的递送。
在清楚了到MUA与MTA之间的关系后,让我们看看JavaMail API是如何提供信息访问功能的吧!JavaMail API被设计用于以不依赖协议的方式去发送和接收电子信息,这个API被分为两大部分:

基本功能:如何以不依赖于协议的方式发送接收电子信息,这也是本文所要描述的,不过在下文中,大家将看到这只是一厢情愿而已。
第二个部分则是依赖特定协议的,比如SMTP、POP、IMAP、NNTP协议。在这部分的JavaMail API是为了和服务器通讯,并不在本文的内容中。

二、相关协议一览
在我们步入JavaMail API之前,先看一下API所涉及的协议。以下便是大家日常所知、所乐于使用的4大信息传输协议:
SMTP
POP
IMAP
MIME
当然,上面的4个协议,并不是全部,还有NNTP和其它一些协议可用于传输信息,但是由于不常用到,所以本文便不提及了。理解这4个基本的协议有助于我们更好的使用JavaMail API。然而JavaMail API是被设计为与协议无关的,目前我们并不能克服这些协议的束缚。确切的说,如果我们使用的功能并不被我们选择的协议支持,那么JavaMail API并不可能如魔术师一样神奇的赋予我们这种能力。

1.SMTP
简单邮件传输协议定义了递送邮件的机制。在下文中,我们将使用基于Java-Mail的程序与公司或者ISP的SMTP服务器进行通讯。这个SMTP服务器将邮件转发到接收者的SMTP服务器,直至最后被接收者通过POP或者IMAP协议获取。这并不需要SMTP服务器使用支持授权的邮件转发,但是却的确要注意SMTP服务器的正确设置(SMTP服务器的设置与JavaMail API无关)。

2.POP
POP是一种邮局协议,目前为第3个版本,即众所周知的POP3。POP定义了一种用户如何获得邮件的机制。它规定了每个用户使用一个单独的邮箱。大多数人在使用POP时所熟悉的功能并非都被支持,例如查看邮箱中的新邮件数量。而这个功能是微软的Outlook内建的,那么就说明微软Outlook之类的邮件客户端软件是通过查询最近收到的邮件来计算新邮件的数量来实现前面所说的功能。因此在我们使用JavaMail API时需要注意,当需要获得如前面所讲的新邮件数量之类的信息时,我们不得不自己进行计算。

3.IMAP
IMAP使用在接收信息的高级协议,目前版本为第4版,所以也被称为IMAP4。需要注意的是在使用IMAP时,邮件服务器必须支持该协议。从这个方面讲,我们并不能完全使用IMAP来替代POP,不能期待IMAP在任何地方都被支持。假如邮件服务器支持IMAP,那么我们的邮件程序将能够具有以下被IMAP所支持的特性:每个用户在服务器上可具有多个目录,这些目录能在多个用户之间共享。
其与POP相比高级之处显而易见,但是在尝试采取IMAP时,我们认识到它并不是十分完美的:由于IMAP需要从其它服务器上接收新信息,将这些信息递送给用户,维护每个用户的多个目录,这都为邮件服务器带来了高负载。并且IMAP与POP的一个不同之处是POP用户在接收邮件时将从邮件服务器上下载邮件,而IMAP允许用户直接访问邮件目录,所以在邮件服务器进行备份作业时,由于每个长期使用此邮件系统的用户所用的邮件目录会占有很大的空间,这将直接导致邮件服务器上磁盘空间暴涨。

4.MIME
MIME并不是用于传送邮件的协议,它作为多用途邮件的扩展定义了邮件内容的格式:信息格式、附件格式等等。一些RFC标准都涉及了MIME:RFC 822, RFC 2045, RFC 2046, RFC 2047,有兴趣的Matrixer可以阅读一下。而作为JavaMail API的开发者,我们并不需关心这些格式定义,但是这些格式被用在了程序中。

5.NNTP和其它的第三方协议
正因为JavaMail API在设计时考虑到与第三方协议实现提供商之间的分离,故我们可以很容易的添加一些第三方协议。SUN维护着一个第三方协议实现提供商的列表:http://java.sun.com/products/javamail/Third_Party.html,通过此列表我们可以找到所需要的而又不被SUN提供支持的第三方协议:比如NNTP这个新闻组协议和S/MIME这个安全的MIME协议。

三、安装
1.安装JavaMail
为了使用JavaMail API,需要从http://java.sun.com/products/javamail/downloads/index.html下载文件名格式为javamail-[version].zip的文件(这个文件中包括了JavaMail实现),并将其中的mail.jar文件添加到CLASSPATH中。这个实现提供了对SMTP、IMAP4、POP3的支持。
注意:在安装JavaMail实现之后,我们将在demo目录中发现许多有趣的简单实例程序。
在安装了JavaMail之后,我们还需要安装JavaBeans Activation Framework,因为这个框架是JavaMail API所需要的。如果我们使用J2EE的话,那么我们并无需单独下载JavaMail,因为它存在于J2EE.jar中,只需将J2EE.jar加入到CLASSPATH即可。

2.安装JavaBeans Activation Framework
http://java.sun.com/products/javabeans/glasgow/jaf.html下载JavaBeans Activation Framework,并将其添加到CLASSPATH中。此框架增加了对任何数据块的分类、以及对它们的处理的特性。这些特性是JavaMail API需要的。虽然听起来这些特性非常模糊,但是它对于我们的JavaMail API来说只是提供了基本的MIME类型支持。
到此为止,我们应当把mail.jar和activation.jar都添加到了CLASSPATH中。
当然如果从方便的角度讲,直接把这两个Jar文件复制到JRE目录的lib/ext目录中也可以。

四、初次认识JavaMail API
1.了解我们的JavaMail环境
A.纵览JavaMail核心类结构
打开JavaMail.jar文件,我们将发现在javax.mail的包下面存在着一些核心类:Session、Message、Address、Authenticator、Transport、Store、Folder。而且在javax.mail.internet包中还有一些常用的子类。
B.Session
Session类定义了基本的邮件会话。就像Http会话那样,我们进行收发邮件的工作都是基于这个会话的。Session对象利用了java.util.Properties对象获得了邮件服务器、用户名、密码信息和整个应用程序都要使用到的共享信息。
Session类的构造方法是私有的,所以我们可以使用Session类提供的getDefaultInstance()这个静态工厂方法获得一个默认的Session对象:


Properties props = new Properties();
// fill props with any information
Session session = Session.getDefaultInstance(props, null);
或者使用getInstance()这个静态工厂方法获得自定义的Session:
Properties props = new Properties();
// fill props with any information
Session session = Session.getInstance(props, null);

从上面的两个例子中不难发现,getDefaultInstance()和getInstance()方法的第二个参数都是null,这是因为在上面的例子中并没有使用到邮件授权,下文中将对授权进行详细介绍。
从很多的实例看,在对mail server进行访问的过程中使用共享的Session是足够的,即使是工作在多个用户邮箱的模式下也不例外。

C.Message
当我们建立了Session对象后,便可以被发送的构造信息体了。在这里SUN提供了Message类型来帮助开发者完成这项工作。由于Message是一个抽象类,大多数情况下,我们使用javax.mail.internet.MimeMessage这个子类,该类是使用MIME类型、MIME信息头的邮箱信息。信息头只能使用US-ASCII字符,而非ASCII字符将通过编码转换为ASCII的方式使用。
为了建立一个MimeMessage对象,我们必须将Session对象作为MimeMessage构造方法的参数传入:
MimeMessage message = new MimeMessage(session);
注意:对于MimeMessage类来讲存在着多种构造方法,比如使用输入流作为参数的构造方法。

在建立了MimeMessage对象后,我们需要设置它的各个part,对于MimeMessage类来说,这些part就是MimePart接口。最基本的设置信息内容的方法就是通过表示信息内容和米么类型的参数调用setContent()方法:
message.setContent("Hello", "text/plain");
然而,如果我们所使用的MimeMessage中信息内容是文本的话,我们便可以直接使用setText()方法来方便的设置文本内容。
message.setText("Hello");
前面所讲的两种方法,对于文本信息,后者更为合适。而对于其它的一些信息类型,比如HTML信息,则要使用前者。
别忘记了,使用setSubject()方法对邮件设置邮件主题:
message.setSubject("First");

D.Address
到这里,我们已经建立了Session和Message,下面将介绍如何使用邮件地址类:Address。像Message一样,Address类也是一个抽象类,所以我们将使用javax.mail.internet.InternetAddress这个子类。
通过传入代表邮件地址的字符串,我们可以建立一个邮件地址类:
Address address = new InternetAddress("president@whitehouse.gov");
如果要在邮件地址后面增加名字的话,可以通过传递两个参数:代表邮件地址和名字的字符串来建立一个具有邮件地址和名字的邮件地址类:
Address address = new InternetAddress("president@whitehouse.gov", "George Bush");
本文在这里所讲的邮件地址类是为了设置邮件信息的发信人和收信人而准备的,在建立了邮件地址类后,我们通过message的setFrom()和setReplyTo()两种方法设置邮件的发信人:
message.setFrom(address);
message.setReplyTo(address);
若在邮件中存在多个发信人地址,我们可用addForm()方法增加发信人:
Address address[] = ...;
message.addFrom(address);
为了设置收信人,我们使用addRecipient()方法增加收信人,此方法需要使用Message.RecipientType的常量来区分收信人的类型:
message.addRecipient(type, address)
下面是Message.RecipientType的三个常量:
Message.RecipientType.TO
Message.RecipientType.CC
Message.RecipientType.BCC
因此,如果我们要发送邮件给总统,并发用一个副本给第一夫人的话,下面的方法将被用到:
Address toAddress = new InternetAddress(vice.president@whitehouse.gov);
Address ccAddress = new InternetAddress(first.lady@whitehouse.gov);
message.addRecipient(Message.RecipientType.TO, toAddress);
message.addRecipient(Message.RecipientType.CC, ccAddress);

JavaMail API并没有提供检查邮件地址有效性的机制。当然我们可以自己完成这个功能:验证邮件地址的字符是否按照RFC822规定的格式书写或者通过DNS服务器上的MX记录验证等。

E.Authenticator
像java.net类那样,JavaMail API通过使用授权者类(Authenticator)以用户名、密码的方式访问那些受到保护的资源,在这里“资源”就是指邮件服务器。在javax.mail包中可以找到这个JavaMail的授权者类(Authenticator)。
在使用Authenticator这个抽象类时,我们必须采用继承该抽象类的方式,并且该继承类必须具有返回PasswordAuthentication对象(用于存储认证时要用到的用户名、密码)getPasswordAuthentication()方法。并且要在Session中进行注册,使Session能够了解在认证时该使用哪个类。
下面代码片断中的MyAuthenticator就是一个Authenticator的子类。
Properties props = new Properties();
// fill props with any information
Authenticator auth = new MyAuthenticator();

Session session = Session.getDefaultInstance(props, auth);

F.Transport
在发送信息时,Transport类将被用到。这个类实现了发送信息的协议(通称为SMTP),此类是一个抽象类,我们可以使用这个类的静态方法send()来发送消息:
Transport.send(message);
当然,方法是多样的。我们也可由Session获得相应协议对应的Transport实例。并通过传递用户名、密码、邮件服务器主机名等参数建立与邮件服务器的连接,并使用sendMessage()方法将信息发送,最后关闭连接:
message.saveChanges();
// implicit with send()
Transport transport = session.getTransport("smtp");
transport.connect(host, username, password);
transport.sendMessage(message, message.getAllRecipients());
transport.close();
评论:上面的方法是一个很好的方法,尤其是在我们在同一个邮件服务器上发送多个邮件时。因为这时我们将在连接邮件服务器后连续发送邮件,然后再关闭掉连接。send()这个基本的方法是在每次调用时进行与邮件服务器的连接的,对于在同一个邮件服务器上发送多个邮件来讲可谓低效的方式。
注意:如果需要在发送邮件过程中监控mail命令的话,可以在发送前设置debug标志:
session.setDebug(true)。

G.Store和Folder
接收邮件和发送邮件很类似都要用到Session。但是在获得Session后,我们需要从Session中获取特定类型的Store,然后连接到Store,这里的Store代表了存储邮件的邮件服务器。在连接Store的过程中,极有可能需要用到用户名、密码或者Authenticator。
// Store store = session.getStore("imap");
Store store = session.getStore("pop3");
store.connect(host, username, password);
在连接到Store后,一个Folder对象即目录对象将通过Store的getFolder()方法被返回,我们可从这个Folder中读取邮件信息:
Folder folder = store.getFolder("INBOX");
folder.open(Folder.READ_ONLY);
Message message[] = folder.getMessages();
上面的例子首先从Store中获得INBOX这个Folder(对于POP3协议只有一个名为INBOX的Folder有效),然后以只读(Folder.READ_ONLY)的方式打开Folder,最后调用Folder的getMessages()方法得到目录中所有Message的数组。

注意:对于POP3协议只有一个名为INBOX的Folder有效,而对于IMAP协议,我们可以访问多个Folder(想想前面讲的IMAP协议)。而且SUN在设计Folder的getMessages()方法时采取了很智能的方式:首先接收新邮件列表,然后再需要的时候(比如读取邮件内容)才从邮件服务器读取邮件内容。
在读取邮件时,我们可以用Message类的getContent()方法接收邮件或是writeTo()方法将邮件保存,getContent()方法只接收邮件内容(不包含邮件头),而writeTo()方法将包括邮件头。
System.out.println(((MimeMessage)message).getContent());
在读取邮件内容后,别忘记了关闭Folder和Store。
folder.close(aBoolean);
store.close();
传递给Folder.close()方法的boolean 类型参数表示是否在删除操作邮件后更新Folder。

H.继续向前进!
在讲解了以上的七个Java Mail核心类定义和理解了简单的代码片断后,下文将详细讲解怎样使用这些类实现JavaMail API所要完成的高级功能。

五、使用JavaMail API
在明确了JavaMail API的核心部分如何工作后,本人将带领大家学习一些使用Java Mail API任务案例。
1.发送邮件
在获得了Session后,建立并填入邮件信息,然后发送它到邮件服务器。这便是使用Java Mail API发送邮件的过程,在发送邮件之前,我们需要设置SMTP服务器:通过设置Properties的mail.smtp.host属性。
String host = ...;
String from = ...;
String to = ...;
// Get system properties
Properties props = System.getProperties();
// Setup mail server
props.put("mail.smtp.host", host);
// Get session
Session session = Session.getDefaultInstance(props, null);
// Define message
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
message.setSubject("Hello JavaMail");
message.setText("Welcome to JavaMail");
// Send message
Transport.send(message);
由于建立邮件信息和发送邮件的过程中可能会抛出异常,所以我们需要将上面的代码放入到try-catch结构块中。

2.接收邮件
为了在读取邮件,我们获得了session,并且连接到了邮箱的相应store,打开相应的Folder,然后得到我们想要的邮件,当然别忘记了在结束时关闭连接。
String host = ...;
String username = ...;
String password = ...;
// Create empty properties
Properties props = new Properties();
// Get session
Session session = Session.getDefaultInstance(props, null);
// Get the store
Store store = session.getStore("pop3");
store.connect(host, username, password);
// Get folder
Folder folder = store.getFolder("INBOX");
folder.open(Folder.READ_ONLY);
// Get directory
Message message[] = folder.getMessages();
for (int i=0, n=message.length; i<n; i++) {  
System.out.println(i+":"+ message[i].getFrom()[0]+" "+message[i].getSubject());
}
// Close connection
folder.close(false);
store.close();
上面的代码所作的是从邮箱中读取每个邮件,并且显示邮件的发信人地址和主题。从技术角度讲,这里存在着一个异常的可能:当发信人地址为空时,getFrom()[0]将抛出异常。

下面的代码片断有效的说明了如何读取邮件内容,在显示每个邮件发信人和主题后,将出现用户提示从而得到用户是否读取该邮件的确认,如果输入YES的话,我们可用Message.writeTo(java.io.OutputStream os)方法将邮件内容输出到控制台上,关于Message.writeTo()的具体用法请看JavaMail API。
BufferedReader reader = new BufferedReader (  new InputStreamReader(System.in));
// Get directory
Message message[] = folder.getMessages();
for (int i=0, n=message.length; i<n; i++) { 
System.out.println(i+": "+message[i].getFrom()[0]+ " " + message[i].getSubject()); System.out.println("Do you want to read message? "+"[YES to read/QUIT to end]"); 
String line = reader.readLine(); 
if ("YES".equals(line)) {   
message[i].writeTo(System.out); 
} else if ("QUIT".equals(line)) {   
break; 
}
}

3.删除邮件和标志

设置与message相关的Flags是删除邮件的常用方法。这些Flags表示了一些系统定义和用户定义的不同状态。在Flags类的内部类Flag中预定义了一些标志:
Flags.Flag.ANSWERED
Flags.Flag.DELETED
Flags.Flag.DRAFT
Flags.Flag.FLAGGED
Flags.Flag.RECENT
Flags.Flag.SEEN
Flags.Flag.USER
但需要在使用时注意的:标志存在并非意味着这个标志被所有的邮件服务器所支持。例如,对于删除邮件的操作,POP协议不支持上面的任何一个。所以要确定哪些标志是被支持的??通过访问一个已经打开的Folder对象的getPermanetFlags()方法,它将返回当前被支持的Flags类对象。
删除邮件时,我们可以设置邮件的DELETED标志:
message.setFlag(Flags.Flag.DELETED, true);
但是首先要采用READ_WRITE的方式打开Folder:
folder.open(Folder.READ_WRITE);
在对邮件进行删除操作后关闭Folder时,需要传递一个true作为对删除邮件的擦除确认。
folder.close(true);
Folder类中另一种用于删除邮件的方法expunge()也同样可删除邮件,但是它并不为sun提供的POP3实现支持,而其它第三方提供的POP3实现支持或者并不支持这种方法。
另外,介绍一种检查某个标志是否被设置的方法:Message.isSet(Flags.Flag flag)方法,其中参数为被检查的标志。

4.邮件认证
我们在前面已经学会了如何使用Authenticator类来代替直接使用用户名和密码这两字符串作为 Session.getDefaultInstance()或者Session.getInstance()方法的参数。在前面的小试牛刀后,现在我们将了解到全面认识一下邮件认证。
我们在此取代了直接使用邮件服务器主机名、用户名、密码这三个字符串作为连接到POP3 Store的方式,使用存储了邮件服务器主机名信息的属性文件,并在获得Session时传入自定义的Authenticator实例:
// Setup properties
Properties props = System.getProperties();
props.put("mail.pop3.host", host);
// Setup authentication, get session
Authenticator auth = new PopupAuthenticator();
Session session = Session.getDefaultInstance(props, auth);
// Get the store
Store store = session.getStore("pop3");
store.connect();
PopupAuthenticator 类继承了抽象类Authenticator,并且通过重载Authenticator类的getPasswordAuthentication()方法返回PasswordAuthentication类对象。而getPasswordAuthentication()方法的参数param是以逗号分割的用户名、密码组成的字符串。
import javax.mail.*;
import java.util.*;
public class PopupAuthenticator extends Authenticator { 
public PasswordAuthentication getPasswordAuthentication(String param) {   
String username, password;   
StringTokenizer st = new StringTokenizer(param, ",");   
username = st.nextToken();   
password = st.nextToken();   
return new PasswordAuthentication(username, password); 
}
}

5.回复邮件回复邮件的方法很简单:使用Message类的reply()方法,通过配置回复邮件的收件人地址和主题(如果没有提供主题的话,系统将默认将“Re:”作为邮件的主体),这里不需要设置任何的邮件内容,只要复制发信人或者reply-to到新的收件人。而reply()方法中的boolean参数表示是否将邮件回复给发送者(参数值为false),或是恢复给所有人(参数值为true)。
补充一下,reply-to地址需要在发信时使用setReplyTo()方法设置。
MimeMessage reply = (MimeMessage)message.reply(false);
reply.setFrom(new InternetAddress("president@whitehouse.gov"));
reply.setText("Thanks");
Transport.send(reply);

6.转发邮件

转发邮件的过程不如前面的回复邮件那样简单,它将建立一个转发邮件,这并非一个方法就能做到。
每个邮件是由多个部分组成,每个部分称为一个邮件体部分,是一个BodyPart类对象,对于MIME类型邮件来讲就是MimeBodyPart类对象。这些邮件体包含在成为Multipart的容器中对于MIME类型邮件来讲就是MimeMultiPart类对象。在转发邮件时,我们建立一个文字邮件体部分和一个被转发的文字邮件体部分,然后将这两个邮件体放到一个Multipart中。说明一下,复制一个邮件内容到另一个邮件的方法是仅复制它的 DataHandler(数据处理者)即可。这是由JavaBeans Activation Framework定义的一个类,它提供了对邮件内容的操作命令的访问、管理了邮件内容操作,是不同的数据源和数据格式之间的一致性接口。
// Create the message to forward
Message forward = new MimeMessage(session);
// Fill in header
forward.setSubject("Fwd: " + message.getSubject());
forward.setFrom(new InternetAddress(from));
forward.addRecipient(Message.RecipientType.TO,   new InternetAddress(to));
// Create your new message part
BodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setText(  "Here you go with the original message:/n/n");
// Create a multi-part to combine the parts
Multipart multipart = new MimeMultipart();
multipart.addBodyPart(messageBodyPart);
// Create and fill part for the forwarded content
messageBodyPart = new MimeBodyPart();
messageBodyPart.setDataHandler(message.getDataHandler());
// Add part to multi part
multipart.addBodyPart(messageBodyPart);
// Associate multi-part with message
forward.setContent(multipart);
// Send message
Transport.send(forward);

7.使用附件

附件作为与邮件相关的资源经常以文本、表格、图片等格式出现,如流行的邮件客户端一样,我们可以用JavaMail API从邮件中获取附件或是发送带有附件的邮件。

A.发送带有附件的邮件
发送带有附件的邮件的过程有些类似转发邮件,我们需要建立一个完整邮件的各个邮件体部分,在第一个部分(即我们的邮件内容文字)后,增加一个具有DataHandler的附件而不是在转发邮件时那样复制第一个部分的DataHandler。

如果我们将文件作为附件发送,那么要建立FileDataSource类型的对象作为附件数据源;如果从URL读取数据作为附件发送,那么将要建立URLDataSource类型的对象作为附件数据源。

然后将这个数据源(FileDataSource或是URLDataSource)对象作为DataHandler类构造方法的参数传入,从而建立一个DataHandler对象作为数据源的DataHandler。

接着将这个DataHandler设置为邮件体部分的DataHandler。这样就完成了邮件体与附件之间的关联工作,下面的工作就是BodyPart的setFileName()方法设置附件名为原文件名。

最后将两个邮件体放入到Multipart中,设置邮件内容为这个容器Multipart,发送邮件。
// Define message
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
message.setSubject("Hello JavaMail Attachment");
// Create the message part
BodyPart messageBodyPart = new MimeBodyPart();
// Fill the message
messageBodyPart.setText("Pardon Ideas");
Multipart multipart = new MimeMultipart();
multipart.addBodyPart(messageBodyPart);
// Part two is attachment
messageBodyPart = new MimeBodyPart();
DataSource source = new FileDataSource(filename);
messageBodyPart.setDataHandler(new DataHandler(source));
messageBodyPart.setFileName(filename);
multipart.addBodyPart(messageBodyPart);
// Put parts in message
message.setContent(multipart);
// Send the message
Transport.send(message);
如果我们使用servlet实现发送带有附件的邮件,则必须上传附件给servlet,这时需要注意提交页面form中对编码类型的设置应为multipart/form-data。
<FORM ENCTYPE="multipart/form-data" method=post action="/myservlet">  
<INPUT TYPE="file" NAME="thefile"> 
<INPUT TYPE="submit" VALUE="Upload">
</FORM>

    
B.读取邮件中的附件
读取邮件中的附件的过程要比发送它的过程复杂一点。因为带有附件的邮件是多部分组成的,我们必须处理每一个部分获得邮件的内容和附件。
但是如何辨别邮件信息内容和附件呢?Sun在Part类(BodyPart类实现的接口类)中提供了getDisposition()方法让开发者获得邮件体部分的部署类型,当该部分是附件时,其返回之将是Part.ATTACHMENT。但附件也可以没有部署类型的方式存在或者部署类型为 Part.INLINE,无论部署类型为Part.ATTACHMENT还是Part.INLINE,我们都能把该邮件体部分导出保存。
Multipart mp = (Multipart)message.getContent();for (int i=0, n=multipart.getCount(); i
下列代码中使用了saveFile方法是自定义的方法,它根据附件的文件名建立一个文件,如果本地磁盘上存在名为附件的文件,那么将在文件名后增加数字表示区别。然后从邮件体中读取数据写入到本地文件中(代码省略)。
// from saveFile()
File file = new File(filename);
for (int i=0; file.exists(); i++) { 
file = new File(filename+i);
}
以上是邮件体部分被正确设置的简单例子,如果邮件体部分的部署类型为null,那么我们通过获得邮件体部分的MIME类型来判断其类型作相应的处理,代码结构框架如下:
if (disposition == null) { 
// Check if plain 
MimeBodyPart mbp = (MimeBodyPart)part; 
if (mbp.isMimeType("text/plain")) {   
// Handle plain 
} else {   
// Special non-attachment cases here of    
// image/gif, text/html,
... 
}
...
}

 

8.处理HTML邮件
前面的例子中发送的邮件都是以文本为内容的(除了附件),下面将介绍如何接收和发送基于HTML的邮件。
A.发送HTML邮件
假如我们需要发送一个HTML文件作为邮件内容,并使邮件客户端在读取邮件时获取相关的图片或者文字的话,只要设置邮件内容为html代码,并设置内容类型为text/html即可:
String htmlText = "Hello" +   "<ccid_file values="logo"></ccid_file>";
message.setContent(htmlText, "text/html");
请注意:这里的图片并不是在邮件中内嵌的,而是在URL中定义的。邮件接收者只有在线时才能看到。
在接收邮件时,如果我们使用JavaMail API接收邮件的话是无法实现以HTML方式显示邮件内容的。因为JavaMail API邮件内容视为二进制流。所以要显示HTML内容的邮件,我们必须使用JEditorPane或者第三方HTML展现组件。

以下代码显示了如何使用JEditorPane显示邮件内容:
if (message.getContentType().equals("text/html")) {  
  String content = (String)message.getContent();  
  JFrame frame = new JFrame();  
  JEditorPane text = new JEditorPane("text/html", content);  
  text.setEditable(false);  
  JScrollPane pane = new JScrollPane(text);  
  frame.getContentPane().add(pane);  
  frame.setSize(300, 300);  
  frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);  
  frame.show();
}

B.在邮件中包含图片
如果我们在邮件中使用HTML作为内容,那么最好将HTML中使用的图片作为邮件的一部分,这样无论是否在线都会正确的显示HTML中的图片。处理方法就是将HTML中用到的图片作为邮件附件并使用特殊的cid URL作为图片的引用,这个cid就是对图片附件的Content-ID头的引用。
处理内嵌图片就像向邮件中添加附件一样,不同之处在于我们必须通过设置图片附件所在的邮件体部分的header中Content-ID为一个随机字符串,并在HTML中img的src标记中设置为该字符串。这样就完成了图片附件与HTML的关联。
String file = ...;
// Create the message
Message message = new MimeMessage(session);
// Fill its headers
message.setSubject("Embedded Image");
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
// Create your new message part
BodyPart messageBodyPart = new MimeBodyPart();
String htmlText = "<H1>Hello</H1>" + "<img src="cid:memememe">";
messageBodyPart.setContent(htmlText, "text/html");
// Create a related multi-part to combine the parts
MimeMultipart multipart = new MimeMultipart("related");
multipart.addBodyPart(messageBodyPart);
// Create part for the image
messageBodyPart = new MimeBodyPart();
// Fetch the image and associate to part
DataSource fds = new FileDataSource(file);
messageBodyPart.setDataHandler(new DataHandler(fds));
messageBodyPart.setHeader("Content-ID","");
// Add part to multipart
multipart.addBodyPart(messageBodyPart);
// Associate multi-part with message
message.setContent(multipart);
9.在邮件中搜索短语

JavaMail API提供了过滤器机制,它被用来建立搜索短语。这个短语由javax.mail.search包中的SearchTerm抽象类来定义,在定义后我们便可以使用Folder的Search()方法在Folder中查找邮件:
SearchTerm st = ...;Message[] msgs = folder.search(st);
下面有22个不同的类(继承了SearchTerm类)供我们使用:
AND terms (class AndTerm)
OR terms (class OrTerm)
NOT terms (class NotTerm)
SENT DATE terms (class SentDateTerm)
CONTENT terms (class BodyTerm)
HEADER terms (FromTerm / FromStringTerm, RecipientTerm / RecipientStringTerm, SubjectTerm, etc.)
使用这些类定义的断语集合,我们可以构造一个逻辑表达式,并在Folder中进行搜索。下面是一个实例:在Folder中搜索邮件主题含有“ADV”字符串或者发信人地址为friend@public.com的邮件。
SearchTerm st=new OrTerm(new SubjectTerm("ADV:"), new FromStringTerm (friend@public.com));
Message[] msgs = folder.search(st);


六、参考资源
JavaMail API Home
Sun’s JavaMail API基础
JavaBeans Activation Framework Home
javamail-interest mailing list
Sun's JavaMail FAQ
jGuru's JavaMail FAQ
Third Party Products List
七、代码下载
http://java.sun.com/developer/onlineTraining/JavaMail/exercises.html

 

详细的介绍一下这三个类的主要方法。

Session


       Session用于收集JavaMail运行过程中的环境信息,它可以创建一个单例的对象,也可以每次创建新的对象,Session没有构造器,只能通过如下方法创造实例:
static SessiongetDefaultInstance(Properties props)
          Get the default Session object.
static SessiongetDefaultInstance(Properties props,Authenticator authenticator)
          Get the default Session object.
static SessiongetInstance(Properties props)
          Get a new Session object.
static SessiongetInstance(Properties props,Authenticator authenticator)
          Get a new Session object.

       getDefaultInstance得到的始终是该方法初次创建的缺省的对象,而getInstance得到的始终是新的对象,Authenticator的使用后面会说到。通过Session可以创建Transport(用于发送邮件)和Store(用于接收邮件),Transport和Store是JavaMail API中定义好的接口,通过上文我们知道JavaMail分为API和service provider两部分,API定义了相关接口(eg.:Transport and Store),service provider中实现了这些接口,这些实现类配置在名为javamail.providersjavamail.default.providers的文件中,该文件放在mail.jar/smtp.jar/pop3.jar/imap.jar中的META-INF下,文件内容格式如:

 
# JavaMail IMAP provider Sun Microsystems, Inc
protocol=imap; type=store; class=com.sun.mail.imap.IMAPStore; vendor=Sun Microsystems, Inc;
protocol=imaps; type=store; class=com.sun.mail.imap.IMAPSSLStore; vendor=Sun Microsystems, Inc;
# JavaMail SMTP provider Sun Microsystems, Inc
protocol=smtp; type=transport; class=com.sun.mail.smtp.SMTPTransport; vendor=Sun Microsystems, Inc;
protocol=smtps; type=transport; class=com.sun.mail.smtp.SMTPSSLTransport; vendor=Sun Microsystems, Inc;
# JavaMail POP3 provider Sun Microsystems, Inc
protocol=pop3; type=store; class=com.sun.mail.pop3.POP3Store; vendor=Sun Microsystems, Inc;
protocol=pop3s; type=store; class=com.sun.mail.pop3.POP3SSLStore; vendor=Sun Microsystems, Inc;

       每一行声明了协议名称、类型、实现类、供应商、版本等信息,如果需要自己实现相应的协议,必须按照该格式配置好,Java Mail API中才能正确的调用到。Session中提供的创建Trasnsport和Store的方法如下:

 StoregetStore()
          Get a Store object that implements this user's desired Store protocol.
 StoregetStore(Provider provider)
          Get an instance of the store specified by Provider.
 StoregetStore(String protocol)
          Get a Store object that implements the specified protocol.
 StoregetStore(URLName url)
          Get a Store object for the given URLName.
 TransportgetTransport()
          Get a Transport object that implements this user's desired Transport protcol.
 TransportgetTransport(Address address)
          Get a Transport object that can transport a Message of the specified address type.
 TransportgetTransport(Provider provider)
          Get an instance of the transport specified in the Provider.
 TransportgetTransport(String protocol)
          Get a Transport object that implements the specified protocol.
 TransportgetTransport(URLName url)
          Get a Transport object for the given URLName.
       可以看到,重构了很多,这些方法最终都会去解析上文提到的配置文件,找到对应配置信息。


Message


       Message是邮件的载体,用于封装邮件的所有信息,Message是一个抽象类,已知的实现类有MimeMessage。一封完整的邮件都有哪些信息呢?我们打开一个邮件客户端,我用的是FoxMail,新建一封邮件,如下图所示:
       这就是一封完整的邮件包含的所有信息,默认情况下是没有暗送和回复设置的,可以通过菜单栏-->查看-->暗送地址/回复地址来显示出来,回复地址默认情况下为发件人,暗送是比较猥琐的发邮件方式,暗送邮件除了被暗送者,没有人能知道暗送给谁了,邮件头信息中也不会记录。下面来看下Message中设置邮件信息的一些方法。
 

发件人

abstract  voidsetFrom()
          Set the "From" attribute in this Message.
abstract  voidsetFrom(Address address)
          Set the "From" attribute in this Message.
       现在大多数SMTP服务器要求发件人与连接账户必须一致,否则会抛出验证失败的异常,这样是防止用户伪装成其它人的账户恶意发送邮件。
 

收件人/抄送人/暗送人

voidsetRecipient(Message.RecipientType type,Address address)
          Set the recipient address.
abstract  voidsetRecipients(Message.RecipientType type,Address[] addresses)
          Set the recipient addresses.
       第一个方法设置单个人,第二个方法设置多个人。方法中第一个参数涉及到另一个类RecipientType,该类是Message的静态内部类,期内有三个常量值:
static Message.RecipientTypeBCC
          The "Bcc" (blind carbon copy) recipients.
static Message.RecipientTypeCC
          The "Cc" (carbon copy) recipients.
static Message.RecipientTypeTO
          The "To" (primary) recipients.
       TO - 收件人;CC - 抄送人;BCC - 暗送人。
 
 
回复人
 voidsetReplyTo(Address[] addresses)
          Set the addresses to which replies should be directed.
       设置收件人收到邮件后的回复地址。
 
 

标题

abstract  voidsetSubject(String subject)
          Set the subject of this message.
       设置邮件标题/主题。
 
 

内容

 voidsetContent(Multipart mp)
          This method sets the given Multipart object as this message's content.
 voidsetContent(Object obj,String type)
          A convenience method for setting this part's content.
 voidsetText(String text)
          A convenience method that sets the given String as this part's content with a MIME type of "text/plain".
       设置邮件文本内容、HTML内容、附件内容。

示例


       下面来看一个稍复杂点的示例:

public class JavaMailTest2 {

 public static void main(String[] args) throws MessagingException {
  Properties props = new Properties();
  // 开启debug调试
  props.setProperty("mail.debug", "true");
  // 发送服务器需要身份验证
  props.setProperty("mail.smtp.auth", "true");
  // 设置邮件服务器主机名
  props.setProperty("mail.host", "smtp.163.com");
  // 发送邮件协议名称
  props.setProperty("mail.transport.protocol", "smtp");
  
  // 设置环境信息
  Session session = Session.getInstance(props, new Authenticator() {
   // 在session中设置账户信息,Transport发送邮件时会使用
   protected PasswordAuthentication getPasswordAuthentication() {
    return new PasswordAuthentication("java_mail_001", "javamail");
   }
  });
  
  // 创建邮件对象
  Message msg = new MimeMessage(session);
  // 发件人
  msg.setFrom(new InternetAddress("java_mail_001@163.com"));
  // 多个收件人
  msg.setRecipients(RecipientType.TO, InternetAddress.parse("java_mail_002@163.com,java_mail_003@163.com"));
  // 抄送人
  msg.setRecipient(RecipientType.CC, new InternetAddress("java_mail_001@163.com"));
  // 暗送人
  msg.setRecipient(RecipientType.BCC, new InternetAddress("java_mail_004@163.com"));
  
  // 主题
  msg.setSubject("中文主题");
  // HTML内容
  msg.setContent("<div align=\"center\">你好啊</div>", "text/html;charset=utf-8");
  
  // 连接邮件服务器、发送邮件、关闭连接,全干了
  Transport.send(msg);
 }

}

JavaMail使用

下面我用一个最简单的例子还演示第一条消息的发送.

1.获取系统Properties.

Properties props = System.getProperties();

2.将您的SMTP服务器名添加到mail.smtp.host关键字的属性中.

Props.pout( “ mail.smtp.host ” ,host);

3.获取基于Properties Session对象.

Session session = Session.getDefaultInstance(props,null);

4.从Session创建一个MimeMessage.

MimeMessage message = new MimeMessage(session);

5.设置消息from域.

Message.setForm(new InternetAddress(from));

6.设置to域.

Message.addRecipient(Message.RecipientType.TO,new InternetAddress(to));

7.设置消息主题.

message.setSubject( “ HelloJavaMail ” );

8.设置消息内容.

Message.setText( “ Welcome to JavaMail ” );

9.发送消息.

Transport.send(message);

10.关闭连接.

Transport.close();


通过简单的接触了JavaMail相信大家多邮件发送也有了简单的了解和认识,下面我主要研究一下它的具体功能,也就是说具体的接口或类的含义.

Session类定义了一个基本的邮件会话,所有的其他类都是由这个session才得意生效的,Session对象用java.util.Properties对象获取信息,如邮件服务器,用户名,密码及整个应用程序中共享的其他信息.类的构造器是此有的,private.它能用getDefaultInstance()方法来共享.获取Session对象的方方法如下:

Properties props = new Properties();

Session session = Session.getDefaultInstance(props,null);

Null参数都是Authenticator对象,在这里没有使用.

对于大多数情况,共享的session已经足够用了.

Message消息类,在获得了Session对象后,就可以继续创建要发送的消息.因为Message是个抽象类,您必须用一个子类,多数情况下为java.mail.internet.MimeMessage.这个能理解成MIME类型和头的电子邮件消息.正如不同的RFC中定义的,虽然在某些头部域非ASCII字符也能被编译,但是Message头只能被限制用US-ASCII字符.要创建一个Message请将Session对象传递给MimeMessage的构造器.

MimeMessage message = newMimeMessage(session);

一旦获得消息,就可以设置各个部分了.最基本的就是setContent()方法,例如:

message.setContent( “ Hello ” , ” text/plain ” );

如果知道在实用MimeMessage,而且消息是纯文本格式,就可以用setText()方法,它只需要代表实际内容的参数.(Mime类型缺省为text/plain)

用setSubject()方法设置subject(主题);

message.setSubject( “ 主题 ” );

Address地址类,和Message一样也是一个抽象类,一旦创建了Session和Message并将内容填入消息后,就可以用Address确定信件的地址了,用javax.mail.internet.

InternetAddress类.若创建的地址只包含电子邮件地址,只要传递电子邮件地址给构造器就可以了.例如:Address address = new InternetAddress( “ it5719@163.com ” );

若希望名字挨着电子邮件现实,就可以把它传递给构造器,如下:

Address address = new InternetAddress( “ it5719@163.com ” , ” 我心依旧 ” );

需要为消息的from域和to域创建地址对象,除非邮件服务器阻止,没有什么能阻止你发送一段看上去是任何人的消息了呵呵.一旦创建address将他们域消息连接方法有两种,如要要识别发件人的就可以用setFrom()和setReplyTo方法.然后message.setFrom(address);

需要实用多个from地址的就用addFrom()方法.例子如下:

Address[] address = ,.,. ;    message.addFrom(address);

若要识别消息recipient收件人,就要实用addRecipient()方法了.例如:

message.addRecipient(type,address)

Authenticator与java.net类一样,JavaMailAPI也可以利用Authentcator通过用户名密码访问受保护的资源.对于JavaMail来说,这些资源就是邮件服务器,Authentcator类在javax.mail包中.要使用Authenticator,首先创建一个抽象的子类,并从

GetPasswordAuthentication方法中返回passwordAuthentication实例,创建完成后,您必须向session注册Authenticator,然后在需要认证的时候会通知它,其实说白了就是把配置的用户名和密码返回给调用它的程序.例如:

Properties props = new properties();

Authenticator auth = new MailAuthenticator()//接口声明,创建自己新类的实例.

Session session = Session.getDefauItInstance(props,auth);

Transport消息发送传输类,这个类用协议指定的语言发送消息,通常是SMTP,它是抽象类,它的工作方式与Session有些类似,尽调用静态方法send()方法,就OK了.例如:

Transport.send(message);

或者也可以从针对协议的会话中获取一个特定的实例,传递用户名和密码.发送消息,然后关闭连接,例如:

message.saveChanges();

transport transport = session.getTreansport( “ smtp ” );//指定的协议

transport.connect(host,username,password);

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

transport.close();

如果要观察传到邮件服务器上的邮件命令,请用session.setDubug(true)设置调试标志.

Store和folder用session获取消息,与发送消息开始很相似,但是在session得到后,很可能实用用户名和密码或实用Authenticator连接到一个Store.类似于Transport,也是一样要告诉store用什么协议.例如

Store store = session.getStore( “ pop3 ” );

Store.connect(host,username,password);

连接到Store之后,接下来,获得一个folder,必须打开它就可以读取里边的消息了.

Folder folder = store.getFolder("INBOX");

folder.open(Folder.READ_ONLY);

Message[] message = folder.getMessages();

POP3唯一可用的文件夹就是INBOX,如果实用IMAP,还可以用其他的文件夹.

当读到了具体的message以后,就可以用getContent来获取内容,或者用writeTo()将内容写入流,getContent()方法只能得到消息内容,而writeTo()的输出却包含消息头.

System.out.println(((MimeMessage)message).getConntent());

一旦读取完毕邮件,要关闭store和folder的连接.

folder.colse(boolean);

store.colse();

传递给folder的close()方法的boolean参数表示是否清楚已删除的消息从而更新folder.

上面就是JavaMail邮件操作的基本的常用类,我觉得理解了这几个类的机制,基本就可以处理一般的邮件操作了.
下面是JavaMail实现邮件发送的代码样例1.

import javax.mail.*;
public class MailAuthenticator extends Authenticator
{
    //******************************
    //由于发送邮件的地方比较多,
    //下面统一定义用户名,口令.
    //******************************
    public static String HUAWEI_MAIL_USER = "it5719@163.com";
    public static String HUAWEI_MAIL_PASSWORD = "密码";
    public MailAuthenticator()
    {
    }
    protected PasswordAuthentication getPasswordAuthentication()
    {
        return new PasswordAuthentication(HUAWEI_MAIL_USER, HUAWEI_MAIL_PASSWORD);
    }
}
这个类是发送邮件的类.
package com.deepdo.common.mail;
/**
 * 此处插入类型说明。
 * 创建日期:(2006-4-21 14:57:16)
 * @author:张宏亮
 */
import java.util.*;
import java.io.*;
import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;
public class SendMail {
    //要发送Mail地址
    private String mailTo = null;
    //Mail发送的起始地址
    private String mailFrom = null;
    //SMTP主机地址
    private String smtpHost = null;
    //是否采用调试方式
    private boolean debug = false;
    private String messageBasePath = null;
    //Mail主题
    private String subject;
    //Mail内容
    private String msgContent;
    private Vector attachedFileList;
    private String mailAccount = null;
    private String mailPass = null;
    private String messageContentMimeType ="text/html; charset=gb2312";
    private String mailbccTo = null;
    private String mailccTo = null;
    /**
     * SendMailService 默认构造函数。
     */
    public SendMail() {
        super();
    }
    private void fillMail(Session session,MimeMessage msg) throws IOException, MessagingException{
        String fileName = null;
        Multipart mPart = new MimeMultipart();
        if (mailFrom != null) {
            msg.setFrom(new InternetAddress(mailFrom));
            System.out.println("发送人Mail地址:"+mailFrom);
        } else {
            System.out.println("没有指定发送人邮件地址!");
            return;
        }
        if (mailTo != null) {
            InternetAddress[] address = InternetAddress.parse(mailTo);
            msg.setRecipients(Message.RecipientType.TO, address);
            System.out.println("收件人Mail地址:"+mailTo);
        } else {
            System.out.println("没有指定收件人邮件地址!");
            return;
        }
        if (mailccTo != null) {
            InternetAddress[] ccaddress = InternetAddress.parse(mailccTo);
            System.out.println("CCMail地址:"+mailccTo);
            msg.setRecipients(Message.RecipientType.CC, ccaddress);
        }
        if (mailbccTo != null) {
            InternetAddress[] bccaddress = InternetAddress.parse(mailbccTo);
            System.out.println("BCCMail地址:"+mailbccTo);
            msg.setRecipients(Message.RecipientType.BCC, bccaddress);
        }
        msg.setSubject(subject);
        InternetAddress[] replyAddress = { new InternetAddress(mailFrom)};
        msg.setReplyTo(replyAddress);
        // create and fill the first message part
        MimeBodyPart mBodyContent = new MimeBodyPart();
        if (msgContent != null)
            mBodyContent.setContent(msgContent, messageContentMimeType);
        else
            mBodyContent.setContent("", messageContentMimeType);
        mPart.addBodyPart(mBodyContent);
        // attach the file to the message
        if (attachedFileList != null) {
            for (Enumeration fileList = attachedFileList.elements(); fileList.hasMoreElements();) {
                    fileName = (String) fileList.nextElement();
                    MimeBodyPart mBodyPart = new MimeBodyPart();
                    // attach the file to the message
                    FileDataSource fds = new FileDataSource(messageBasePath + fileName);
                    System.out.println("Mail发送的附件为:"+messageBasePath + fileName);
                    mBodyPart.setDataHandler(new DataHandler(fds));
                    mBodyPart.setFileName(fileName);
                    mPart.addBodyPart(mBodyPart);
            }
        }
        msg.setContent(mPart);
        msg.setSentDate(new Date());
    }
    /**
     * 此处插入方法说明。
     */
    public void init()
    {
    }
    /**
     * 发送e_mail,返回类型为int
     * 当返回值为0时,说明邮件发送成功
     * 当返回值为3时,说明邮件发送失败
     */
    public int sendMail() throws IOException, MessagingException {
        int loopCount;
        Properties props = System.getProperties();
        props.put("mail.smtp.host", smtpHost);
        props.put("mail.smtp.auth", "true");
        MailAuthenticator auth = new MailAuthenticator();
        Session session = Session.getInstance(props, auth);
        session.setDebug(debug);
        MimeMessage msg = new MimeMessage(session);
        Transport trans = null;
        try {
            fillMail(session,msg);
            // send the message
            trans = session.getTransport("smtp");
            try {
                   trans.connect(smtpHost, MailAuthenticator.HUAWEI_MAIL_USER, MailAuthenticator.HUAWEI_MAIL_PASSWORD);
            } catch (AuthenticationFailedException e) {
                   e.printStackTrace();
                   System.out.println("连接邮件服务器错误:");
                   return 3;
            } catch (MessagingException e) {
                   System.out.println("连接邮件服务器错误:");
                   return 3;
            }
            trans.send(msg);
            trans.close();
        } catch (MessagingException mex) {
            System.out.println("发送邮件失败:");
            mex.printStackTrace();
            Exception ex = null;
            if ((ex = mex.getNextException()) != null) {
                System.out.println(ex.toString());
                ex.printStackTrace();
            }
            return 3;
        } finally {
            try {
                 if (trans != null && trans.isConnected())
                         trans.close();
            } catch (Exception e) {
                System.out.println(e.toString());
            }
        }
        System.out.println("发送邮件成功!");
        return 0;
    }
    public void setAttachedFileList(java.util.Vector filelist)
    {
        attachedFileList = filelist;
    }
    public void setDebug(boolean debugFlag)
    {
        debug=debugFlag;
    }
    public void setMailAccount(String strAccount) {
        mailAccount = strAccount;
    }
    public void setMailbccTo(String bccto) {
        mailbccTo = bccto;
    }
    public void setMailccTo(String ccto) {
        mailccTo = ccto;
    }
    public void setMailFrom(String from)
    {
        mailFrom=from;
    }
    public void setMailPass(String strMailPass) {
        mailPass = strMailPass;
    }
    public void setMailTo(String to)
    {
        mailTo=to;
    }
    public void setMessageBasePath(String basePath)
    {
        messageBasePath=basePath;
    }
    public void setMessageContentMimeType(String mimeType)
    {
        messageContentMimeType = mimeType;
    }
    public void setMsgContent(String content)
    {
        msgContent=content;
    }
    public void setSMTPHost(String host)
    {
        smtpHost=host;
    }
    public void setSubject(String sub)
    {
        subject=sub;
    }
    public static void main(String[] argv) throws Exception
    {
        for(int i = 0;i<10;i++) {
            SendMail sm = new SendMail();
            sm.setSMTPHost("SMTP地址");
            sm.setMailFrom("发送地址");
            sm.setMailTo("目标地址");
            sm.setMsgContent("内容");
            sm.setSubject("标题");
            sm.sendMail();
        }
    }
}

下面是JavaMail实现邮件发送的代码样例2
  样例代码如下:
 UserAuthentication.java
package cn.com.javaweb.mail;

import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;

public class UserAuthentication extends Authenticator {
    private String userName;
    private String password;
    public void setPassword(String password){
        this.password = password;
    }

    public void setUserName(String userName){
        this.userName = userName;
    }

    public PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(userName, password);
    }
}

SendMailTest.java
package cn.com.javaweb.mail;

import java.io.IOException;
import javax.mail.internet.MimeMultipart;
import javax.mail.Multipart;
import java.io.FileReader;
import java.util.Properties;
import java.io.FileInputStream;
import javax.mail.Session;
import javax.mail.Authenticator;
import javax.mail.internet.MimeMessage;
import javax.mail.PasswordAuthentication;
import javax.mail.Message;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import java.util.Vector;
import java.util.Enumeration;
import javax.activation.FileDataSource;
import javax.activation.DataHandler;
import javax.mail.Transport;
import javax.mail.*;

public class SendMailTest {

  public SendMailTest() {
  }

  public static void main(String[] args) throws Exception {
      String smtpServer = "192.168.1.245";
      String userName = "user2";
      String password = "yyaccp";
      String fromAddress = "user1@mailserver";
      String toAddress = "user2@mailserver";

      String subject = "邮件标题";
      String content = "邮件内容";
      String attachFile1 = "d:/temp/test.txt";
      String attachFile2 = "d:/temp/test2.txt";

      SendMailTest sendMail = new SendMailTest();
      Session session = sendMail.getSession(smtpServer, userName, password);
      if(session != null){
          String[] files = {attachFile1, attachFile2};
          //Message msg = sendMail.getMessage(session, subject, content, files);
          Message msg = sendMail.getMessage(session, subject, content);

          if(msg != null){
          //发送源地址
        msg.setFrom(new InternetAddress(fromAddress));

        //发送目的地址
        InternetAddress[] tos = InternetAddress.parse(toAddress);
        msg.setRecipients(Message.RecipientType.TO, tos);
//抄送目的地址
//      InternetAddress[] toscc = InternetAddress.parse(ccAddr);
//      msg.setRecipients(Message.RecipientType.CC, toscc);
//
//密送目的地址
//      InternetAddress[] tosbcc = InternetAddress.parse(bccAddr);
//      msg.setRecipients(Message.RecipientType.BCC, tosbcc);

        //发送邮件
        boolean bool = sendMail.sendMail(msg);
        if(bool){
            System.out.println("发送成功");
        }else{
            System.out.println("发送失败");
        }
          }
      }
  }

  public Session getSession(String smtpServer, String userName, String password){
      // 192.168.1.245
      // user2
      // yyaccp
      Session session = null;
      try{
          Properties props = new Properties();
          props.put("mail.smtp.host", smtpServer); //例如:202.108.44.206 smtp.163.com
          props.put("mail.smtp.auth", "true"); //认证是否设置

          UserAuthentication authen = new UserAuthentication();
          authen.setPassword(password);
          authen.setUserName(userName);

          session = Session.getDefaultInstance(props, authen);
      }catch(Exception e){
          e.printStackTrace();
      }
      return session;
  }

  public Message getMessage(Session session, String subject, String text){
      Message msg = null;
      try{
          msg = new MimeMessage(session);
          msg.setText(text);
          msg.setSubject(subject);

      }catch(Exception e){
          e.printStackTrace();
      }
      return msg;
  }

  public boolean sendMail(Message msg){
      boolean bool = false;
      try {
          Transport.send(msg);
          bool = true;
      } catch (MessagingException ex) {
          ex.printStackTrace();
      }
      return bool;
  }

  public Message getMessage(Session session, String subject, String text, String[] archives){
      //  d:/temp/saa.txt
      Message msg = null;
      try{
          Multipart contentPart = new MimeMultipart();
          // 生成Message对象
          msg = new MimeMessage(session);
          // 设置邮件内容
          msg.setContent(contentPart);
          // 设置邮件标题
          msg.setSubject(subject);

          // 组织邮件内容,包括邮件的文本内容和附件
          // 1 邮件文本内容
          MimeBodyPart textPart = new MimeBodyPart();
          textPart.setText(text);
          // 将文本部分,添加到邮件内容
          contentPart.addBodyPart(textPart);

          // 2 附件
          if(archives != null){
              for(int i=0; i<archives.length; i++){
                  MimeBodyPart archivePart = new MimeBodyPart();
                  //选择出每一个附件文件名
                  String filename = archives[i];
                  //得到数据源
                  FileDataSource fds = new FileDataSource(filename);
                  //得到附件本身并至入BodyPart
                  archivePart.setDataHandler(new DataHandler(fds));
                  //得到文件名同样至入BodyPart
                  archivePart.setFileName(fds.getName());
                  // 将附件添加到附件集
                  contentPart.addBodyPart(archivePart);
              }
          }
      }catch(Exception e){
          e.printStackTrace();
      }
      return msg;
  }

  /**
   * 获取文本文件内容
   * @param path String
   * @throws IOException
   * @return String
   */
  public String getFile(String path) throws IOException {
    //读取文件内容
    char[] chrBuffer = new char[10];//缓冲十个字符
    int intLength;
    String s = "";//文件内容字符串
    FileReader fis = new FileReader(path);
    while ( (intLength = fis.read(chrBuffer)) != -1) {
      String temp = String.valueOf(chrBuffer);//转换字符串
      s = s + temp;//累加字符串
    }
    return s;
  }
}

   ReceiveMailTest.java
package cn.com.javaweb.mail;

import javax.mail.Message;
import javax.mail.Folder;
import javax.mail.Store;
import javax.mail.FetchProfile;
import javax.mail.BodyPart;
import javax.mail.Multipart;
import javax.mail.Part;
import java.io.InputStream;
import java.io.FileOutputStream;
import java.io.File;
import java.util.Properties;
import javax.mail.Session;

public class ReceiveMailTest {
    private Folder inbox;
    private Store store;

    //连接邮件服务器,获得所有邮件的列表
    public Message[] getMail(String host, String name, String password) throws
            Exception {
        Properties prop = new Properties();
        prop.put("mail.pop3.host", host);
        Session session = Session.getDefaultInstance(prop);

        store = session.getStore("pop3");
        store.connect(host, name, password);

        inbox = store.getDefaultFolder().getFolder("INBOX");
        inbox.open(Folder.READ_ONLY);

        Message[] msg = inbox.getMessages();

        FetchProfile profile = new FetchProfile();
        profile.add(FetchProfile.Item.ENVELOPE);
        profile.add(FetchProfile.Item.FLAGS);
        profile.add("X-Mailer");
        inbox.fetch(msg, profile);

        return msg;
    }

    //处理任何一种邮件都需要的方法
    private void handle(Message msg) throws Exception {
        System.out.println("邮件主题:" + msg.getSubject());
        System.out.println("邮件作者:" + msg.getFrom()[0].toString());
        System.out.println("发送日期:" + msg.getSentDate());

    }

    //处理文本邮件
    public void handleText(Message msg) throws Exception {
        this.handle(msg);
        System.out.println("邮件内容:" + msg.getContent());
    }

    //处理Multipart邮件,包括了保存附件的功能
    public void handleMultipart(Message msg) throws Exception {
        String disposition;
        BodyPart part;
        // 1、从Message中取到Multipart
        // 2、遍历Multipart里面得所有bodypart
        // 3、判断BodyPart是否是附件,
        //    如果是,就保存附件
        //    否则就取里面得文本内容
        Multipart mp = (Multipart) msg.getContent();
        int mpCount = mp.getCount(); //Miltipart的数量,用于除了多个part,比如多个附件
        for (int m = 0; m < mpCount; m++) {
            this.handle(msg);

            part = mp.getBodyPart(m);
            disposition = part.getDisposition();
            if (disposition != null && disposition.equals(Part.ATTACHMENT)) { //判断是否有附件
                this.saveAttach(part);//这个方法负责保存附件,注释掉是因为附件可能有病毒,请清理信箱之后再取掉注释
            } else {
                System.out.println(part.getContent());
            }
        }
    }

    private void saveAttach(BodyPart part) throws Exception {
        String temp = part.getFileName(); //得到未经处理的附件名字
        System.out.println("*********temp=" + temp);
        System.out.println("**********" + base64Decoder(temp));
        String s = temp;//temp.substring(11, temp.indexOf("?=") - 1); //去到header和footer

        //文件名一般都经过了base64编码,下面是解码
        String fileName = s;//this.base64Decoder(s);
        System.out.println("有附件:" + fileName);

        InputStream in = part.getInputStream();
        FileOutputStream writer = new FileOutputStream(new File("d:/temp/"+fileName));
        byte[] content = new byte[255];
        int read = 0;
        while ((read = in.read(content)) != -1) {
            writer.write(content);
        }
        writer.close();
        in.close();
    }

    //base64解码
    private String base64Decoder(String s) throws Exception {
        sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
        byte[] b = decoder.decodeBuffer(s);
        //sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
        //String str = encoder.encode(bytes);

        return (new String(b));
    }

    //关闭连接
    public void close() throws Exception {
        if (inbox != null) {
            inbox.close(false);
        }

        if (store != null) {
            store.close();
        }
    }

    public static void main(String args[]) {
        String host = "192.168.1.245";
        String name = "user2";
        String password = "yyaccp";

        ReceiveMailTest receiver = new ReceiveMailTest();

        try {
            Message[] msg = receiver.getMail(host, name, password);

            for (int i = 0; i < msg.length; i++) {
                if (msg[i].isMimeType("text/*")) { //判断邮件类型
                    receiver.handleText(msg[i]);
                } else {
                    receiver.handleMultipart(msg[i]);
                }
                System.out.println("****************************");
            }
            receiver.close();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(e);
        }
    }
}

 

发送HTML格式邮件

一、开发思想:
1、发送HTML的公共主程序可以参考java的发送html邮件的程序;
2、在邮件中显示出html的样式效果是根据EBS中HTML报表的代码样式转换而来


二、实现程序:
1、主程序SendHtmlMail.java中的host、user、pwd、from这里是写死了,可以提取出来当参数传入更具有通用型
package cux.oracle.apps.pos.Util;

import java.io.FileOutputStream;
import java.io.UnsupportedEncodingException;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.util.Date;
import java.util.Properties;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import oracle.apps.fnd.cp.request.JavaConcurrentProgram;

/**
* 创建 HTML 格式的邮件
*
* @author Jason Gu
*/
public class SendHtmlMail
{

  public String sendMessage(String host, String user, String pwd, String from,
                            String to, String subject,
                            String body) throws MessagingException,
                                                java.io.UnsupportedEncodingException
  {
    Properties props = new Properties();

    // 设置发送邮件的邮件服务器的属性
    props.put("mail.smtp.host", host);

    // 需要经过授权,也就是用户名和密码的校验,这样才能通过验证(一定要有这一条)
    props.put("mail.smtp.auth", "true");

    // 创建该邮件应用程序所需的环境信息以及会话信息
    Session session = Session.getDefaultInstance(props);

    // 有了这句便可以在发送邮件的过程中在console处显示过程信息,供调试使
    // 用(你可以在控制台(console)上看到发送邮件的过程)
    session.setDebug(true);

    // 根据上面的 Session 实例创建 MimeMessage 实例,即一封邮件
    MimeMessage msg = new MimeMessage(session);

    try
    {
      // 设置发件人地址
      msg.setFrom(new InternetAddress(from));

      // 设置收件人地址
      msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));

      // 设置 E-mail 主题
      msg.setSubject(subject);

      // 设置发送时间
      msg.setSentDate(new Date());

      // 设置 E-mail 正文部分
      // msg.setText(body);
      msg.setContent(body, "text/html;charset = UTF-8");

      // 保存对该 MimeMessage 实例的更改
      msg.saveChanges();

      // 发送邮件
      Transport transport = session.getTransport("smtp");

      // 连接服务器的邮箱
      transport.connect(host, user, pwd);

      // 把邮件发送出去
      transport.sendMessage(msg, msg.getAllRecipients());
      transport.close();
      // 将 msg 对象中内容写入文件
      msg.writeTo(new FileOutputStream("SendHtmlMail.eml"));
      return "S";
    } catch (Exception e)
    {
      e.printStackTrace();
      return "E";
    }
  }

  public static String main(String to,String subject,String body) throws MessagingException,
                                              UnsupportedEncodingException
  {
    String host = "172.17.27.249"; // smtp服务器
    String user = "Ebs-admin"; // 用户名
    String pwd = "[CONTRACT]2013(approval)"; // 密码
    String from = "Ebs-admin@jd.com";

    SendHtmlMail sendmail = new SendHtmlMail();
    String result =
      sendmail.sendMessage(host, user, pwd, from, to, subject, body);
    return result;
  }
}

2、邮件内容和收件人的程序CuxPtebsReport.java:
package cux.oracle.apps.pos.Util;

import java.sql.PreparedStatement;

import java.util.Date;

import java.text.SimpleDateFormat;

import oracle.apps.fnd.common.VersionInfo;
import oracle.apps.fnd.cp.request.CpContext;
import oracle.apps.fnd.cp.request.JavaConcurrentProgram;
import oracle.apps.fnd.cp.request.LogFile;
import oracle.apps.fnd.cp.request.ReqCompletion;
import oracle.apps.fnd.framework.OAException;
import oracle.apps.fnd.util.ParameterList;

import java.sql.Connection;
import java.sql.ResultSet;

import oracle.apps.fnd.util.NameValueType;

import cux.oracle.apps.pos.Util.SendHtmlMail;

/**
*  请求调用生成 HTML 格式的邮件
*
* @author Jason Gu
*/
public class CuxPtebsReport implements JavaConcurrentProgram
{
  public static final String RCS_ID =
    "$Header: CuxRepayReport.java 120.1 2013/09/06 14:36:06 sabatra noship $";
  public static final boolean RCS_ID_RECORDED =
    VersionInfo.recordClassVersion("$Header: CuxRepayReport.java 120.1 2013/09/06 14:36:06 sabatra noship $",
                                   "%packagename%");
  protected LogFile log;
  protected ReqCompletion reqc;
  private static SimpleDateFormat mDateFormat;
  private String l_return_status = "S";

  public void runProgram(CpContext cpContext)
  {
    ParameterList lPara = cpContext.getParameterList();
    StringBuffer com_content = new StringBuffer();
    StringBuffer dept_content = new StringBuffer();
    String sendresult = "S";
    String cux_combody = new String();
    String cux_deptbody = new String();
    String body = new String();
    try
    {
      this.log = cpContext.getLogFile();
      this.reqc = cpContext.getReqCompletion();

      Connection con = cpContext.getJDBCConnection();

      this.log.writeln("input parameters list:", 1);
      while (lPara.hasMoreElements())
      {
        NameValueType nvt = lPara.nextParameter();
        this.log.writeln(nvt.getName() + ":" + nvt.getValue(), 1);

      }
      //获取当前日期
      java.util.Calendar c = java.util.Calendar.getInstance();
      java.text.SimpleDateFormat f =
        new java.text.SimpleDateFormat("yyyy年MM月dd日");
      //邮件主题
      String subject = f.format(c.getTime()) + "新增公司和部门";
      //表格标题
      String com = f.format(c.getTime()) + "数据新增公司";
      String dept = f.format(c.getTime()) + "数据新增部门";
      String comheader =
        "<table border=1 cellspacing=0 cellpadding=2><tr><td colspan=4 align=center>" +
        com +
        "</td></tr><tr><td nowrap>公司编码</td><td nowrap>公司名称</td><td nowrap>生效日期</td><td>最后更新日期</td></tr>";
      String combody =
        "<tr align=left><td nowrap>VAR_A</td><td nowrap>VAR_B</td><td nowrap>VAR_C</td><td nowrap>VAR_D</td></tr>";
      String deptheader =
        "<table border=1 cellspacing=0 cellpadding=2><tr><td colspan=4 align=center>" +
        dept +
        "</td></tr><tr><td nowrap>部门编码</td><td nowrap>部门名称</td><td nowrap>生效日期</td><td>最后更新日期</td></tr>";
      String deptbody =
        "<tr align=left><td nowrap>VAR_E</td><td nowrap>VAR_F</td><td nowrap>VAR_G</td><td nowrap>VAR_H</td></tr>";
      String foot = "</table><br><br>";

      PreparedStatement compre = null;
      PreparedStatement deptpre = null;
      PreparedStatement updatecompre = null;
      PreparedStatement updatedeptpre = null;
      PreparedStatement mailtopre = null;
      ResultSet comresult = null;
      ResultSet deptresult = null;
      ResultSet mailtoresult = null;
      ResultSet updatecomresult = null;
      ResultSet updatedeptresult = null;
      this.log.writeln("start datebase connection...", 1);

      //新增公司数据
      String comsql =
        "SELECT company_code, company_name, to_char(start_date, 'yyyy-mm-dd') start_date, to_char(last_update_date,'yyyy-mm-dd') last_update_date \n" +
        "FROM cux_test_com_data WHERE send_flag = 'N' \n";

      compre = con.prepareStatement(comsql);
      comresult = compre.executeQuery();
      while (comresult.next())
      {
        this.log.writeln("com_code:" + comresult.getString("company_code"), 1);
        cux_combody = combody;
        cux_combody =
            cux_combody.replace("VAR_A", comresult.getString("company_code"));
        cux_combody =
            cux_combody.replace("VAR_B", comresult.getString("company_name"));
        String com_start_date = comresult.getString("start_date");
        if (com_start_date == null)
        {
          com_start_date = " ";
        }
        String date = com_start_date;
        cux_combody = cux_combody.replace("VAR_C", date);
        cux_combody =
            cux_combody.replace("VAR_D", comresult.getString("last_update_date"));
        com_content.append(cux_combody);
      }

      //部门新增数据
      String deptsql =
        "SELECT dept_code, dept_name, to_char(start_date,'yyyy-mm-dd') start_date,to_char(last_update_date,'yyyy-mm-dd') last_update_date \n" +
        "  FROM cux_test_dept_data where send_flag = 'N'";
      deptpre = con.prepareStatement(deptsql);
      deptresult = deptpre.executeQuery();
      //deptresult.
      while (deptresult.next())
      {
        cux_deptbody = deptbody;
        cux_deptbody =
            cux_deptbody.replace("VAR_E", deptresult.getString("dept_code"));
        cux_deptbody =
            cux_deptbody.replace("VAR_F", deptresult.getString("dept_name"));
        String dept_start_date = deptresult.getString("start_date");
        if (dept_start_date == null)
        {
          dept_start_date = " ";
        }
        String start_date = dept_start_date;
        cux_deptbody = cux_deptbody.replace("VAR_G", start_date);
        cux_deptbody =
            cux_deptbody.replace("VAR_H", deptresult.getString("last_update_date"));
        dept_content.append(cux_deptbody);
      }

      int resp_id = cpContext.getRespId();
      this.log.writeln("resp_id:" + resp_id, 2);
      //获取职责id
      String addrsql =
        "SELECT distinct pf.email_address\n" + "        FROM fnd_user_resp_groups_direct c, fnd_responsibility r, fnd_user fu, per_all_people_f pf\n" +
        "       WHERE c.responsibility_id = r.responsibility_id\n" +
        "         AND c.user_id = fu.user_id\n" +
        "         AND pf.email_address is not null\n" +
        "         AND fu.employee_id = pf.person_id\n" +
        "         AND c.responsibility_id = " + resp_id;
      //this.log.writeln("addrsql:" + addrsql, 2);
      mailtopre = con.prepareStatement(addrsql);
      mailtoresult = mailtopre.executeQuery();
      while (mailtoresult.next())
      {
        String addr = mailtoresult.getString("email_address");
        this.log.writeln("addr:" + addr, 1);
        if (addr != null && addr != "")
        {
          if (!"".equals(com_content.toString()) ||
              !"".equals(dept_content.toString()))
          {
            this.log.writeln("com not null or dept not null in", 4);
            //邮件内容
            if (com_content.toString().equals("") &&
                !"".equals(dept_content.toString()))
            {
              this.log.writeln("com null,dept not null", 4);
              body = deptheader + dept_content.toString() + foot;
            } else if (!"".equals(com_content.toString()) &&
                       dept_content.toString().equals(""))
            {
              this.log.writeln("com not null,dept null", 4);
              body = comheader + com_content.toString() + foot;
            } else if (!"".equals(com_content.toString()) &&
                       !"".equals(dept_content.toString()))
            {
              this.log.writeln("com not null,dept not null", 4);
              body =
                  comheader + com_content.toString() + foot + deptheader + dept_content.toString() +
                  foot;
            }
            //发送邮件
            SendHtmlMail sendmail = new SendHtmlMail();
            sendresult = sendmail.main(addr, subject, body);
          } else
          {
            this.log.writeln("没有需要发送的内容", 4);
          }
        } else
        {
          write("resp_id:" + resp_id + "未维护邮箱");
        }
      }

      if (!"S".equals(sendresult))
      {
        this.l_return_status = "E";
      }

      if ("S".equals(this.l_return_status))
      {
        this.reqc.setCompletion(0, "program completed Successfully.");
        String update_com_sql =
          "UPDATE cux_test_com_data c\n" + "   SET c.send_flag = 'Y', c.last_update_date = SYSDATE, c.last_updated_by = fnd_global.user_id\n" +
          " WHERE c.send_flag = 'N'";
        updatecompre = con.prepareStatement(update_com_sql);
        updatecomresult = updatecompre.executeQuery();
        String update_dept_sql =
          "UPDATE cux_test_dept_data d\n" + "   SET d.send_flag = 'Y', d.last_update_date = SYSDATE, d.last_updated_by = fnd_global.user_id\n" +
          " WHERE d.send_flag = 'N'";
        updatedeptpre = con.prepareStatement(update_dept_sql);
        updatedeptresult = updatedeptpre.executeQuery();
      } else
      {
        this.reqc.setCompletion(2,
                                "program completed with error. Please see request log for details.");
      }
    }

    catch (OAException localOAException)
    {
      localOAException.printStackTrace();
      this.reqc.setCompletion(2, localOAException.getMessage());
    } catch (Exception localException)
    {
      localException.printStackTrace();
      this.reqc.setCompletion(2, localException.toString());
    }
  }

  protected void write(String paramString)
  {
    this.log.writeln(getCurrDateStr() + paramString, 0);
  }

  private String getCurrDateStr()
  {
    if (mDateFormat == null)
    {
      mDateFormat = new SimpleDateFormat("MM/dd/yyyy hh:mm:ss:SSS");
    }

    return "[" + mDateFormat.format(new Date()) + "] ";
  }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值