【Java】纯底层SMTP实现邮件发送核心源码

 信息来源: 软件人才网    作者:林立超

       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欢迎您!
 
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值