信息来源: 软件人才网 作者:林立超
2002年我写了一篇关于java实现邮件发送的文章,那时我写的邮件发送功能很简单,不能带附件,不能带模版。后来有很多朋友陆续问我,有没有更完善一点的版本,现有我编写的邮件发送程序设计思想是可以方便的发送附件,可以发送html页面包括内置图片,样式,并且新增了邮件中html元素背景图片功能(这个功能是outlook没有的),还有邮件连接池架构等等,下面我就把目前我使用很完善,功能也较强的版本核心源码公布如下,希望能让更多的技术人员掌握java邮件发送底层技术,有什么问题可以在软件人才网论坛发帖。
注:Base64为一种编码方式,主要将二进制数据编码为文本ascii编码,利于文本传输。邮件体可以有多种编码方法,base64是常用的一种。
package com.sunstudio.pool.mail;
import java.io.*;
import java.net.*;
import java.util.*;
import sun.misc.*;
import com.sigmaproject.config.*;
import com.sigmaproject.util.*;
import com.sigmaproject.stream.*;
import com.sunstudio.pool.*;
public class MailConnection extends PoolConnection{
public static String CRLF=System.getProperty("line.separator");
public static String CRLFTAB=CRLF+"/t";
public static String[] TPL_START_FLAG=new String[]{
" background=/"",
" src=/"",
" background='",
" src='"
};
public static String[] TPL_END_FLAG=new String[]{
"/"",
"/"",
"'",
"'"
};
public static String TPL_INNER_NAME="f";
public transient static final int LOW_PRIORITY = 5;
public transient static final int NORMAL_PRIORITY = 3;
public transient static final int HIGH_PRIORITY = 1;
public static BASE64Encoder encoder=new BASE64Encoder();
Socket socket=null;
PureOutputStream outData=null;
PureInputStream inData=null;
boolean connected=false;
boolean destoryed=false;
boolean use_template=false;
String smtpServer=null;
String smtpPort=null;
String fromUser=null;
String pass=null;
String fromName=null;
boolean isNeedAuthLogin=false;
boolean returnReceipt=false;
String subject="";
String textContent="";
String htmlContent="";
Hashtable tos=new Hashtable();
Vector attach0=new Vector();
Vector attach1=new Vector();
int priority=3;
public MailConnection(ConnectionPool pool1,String serverAddr,String serverPort,String user_email,String password){
super(pool1,CONN_TYPE_MAIL);
smtpServer=serverAddr;
smtpPort=serverPort;
user_email=(user_email==null||user_email.equals("")?"unknown":user_email);
int s1=user_email.indexOf("<");
if(s1==-1){
fromUser=user_email;
fromName=user_email;
}else{
fromUser=user_email.substring(s1+1,user_email.indexOf(">"));
fromName=user_email.substring(0,s1);
}
pass=password;
}
/**使用模板发送邮件
*
* 注意:书写模板内容时以及邮件体内容时要注意一定的格式,凡是邮件使用内置文件时,需要特殊标注以便于组件进行替换和处理;
* 使用邮件模板发送时可以另新增其他附件,但不用再指定邮件内容,即最好不要再调用setHtmlContent方法,否则innerattach将失效
*/
public void useTemplate(String tpl,String source_path){
attach0.removeAllElements();
attach1.removeAllElements();
tpl=tpl.toLowerCase();//mail中不能使用javascript基本,故可以做纯html来操作
Vector s1=new Vector(),d1=new Vector();
int a=-1,b=-1,spos=0,x=0;
String fa=null,fb=null,s=null,e=null,f=null;
File ftmp=null;
for(int i=0;i<TPL_START_FLAG.length;i++,spos=0){
s=TPL_START_FLAG[i];
e=TPL_END_FLAG[i];
while(true){
if((a=tpl.indexOf(s,spos))<0)break;//未找到起始标志
if((b=tpl.indexOf(e,a+s.length()))<0)break;//未找到结束标志
fa=tpl.substring(a+s.length(),b);
spos=b+e.length();
if(s1.contains((f=s+fa+e)))continue;
if(!(ftmp=new File(source_path,fa)).exists())continue;
fb=TPL_INNER_NAME+x++;
s1.addElement(f);
d1.addElement(s+"cid:"+fb+e);
addInnerAttach(new Attachment(ftmp,fb));
ftmp=null;
}
}
setHtmlContent(StringEx.replace(tpl,s1,d1));
use_template=true;
}
public void setReturnReceipt(boolean s){returnReceipt=s;}
public void setPriority(int s){priority=s;}
public void setTextContent(String s){if(s==null)return;textContent=s;}
public void setHtmlContent(String s){if(s==null)return;htmlContent=s;}
public void setSubject(String s){subject=(s==null||s.equals("")?" ":s);}
public boolean addMailTo(String toName1,String toEmail){return tos.put(toEmail,toName1)!=null;}
public boolean addMailTo(String user_email){
int s1=user_email.indexOf("<");
String name1="";
String email1="";
if(s1<0){
name1=user_email;
email1=user_email;
}else{
email1=user_email.substring(s1+1,user_email.indexOf(">"));
name1=user_email.substring(0,s1);
}
return tos.put(email1,name1)!=null;
}
public void addInnerAttach(Attachment att){attach0.addElement(att);}
public void addAttach(Attachment att){attach1.addElement(att);}
public boolean connect(){
try{
fromName=(fromName==null?"":fromName);
isNeedAuthLogin=(fromUser!=null&&pass!=null&&!fromUser.equals("")&&!pass.equals(""));
socket=new Socket(smtpServer,Integer.parseInt(smtpPort));
outData=new PureOutputStream(new BufferedOutputStream(socket.getOutputStream()));
inData=new PureInputStream(socket.getInputStream());
readResponse("220");
sendRequestResponse("250","HELO "+smtpServer+CRLF);
if(isNeedAuthLogin){
sendRequestResponse("334","AUTH LOGIN"+CRLF);//AUTH LOGIN
sendRequestResponse("334",new String(encoder.encode(fromUser.getBytes()))+CRLF);//USERNAME:
sendRequestResponse("235",new String(encoder.encode(pass.getBytes()))+CRLF);//PASSWORD:
}
connected=true;
}catch(Exception e){
e.printStackTrace();
connected=false;
destory();
}
//System.out.println("邮件服务器连接"+connected);
return connected;
}
public boolean send(){
try{
//注:163邮箱不允许邮件人名称和邮件人地址相同
sendRequestResponse("250","RSET"+CRLF);
sendRequestResponse("250","MAIL FROM: "+"<"+fromUser+">"+CRLF);//命令不需要带邮件人名称,切记!!!!!
for(Enumeration enu=tos.keys();enu.hasMoreElements();){
Object to1=enu.nextElement();
if(to1==null)continue;
String touser=(String)to1;
String toname=(String)tos.get(to1);
sendRequestResponse("250,550","RCPT TO: "+"<"+touser+">"+CRLF);//命令不需要带邮件人名称,切记!!!!!
}
sendRequestResponse("354","DATA"+CRLF);
if(!sendBody())throw new Exception("发送邮件内容时出错!");
sendRequestResponse("250",CRLF+"."+CRLF);
return true;
}catch(Exception e){
e.printStackTrace();
destory();
}
return false;
}
public boolean isAvailable(){
while(!connected){
connect();
try{Thread.currentThread().sleep(100);}catch(Exception e){}
}
return socket!=null
&&connected
&&!destoryed
&&!socket.isClosed()
&&!socket.isOutputShutdown()
&&!socket.isInputShutdown();
}
public void close(){
try{
returnReceipt=false;
subject="";
textContent="";
htmlContent="";
tos.clear();
attach0.clear();
attach1.clear();
sendRequestResponse("250","RSET"+CRLF);
}catch(Exception e){
destory();
}
}
public void destory(){
try{
sendRequestResponse("221","QUIT"+CRLF);//QUIT退出
}catch(Exception e){}
try{
closeSocket();
}catch(Exception e){}
destoryed=true;
}
public boolean isDestory(){
return destoryed ;
}
private void closeSocket(){
try{if(inData!=null){inData.close();inData=null;}}catch(Exception ex){}
try{if(outData!=null){outData.close();outData=null;}}catch(Exception ex){}
try{if(socket!=null){socket.close();socket=null;}}catch(Exception ex){}
}
private String getGBKEncodeStr(String x){
return "=?gb2312?B?"+Base64.encodeString(x==null?"":x)+"?=";
}
private void readResponse(String cmd)throws Exception{
String tmp=inData.readLine();
String code=tmp.substring(0,3);
//System.out.println("S:"+tmp);
if((","+cmd+",").indexOf(code)>-1);
else throw new Exception("##########邮件发送失败!##########"+tmp+"("+cmd+")");
while(tmp.startsWith(cmd+"-"))tmp=inData.readLine();
}
private void sendRequest(String msg)throws Exception{
//System.out.println("C:"+msg);
outData.print(msg);
outData.flush();
}
private void sendRequestResponse(String cmd,String msg)throws Exception{
sendRequest(msg);
readResponse(cmd);
}
private boolean sendBody(){
String boundary0="----=hawa_multipart_mixed_"+MD5.encode(String.valueOf(Math.random()+1));
String boundary1="----=hawa_multipart_related_"+MD5.encode(String.valueOf(Math.random()+2));
String boundary2="----=hawa_multipart_alternative_"+MD5.encode(String.valueOf(Math.random()+3));
try{
//邮件头信息区
outData.print("Date: "+new Date().toGMTString()+CRLF);
outData.print("From: "+"/""+getGBKEncodeStr(fromName)+"/" <"+fromUser+">"+CRLF);
outData.print("To: ");
int tmp=0;
String to1=null;
for(Enumeration enu=tos.keys();enu.hasMoreElements();){
to1=(String)enu.nextElement();
outData.print((tmp==0?"":","+CRLFTAB)+"/""+getGBKEncodeStr((String)tos.get(to1))+"/" <"+to1+">");
tmp++;
}
outData.print(CRLF);
outData.print("Subject: ");
for(int i=0;i<subject.length();i+=26){
if(i>0)outData.print(CRLFTAB);
outData.print(getGBKEncodeStr(subject.substring(i,Math.min(subject.length(),i+26))));
}
outData.print(CRLF);
outData.print("Message-ID: <"+System.currentTimeMillis()+fromUser.substring(fromUser.indexOf("@"))+">"+CRLF);
outData.print("X-Priority: "+priority+CRLF);
outData.print("X-Mailer: Hawa Java Mailer"+CRLF);
//阅读回执
if(returnReceipt)outData.print("Disposition-Notification-To: "+"/""+getGBKEncodeStr(fromName)+"/" <"+fromUser+">"+CRLF);
outData.print("MIME-Version: 1.0"+CRLF);
outData.print("Content-Type: multipart/mixed;boundary=/""+boundary0+"/""+CRLF);
outData.print(CRLF);
///邮件体数据区(分隔符定义)///
outData.print("This is a multi-part message in MIME format."+CRLF);
outData.print("--"+boundary0+CRLF);
outData.print("Content-Type: multipart/related;type=/"multipart/alternative/";"+CRLFTAB+"boundary=/""+boundary1+"/""+CRLF);
outData.print(CRLF);
outData.print("//Alternative boundaryID"+CRLF);
outData.print("--"+boundary1+CRLF);
outData.print("Content-Type: multipart/alternative;"+CRLFTAB+"boundary=/""+boundary2+"/""+CRLF);
outData.print(CRLF);
///邮件普通文本///
outData.print("//Alternative plaintext"+CRLF);
outData.print("--"+boundary2+CRLF);
outData.print("Content-Type: text/plain;"+CRLFTAB+"charset=/"gb2312/";"+CRLF);
outData.print("Content-Transfer-Encoding: base64"+CRLF);
outData.print("Content-Disposition: inline"+CRLF);
outData.print(CRLF);
outData.print(encoder.encode(textContent.getBytes())+CRLF);
outData.print(CRLF);
///邮件HTML文本///
outData.print("//Alternative html"+CRLF);
outData.print("--"+boundary2+CRLF);
outData.print("Content-Type: text/html;"+CRLFTAB+"charset=/"GB2312/""+CRLF);
outData.print("Content-Transfer-Encoding: base64"+CRLF);
outData.print("Content-Disposition: inline"+CRLF);
outData.print(CRLF);
outData.print(encoder.encode(htmlContent.getBytes())+CRLF);
outData.print(CRLF);
outData.print("--"+boundary2+"--"+CRLF);
outData.print(CRLF);
///邮件HTML链接Content-ID(主要包含image和style)///
for(Enumeration enu=attach0.elements();enu.hasMoreElements();)((Attachment)enu.nextElement()).writeToMail(outData,boundary1);
outData.print("--"+boundary1+"--"+CRLF);
outData.print(CRLF);
///邮件附件///
for(Enumeration enu=attach1.elements();enu.hasMoreElements();)((Attachment)enu.nextElement()).writeToMail(outData,boundary0);
outData.print("--"+boundary0+"--"+CRLF);
//
outData.flush();
}catch(Exception e){
return (connected=false);
}
return true;
}
public static String GetMIMEType(String filename){
if(filename==null||filename.equals("")||filename.indexOf(".")==-1)return("application/octet-stream");
String ext=filename.substring(filename.lastIndexOf(".")).toLowerCase();
String ret=SystemConfig.getParameter(ext);
if(ret==null||ret.equals(""))return("application/octet-stream");
return ret;
}
}
软件人才网http://www.soft-hr.cn欢迎您!