Java发送邮件工具类(可发送匿名邮件)

Wesley13
• 阅读 921

为了不想到处去下载jar包,我使用maven为我管理,在开始编码这些东西之前,我们先在pom.xml文件中标签内加入以下内容:

<!-- Following jars are involved by MailSender.java -->
<dependency>
    <groupId>com.sun.mail</groupId>
    <artifactId>javax.mail</artifactId>
    <version>1.5.2</version>
</dependency>
<dependency>
    <groupId>javax.activation</groupId>
    <artifactId>activation</artifactId>
    <version>1.1.1</version>
</dependency>

为了方便,我抽象了一个MailMessage对象,此对象代表了邮件对象,内封装了收信人、发信人、邮件内容、抄送人、密件抄送等等诸多代表邮件的属性,如下:

package com.abc.common.mail;

/**
 * Represents a Mail message object which contains all the massages needed
 * by an email.
 */
class MailMessage {
    private String subject;
    private String from;
    private String[] tos;
    private String[] ccs;
    private String[] bccs;
    private String content;
    private String[] fileNames;

    /**
     * No parameter constructor.
     */
    public MailMessage(){}
    
    /**
     * Construct a MailMessage object.
     */
    public MailMessage(String subject, String from, String[] tos, 
            String[] ccs, String[] bccs, String content, String[] fileNames) {
        this.subject = subject;
        this.from = from;
        this.tos = tos;
        this.ccs = ccs;
        this.bccs = bccs;
        this.content = content;
        this.fileNames = fileNames;
    }
    /**
     * Construct a simple MailMessage object.
     */
    public MailMessage(String subject, String from, String to, String content) {
        this.subject = subject;
        this.from = from;
        this.tos = new String[]{to};
        this.content = content;
    }
    public String getSubject() {
        return subject;
    }
    public void setSubject(String subject) {
        this.subject = subject;
    }
    public String getFrom() {
        return from;
    }
    public void setFrom(String from) {
        this.from = from;
    }
    public String[] getTos() {
        return tos;
    }
    public void setTos(String[] tos) {
        this.tos = tos;
    }
    public String[] getCcs() {
        return ccs;
    }
    public void setCcs(String[] ccs) {
        this.ccs = ccs;
    }
    public String[] getBccs() {
        return bccs;
    }
    public void setBccs(String[] bccs) {
        this.bccs = bccs;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    public String[] getFileNames() {
        return fileNames;
    }
    public void setFileNames(String[] fileNames) {
        this.fileNames = fileNames;
    }
}

另外,我们还需要一个对象来描述发送者的授权问题。即,发送者在发送邮件直线需要获取SMTP服务器的授权,只有经过授权的账户才能发送邮件,这个对象如下:

package com.abc.common.mail;

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

public class MailAuthenticator extends Authenticator {
    
    /**
     * Represents the username of sending SMTP server.
     * <p>For example: If you use smtp.163.com as your smtp server, then the related
     * username should be: <br>'<b>testname@163.com</b>', or just '<b>testname</b>' is OK.
     */
    private String username = null;
    /**
     * Represents the password of sending SMTP sever.
     * More explicitly, the password is the password of username.
     */
    private String password = null;

    public MailAuthenticator(String user, String pass) {
        username = user;
    password = pass;
    }

    protected PasswordAuthentication getPasswordAuthentication() {
    return new PasswordAuthentication(username, password);
    }
}

最后,是最重要的主类了。调用此类的sendEmail(MailMessage mail)方法可以发送邮件,这封邮件中可以包含一个或多个附件。

但是在发送附件之前,我们需要了解附件名和内容乱码的问题:MIME要解决的一个问题就是将SMTP协议不支持的字节流转换成为SMTP 协议支持的字节流。比如我们要通过邮件传输一个附件文档,该附件文档就是一个8bit 字节流,如果简单的直接通过SMTP 发送,其最高位信息将被丢失。MIME规定可以用两种编码方式将8bit 的字节流编码成为低于8bit 的字节流,它们分别是BASE64 编码(BASE64 将8bit 字节流编码成6bit 字节流)和QP 编码。这两种编码方式同样应用在对中文的编码上。例如如果邮件中文题目叫做“CVS 介绍”,那么其编码后的形式可能为:

Subject: =?gb2312?B?Q1ZTLS3QpMX0LnBwdA==?=

其中,标题字符串以”=?”开始,以”?=”结束。”gb2312”表示字符串的字符集,而以”?”分隔的”B”就表示此字符串的编码方式为BASE64。那么,此编码从何而来的呢?查阅相关资料后,发现MimeUtility.encodeWord()和MimeUtility.encodeText()等方法就是用来编码中文等特殊字符的:

//solve encoding problem of attachments file name.
try {
    fileName = MimeUtility.encodeText(fileName);
} catch (UnsupportedEncodingException e) {
    LOGGER.error("Cannot convert the encoding of attachments file name.", e);
}

同样的, 我们处理此标题时就要先将BASE64编码的6bit 字节流转换为原来的8bit 字节流,再根据字符集”gb2312”转换为Java 中的String 类型。这里可以简单的使用JavaMail 提供的MimeUtility.decodeWord()或者MimeUtility.decodeText()静态方法将编码后的字符串解码。当然,不是每个情况都会出现乱码的,所以,不要对所有的乱码都执行这个操作,因此,我们需要判断其内容是不是符合某些规则,满足这些规则的字符串,我们可以视其为乱码,并执行相应的解码操作。于是,我封装了一个方法,方法内部进行了内容的判断,如果满足规则,则进行解码,否则不进行:

/**
 * For receiving an email, the sender, receiver, reply-to and subject may 
 * be messy code. The default encoding of HTTP is ISO8859-1, In this situation, 
 * use MimeUtility.decodeTex() to convert these information to GBK encoding.
 * @param res The String to be decoded.
 * @return A decoded String.
 */
private static String mimeDecodeString(String res) {
    if(res != null) {
        String s = res.trim();
        try {
            if (s.startsWith("=?GB") || s.startsWith("=?gb")
                    || from.startsWith("=?UTF") || s.startsWith("=?utf")) {
                s = MimeUtility.decodeText(from);
            }
        } catch (Exception e) {
            LOGGER.error("Decode string error. Origin string is: " + res, e);
        }
        return from;
    }
    return null;
}

另外,这个类中还有一个发送匿名邮件的API叫sendAnonymousEmail(MailMessage mail)(仅供交流学习研究使用,不要拿去做坏事哦)。注意,此处的匿名,并不是不写发送者的邮箱,这里的匿名是指我们可以输入任何有效的邮箱地址,这个地址不一定存在,只需要满足邮箱格式的地址即可。比如noreply@sina.cc,又比如111111@111.com,通过这类地址实现隐藏发送者地址的目的。事实上,我们也无需输入真实的发送者地址,因为这封邮件将跳过发送者的SMTP服务器而直接发送到接收者的服务器上。要想明白这个道理,我们得先说说MX。

MX(Mail Exchanger)记录是邮件交换记录,它指向一个邮件服务器,用于电子邮件系统发邮件时根据收信人的地址后缀来定位邮件服务器。例如,当Internet上的某用户要发一封信给 user@mydomain.com 时,该用户的邮件系统通过本机DNS查找mydomain.com这个域名的MX记录,如果MX记录存在,用户计算机就将邮件发送到MX记录所指定的邮件服务器上。

简单的说,MX记录就是用于为发送的邮件指路的记录,它直接指向收件人邮箱所在的域的邮件接收服务器。有了这个邮件接收服务器地址,我们的机器就可以直接向该服务器传送邮件了。

话不多说,看看这个发送匿名邮件的API吧:

/**
 * Send anonymous email. Note that although we could give any address as from address,
 * (for example: <b>'a@a.a' is valid</b>), the from of MailMessage should always be the 
 * correct format of email address(for example the <b>'aaaa' is invalid</b>). Otherwise 
 * an exception would be thrown say that username is invalid.
 * @param mail The MailMessage object which contains at least all the required 
 *        attributes to be sent.
 */
public static void sendAnonymousEmail(MailMessage mail) {
    String dns = "dns://";
    Hashtable<String, String> env = new Hashtable<String, String>();
    env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
    env.put(Context.PROVIDER_URL, dns);
    String[] tos = mail.getTos();
    try {
        DirContext ctx = new InitialDirContext(env);
        for(String to:tos) {
            String domain = to.substring(to.indexOf('@') + 1);
            //Get MX(Mail eXchange) records from DNS
            Attributes attrs = ctx.getAttributes(domain, new String[] { "MX" });
            if (attrs == null || attrs.size() <= 0) {
                throw new java.lang.IllegalStateException(
                    "Error: Your DNS server has no Mail eXchange records!");
            }
            @SuppressWarnings("rawtypes")
            NamingEnumeration servers = attrs.getAll();
            String smtpHost = null;
            boolean isSend = false;
            StringBuffer buf = new StringBuffer();
            //try all the mail exchange server to send the email.
            while (servers.hasMore()) {
                Attribute hosts = (Attribute) servers.next();
                for (int i = 0; i < hosts.size(); ++i) {
                    //sample: 20 mx2.qq.com
                    smtpHost = (String) hosts.get(i);
                    //parse the string to get smtpHost. sample: mx2.qq.com
                    smtpHost = smtpHost.substring(smtpHost.lastIndexOf(' ') + 1);
                    try {
                        sendEmail(smtpHost, mail, true);
                        isSend = true;
                        return;
                    } catch (Exception e) {
                        LOGGER.error("", e);
                        buf.append(e.toString()).append("\r\n");
                        continue;
                    }
                }
            }
            if (!isSend) {
                throw new java.lang.IllegalStateException("Error: Send email error."
                        + buf.toString());
            }
        }
    } catch (NamingException e) {
        LOGGER.error("", e);
    }
}

这个API中,先从邮件中封装的收件人地址里取出收件人所在发服务器地址,然后通过该地址查找本地DNS记录,如果未找到,则抛出IllegalStateException,因为没法知道收件人的邮件服务器地址就没法发送匿名邮件了。如果找到,则尝试依次向每个邮件服务器发送该邮件,如果发送成功,则立即返回,不再尝试下一个邮件服务器地址。如果发送失败,则会抛出异常,提醒失败。注意到这个API中间的

sendEmail(smtpHost, mail, true);

此方法是我封装的用于发送邮件的基础方法。话不多说,先上代码:

/**
 * Send Email. Use string array to represents attachments file names.
 * @see #sendEmail(String, String, String[], String[], String[], String, File[])
 */
private static void sendEmail(String smtpHost, MailMessage mail, 
        boolean isAnonymousEmail) {
    if(mail == null) {
        throw new IllegalArgumentException("Param mail can not be null.");
    }
    String[] fileNames = mail.getFileNames();
    //only needs to check the param: fileNames, other params would be checked through
    //the override method.
    File[] files = null;
    if(fileNames != null && fileNames.length > 0) {
        files = new File[fileNames.length];
        for(int i = 0; i < files.length; i++) {
            File file = new File(fileNames[i]);
            files[i] = file;
        }
    }
    sendEmail(smtpHost, mail.getSubject(), mail.getFrom(), mail.getTos(), 
            mail.getCcs(), mail.getBccs(), mail.getContent(), files, isAnonymousEmail);
}

为了重用有些代码,我特意提取了一部分公共的部分,因此此方法是一个重载方法,也是最核心的方法了。需要注意的是,发送匿名邮件时,需要将mail.smtp.auth属性设置为false,并且在获取邮件session时,不需要提供邮件验证器Authenticator:

if(isAnonymousEmail) {
    //only anonymous email needs param smtpHost
    props.put("mail.smtp.host", smtpHost);
    props.put("mail.smtp.auth", "false");
    session = Session.getInstance(props, null);
}

下面再看看这个被调用的sendEmail方法吧:

/**
 * Send Email. Note that content and attachments cannot be empty at the same time.
 * @param smtpHost The SMTPHost. This param is needed when sending an anonymous email.
 *        When sending normal email, the param is ignored and the default SMTPServer
 *        configured is used.
 * @param subject The email subject.
 * @param from The sender address. This address must be available in SMTPServer.
 * @param tos The receiver addresses. At least 1 address is valid.
 * @param ccs The 'copy' receiver. Can be empty.
 * @param bccs The 'encrypt copy' receiver. Can be empty.
 * @param content The email content.
 * @param attachments The file array represent attachments to be send.
 * @param isAnonymousEmail If this mail is send in anonymous mode. When set to true, the 
 *        param smtpHost is needed and sender's email address from should be in correct
 *        pattern.
 */
private static void sendEmail(String smtpHost, String subject, String from, 
        String[] tos, String[] ccs, String[] bccs, String content, 
        File[] attachments, boolean isAnonymousEmail) {
    //parameter check
    if(isAnonymousEmail && smtpHost == null) {
        throw new IllegalStateException(
            "When sending anonymous email, param smtpHost cannot be null");
    }
    if(subject == null || subject.length() == 0) {
        subject = "Auto-generated subject";
    }
    if(from == null) {
        throw new IllegalArgumentException("Sender's address is required.");
    }
    if(tos == null || tos.length == 0) {
        throw new IllegalArgumentException(
            "At lease 1 receive address is required.");
    }
    if(content == null && (attachments == null || attachments.length == 0)) {
        throw new IllegalArgumentException(
            "Content and attachments cannot be empty at the same time");
    }
    if(attachments != null && attachments.length > 0) {
        List<File> invalidAttachments = new ArrayList<>();
        for(File attachment:attachments) {
            if(!attachment.exists() || attachment.isDirectory() 
                || !attachment.canRead()) {
                invalidAttachments.add(attachment);
            }
        }
        if(invalidAttachments.size() > 0) {
            String msg = "";
            for(File attachment:invalidAttachments) {
                msg += "\n\t" + attachment.getAbsolutePath();
            }
            throw new IllegalArgumentException(
                "The following attachments are invalid:" + msg);
        }
    }
    Session session;
    Properties props = new Properties();
    props.put("mail.transport.protocol", "smtp");
    
    if(isAnonymousEmail) {
        //only anonymous email needs param smtpHost
        props.put("mail.smtp.host", smtpHost);
        props.put("mail.smtp.auth", "false");
        session = Session.getInstance(props, null);
    } else {
        //normal email does not need param smtpHost and 
        //uses the default host SMTPServer
        props.put("mail.smtp.host", SMTPServer); 
        props.put("mail.smtp.auth", "true");
        session = Session.getInstance(
            props, new MailAuthenticator(SMTPUsername, SMTPPassword));
    }
    //create message
    MimeMessage msg = new MimeMessage(session);
    try {
        //Multipart is used to store many BodyPart objects.
        Multipart multipart=new MimeMultipart();
        
        BodyPart part = new MimeBodyPart();
        part.setContent(content,"text/html;charset=gb2312");
        //add email content part.
        multipart.addBodyPart(part);
        
        //add attachment parts.
        if(attachments != null && attachments.length > 0) {
            for(File attachment: attachments) {
                String fileName = attachment.getName();
                DataSource dataSource = new FileDataSource(attachment);
                DataHandler dataHandler = new DataHandler(dataSource);
                part = new MimeBodyPart();
                part.setDataHandler(dataHandler);
                //solve encoding problem of attachments file name.
                try {
                    fileName = MimeUtility.encodeText(fileName);
                } catch (UnsupportedEncodingException e) {
                    LOGGER.error(
                        "Cannot convert the encoding of attachments file name.", e);
                }
                //set attachments the original file name. if not set, 
                //an auto-generated name would be used.
                part.setFileName(fileName);
                multipart.addBodyPart(part);
            }
        }
        msg.setSubject(subject);
        msg.setSentDate(new Date());
        //set sender
        msg.setFrom(new InternetAddress(from));
        //set receiver, 
        for(String to: tos) {
            msg.addRecipient(RecipientType.TO, new InternetAddress(to));
        }
        if(ccs != null && ccs.length > 0) {
            for(String cc: ccs) {
                msg.addRecipient(RecipientType.CC, new InternetAddress(cc));
            }
        }
        if(bccs != null && bccs.length > 0) {
            for(String bcc: bccs) {
                msg.addRecipient(RecipientType.BCC, new InternetAddress(bcc));
            }
        }
        msg.setContent(multipart);
        //save the changes of email first.
        msg.saveChanges();
        //to see what commands are used when sending a email, 
        //use session.setDebug(true)
        //session.setDebug(true);
        //send email
        Transport.send(msg); 
        LOGGER.info("Send email success.");
        System.out.println("Send html email success.");
    } catch (NoSuchProviderException e) {
        LOGGER.error("Email provider config error.", e);
    } catch (MessagingException e) {
        LOGGER.error("Send email error.", e);
    }
}

有了《JavaMail发送和接收邮件API(详解)》一文的基础和前文的叙述,我想里面的逻辑应该不用多解释了吧。下面主要讲讲里面的几个变量。

正如你所见,里面的几个变量SMTPServer、SMTPUsername和SMTPPassword是需要配置的。如果是发送匿名邮件,那么SMTPUsername和SMTPPassword两个变量可以不用配置。这里,我是将这些内容搬到了项目的一个配置文件里,并在初始化这个对象时就去读取指定的配置文件获得这些值:

private static String SMTPServer;
private static String SMTPUsername;
private static String SMTPPassword;
static {
    loadConfigProperties();
}
/**
 * Load configuration properties to initialize attributes.
 */
private static void loadConfigProperties() {
    //get current path
    File f = new File("");
    String absolutePath = f.getAbsolutePath();
    String propertiesPath = "";
    String OSName = System.getProperty("os.name");
    if(OSName.contains("Windows")) {
        propertiesPath = absolutePath + "\\..\\src\\main\\resources\\project.properties";
    } else if(OSName.contains("unix")) {
        propertiesPath = absolutePath + "/../src/main/resources/project.properties";
    }
    f = new File(propertiesPath);
    if(!f.exists()) {
        throw new RuntimeException(
            "Porperties file not found at: " + f.getAbsolutePath());
    }
    Properties props = new Properties();
    try {
        props.load(new FileInputStream(f));
        SMTPServer = props.getProperty("AbcCommon.mail.SMTPServer");
        SMTPUsername = props.getProperty("AbcCommon.mail.SMTPUsername");
        SMTPPassword = props.getProperty("AbcCommon.mail.SMTPPassword");
        POP3Server = props.getProperty("AbcCommon.mail.POP3Server");
        POP3Username = props.getProperty("AbcCommon.mail.POP3Username");
        POP3Password = props.getProperty("AbcCommon.mail.POP3Password");
    } catch (FileNotFoundException e) {
        LOGGER.error("File not found at " + f.getAbsolutePath(), e);
    } catch (IOException e) {
        LOGGER.error("Error reading config file " + f.getName(), e);
    }
}

说了这么多发送邮件,下面再说说接收邮件。

首先,接收邮件,是肯定需要用户名和密码的,因此此方法至少含有两个参数。由于各大邮件服务公司的邮件服务器命名不统一,因此还需要一个参数来指定接收邮件的服务器,于是,此方法将含有三个参数。接收邮件的思路是:用username和password创建一个邮件验证器Authenticator,通过这个Authenticator来获得一个邮件Session。拿到Session后,通过

Store store = session.getStore("pop3");
Folder inbox = store.getFolder("INBOX");

    一句,可以获得该账户的收件箱。《JavaMail发送和接收邮件API(详解)》一文中有提到,Store即是用来接收邮件的对象。然后可以通过

Message[] messages = inbox.getMessages();

    来获得收件箱中的所有信息。接下来就可以迭代这个数组获取需要的内容了。整个方法如下:

/**
 * Receive Email from POPServer. Use POP3 protocal by default. Thus,
 * call this method, you need to provide a pop3 mail server address.
 * @param emailAddress The email account in the POPServer.
 * @param password The password of email address.
 */
public static void receiveEmail(String host, String username, String password) {
    //param check. If param is null, use the default configured value.
    if(host == null) {
        host = POP3Server;
    }
    if(username == null) {
        username = POP3Username;
    }
    if(password == null) {
        password = POP3Password;
    }
    Properties props = System.getProperties();
    //MailAuthenticator authenticator = new MailAuthenticator(username, password);
    try {
        Session session = Session.getDefaultInstance(props, null);
        // Store store = session.getStore("imap");
        Store store = session.getStore("pop3");
        // Connect POPServer
        store.connect(host, username, password);
        Folder inbox = store.getFolder("INBOX");
        if (inbox == null) {
            throw new RuntimeException("No inbox existed.");
        }
        // Open the INBOX with READ_ONLY mode and start to read all emails.
        inbox.open(Folder.READ_ONLY);
        System.out.println("TOTAL EMAIL:" + inbox.getMessageCount());
        Message[] messages = inbox.getMessages();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        for (int i = 0; i < messages.length; i++) {
            Message msg = messages[i];
            String from = InternetAddress.toString(msg.getFrom());
            String replyTo = InternetAddress.toString(msg.getReplyTo());
            String to = InternetAddress.toString(
                msg.getRecipients(Message.RecipientType.TO));
            String subject = msg.getSubject();
            Date sent = msg.getSentDate();
            Date ress = msg.getReceivedDate();
            String type = msg.getContentType();
            System.out.println(
                (i + 1) + ".---------------------------------------------");
            System.out.println("From:" + mimeDecodeString(from));
            System.out.println("Reply To:" + mimeDecodeString(replyTo));
            System.out.println("To:" + mimeDecodeString(to));
            System.out.println("Subject:" + mimeDecodeString(subject));
            System.out.println("Content-type:" + type);
            if (sent != null) {
                System.out.println("Sent Date:" + sdf.format(sent));
            }
            if (ress != null) {
                System.out.println("Receive Date:" + sdf.format(ress));
            }
//                //Get message headers.
//                @SuppressWarnings("rawtypes")
//                Enumeration headers = msg.getAllHeaders();
//                while (headers.hasMoreElements()) {
//                    Header h = (Header) headers.nextElement();
//                    String name = h.getName();
//                    String val = h.getValue();
//                    System.out.println(name + ": " + val);
//                }
            
//                //get the email content.
//                Object content = msg.getContent();
//                System.out.println(content);
//                //print content
//                Reader reader = new InputStreamReader(
//                        messages[i].getInputStream());
//                int a = 0;
//                while ((a = reader.read()) != -1) {
//                    System.out.print((char) a);
//                }
        }
        // close connection. param false represents do not delete messaegs on server.
        inbox.close(false);
        store.close();
//        } catch(IOException e) {
//            LOGGER.error("IOException caught while printing the email content", e);
    } catch (MessagingException e) {
        LOGGER.error("MessagingException caught when use message object", e);
    }
}

    注意到我们在处理邮件中可能出现乱码的内容时,调用了前文提到的自定义的**mimeDecodeString()**方法:

System.out.println("Subject:" + mimeDecodeString(subject));

    还有,这里面的几个变量:POP3Server、POP3Username和POP3Password也是需要配置的,并会在初始化这个工具类的时候读取。

    话不多说,让我们先发送一封试试。为了方便,随便找几个文件放入C:\\根目录下:

    Java发送邮件工具类(可发送匿名邮件)

    直接在MailUtil类中加入main方法:

Java发送邮件工具类(可发送匿名邮件)

    Ctrl+F11执行这个程序,首先可以在控制台看见以下内容(注意执行的程序和发送邮件的时间):

Java发送邮件工具类(可发送匿名邮件)

    然后再进入邮箱,看到收到的邮件:

Java发送邮件工具类(可发送匿名邮件)

    打开邮件后,会看到以下内容(注意标题,发件人,收件人和邮件内容,发送时间):

Java发送邮件工具类(可发送匿名邮件)

    在看看附件的内容(注意附件名字):

Java发送邮件工具类(可发送匿名邮件)

    匿名邮件的发送也类似,我已经测试过了,这里不再贴出。好了,最后再贴出工具类的完整代码:

package com.abc.common.mail;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.BodyPart;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.Message.RecipientType;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.NoSuchProviderException;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

import org.apache.log4j.Logger;

public class MailUtil {
    
    private static final Logger LOGGER = Logger.getLogger(MailUtil.class);
    
    private static String SMTPServer;
    private static String SMTPUsername;
    private static String SMTPPassword;
    private static String POP3Server;
    private static String POP3Username;
    private static String POP3Password;
    
    static {
        loadConfigProperties();
    }
    
    public static void main(String[] args) {
        //发送邮件
        MailMessage mail = new MailMessage(
                "test-subject", 
                "xxxx@163.com", 
                "yyyy@126.com", 
                "This is mail content");
        //set attachments
        String[] attachments = new String[]{
                "C:\\AndroidManifest.xml", 
                "C:\\ic_launcher-web.png", 
                "C:\\光良 - 童话.mp3", 
                "C:\\文档测试.doc", 
                "C:\\中文文件名测试.txt"};
        mail.setFileNames(attachments);
        sendEmail(mail);
        
        //接收邮件
        receiveEmail(POP3Server, POP3Username, POP3Password);
        
        //发送匿名邮件
        MailMessage anonymousMail = new MailMessage("subject", 
            "a@a.a", "zzzz@qq.com", "content");
        anonymousMail.setFileNames(attachments);
        sendAnonymousEmail(anonymousMail);
    }
          
    /**
     * Load configuration properties to initialize attributes.
     */
    private static void loadConfigProperties() {
        File f = new File("");
        //this path would point to AbcCommon
        String absolutePath = f.getAbsolutePath();
        String propertiesPath = "";
        String OSName = System.getProperty("os.name");
        if(OSName.contains("Windows")) {
            propertiesPath = absolutePath + "\\..\\src\\main\\resources\\project.properties";
        } else if(OSName.contains("unix")) {
            propertiesPath = absolutePath + "/../src/main/resources/project.properties";
        }
        f = new File(propertiesPath);
        if(!f.exists()) {
            throw new RuntimeException("Porperties file not found at: " + f.getAbsolutePath());
        }
        Properties props = new Properties();
        try {
            props.load(new FileInputStream(f));
            SMTPServer = props.getProperty("AbcCommon.mail.SMTPServer");
            SMTPUsername = props.getProperty("AbcCommon.mail.SMTPUsername");
            SMTPPassword = props.getProperty("AbcCommon.mail.SMTPPassword");
            POP3Server = props.getProperty("AbcCommon.mail.POP3Server");
            POP3Username = props.getProperty("AbcCommon.mail.POP3Username");
            POP3Password = props.getProperty("AbcCommon.mail.POP3Password");
        } catch (FileNotFoundException e) {
            LOGGER.error("File not found at " + f.getAbsolutePath(), e);
        } catch (IOException e) {
            LOGGER.error("Error reading config file " + f.getName(), e);
        }
    }
    
    /**
     * Send email. Note that the fileNames of MailMessage are the absolute path of file.
     * @param mail The MailMessage object which contains at least all the required 
     *        attributes to be sent.
     */
    public static void sendEmail(MailMessage mail) {
        sendEmail(null, mail, false);
    }
    
    /**
     * Send anonymous email. Note that although we could give any address as from address,
     * (for example: <b>'a@a.a' is valid</b>), the from of MailMessage should always be the 
     * correct format of email address(for example the <b>'aaaa' is invalid</b>). Otherwise 
     * an exception would be thrown say that username is invalid.
     * @param mail The MailMessage object which contains at least all the required 
     *        attributes to be sent.
     */
    public static void sendAnonymousEmail(MailMessage mail) {
        String dns = "dns://";
        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
        env.put(Context.PROVIDER_URL, dns);
        String[] tos = mail.getTos();
        try {
            DirContext ctx = new InitialDirContext(env);
            for(String to:tos) {
                String domain = to.substring(to.indexOf('@') + 1);
                //Get MX(Mail eXchange) records from DNS
                Attributes attrs = ctx.getAttributes(domain, new String[] { "MX" });
                if (attrs == null || attrs.size() <= 0) {
                    throw new java.lang.IllegalStateException(
                        "Error: Your DNS server has no Mail eXchange records!");
                }
                @SuppressWarnings("rawtypes")
                NamingEnumeration servers = attrs.getAll();
                String smtpHost = null;
                boolean isSend = false;
                StringBuffer buf = new StringBuffer();
                //try all the mail exchange server to send the email.
                while (servers.hasMore()) {
                    Attribute hosts = (Attribute) servers.next();
                    for (int i = 0; i < hosts.size(); ++i) {
                        //sample: 20 mx2.qq.com
                        smtpHost = (String) hosts.get(i);
                        //parse the string to get smtpHost. sample: mx2.qq.com
                        smtpHost = smtpHost.substring(smtpHost.lastIndexOf(' ') + 1);
                        try {
                            sendEmail(smtpHost, mail, true);
                            isSend = true;
                            return;
                        } catch (Exception e) {
                            LOGGER.error("", e);
                            buf.append(e.toString()).append("\r\n");
                            continue;
                        }
                    }
                }
                if (!isSend) {
                    throw new java.lang.IllegalStateException("Error: Send email error."
                            + buf.toString());
                }
            }
        } catch (NamingException e) {
            LOGGER.error("", e);
        }
    } 
    
    /**
     * Send Email. Use string array to represents attachments file names.
     * @see #sendEmail(String, String, String[], String[], String[], String, File[])
     */
    private static void sendEmail(String smtpHost, 
        MailMessage mail, boolean isAnonymousEmail) {
        if(mail == null) {
            throw new IllegalArgumentException("Param mail can not be null.");
        }
        String[] fileNames = mail.getFileNames();
        //only needs to check the param: fileNames, other params would be checked through
        //the override method.
        File[] files = null;
        if(fileNames != null && fileNames.length > 0) {
            files = new File[fileNames.length];
            for(int i = 0; i < files.length; i++) {
                File file = new File(fileNames[i]);
                files[i] = file;
            }
        }
        sendEmail(smtpHost, mail.getSubject(), mail.getFrom(), mail.getTos(), 
                mail.getCcs(), mail.getBccs(), mail.getContent(), files, isAnonymousEmail);
    }
    
    /**
     * Send Email. Note that content and attachments cannot be empty at the same time.
     * @param smtpHost The SMTPHost. This param is needed when sending an anonymous email.
     *        When sending normal email, the param is ignored and the default SMTPServer
     *        configured is used.
     * @param subject The email subject.
     * @param from The sender address. This address must be available in SMTPServer.
     * @param tos The receiver addresses. At least 1 address is valid.
     * @param ccs The 'copy' receiver. Can be empty.
     * @param bccs The 'encrypt copy' receiver. Can be empty.
     * @param content The email content.
     * @param attachments The file array represent attachments to be send.
     * @param isAnonymousEmail If this mail is send in anonymous mode. When set to true, the 
     *        param smtpHost is needed and sender's email address from should be in correct
     *        pattern.
     */
    private static void sendEmail(String smtpHost, String subject, 
            String from, String[] tos, String[] ccs, String[] bccs, 
            String content, File[] attachments, boolean isAnonymousEmail) {
        //parameter check
        if(isAnonymousEmail && smtpHost == null) {
            throw new IllegalStateException(
                "When sending anonymous email, param smtpHost cannot be null");
        }
        if(subject == null || subject.length() == 0) {
            subject = "Auto-generated subject";
        }
        if(from == null) {
            throw new IllegalArgumentException("Sender's address is required.");
        }
        if(tos == null || tos.length == 0) {
            throw new IllegalArgumentException(
                "At lease 1 receive address is required.");
        }
        if(content == null && (attachments == null || attachments.length == 0)) {
            throw new IllegalArgumentException(
                "Content and attachments cannot be empty at the same time");
        }
        if(attachments != null && attachments.length > 0) {
            List<File> invalidAttachments = new ArrayList<>();
            for(File attachment:attachments) {
                if(!attachment.exists() || attachment.isDirectory() 
                    || !attachment.canRead()) {
                    invalidAttachments.add(attachment);
                }
            }
            if(invalidAttachments.size() > 0) {
                String msg = "";
                for(File attachment:invalidAttachments) {
                    msg += "\n\t" + attachment.getAbsolutePath();
                }
                throw new IllegalArgumentException(
                    "The following attachments are invalid:" + msg);
            }
        }
        Session session;
        Properties props = new Properties();
        props.put("mail.transport.protocol", "smtp");
        
        if(isAnonymousEmail) {
            //only anonymous email needs param smtpHost
            props.put("mail.smtp.host", smtpHost);
            props.put("mail.smtp.auth", "false");
            session = Session.getInstance(props, null);
        } else {
            //normal email does not need param smtpHost and uses the default host SMTPServer
            props.put("mail.smtp.host", SMTPServer); 
            props.put("mail.smtp.auth", "true");
            session = Session.getInstance(props, 
                new MailAuthenticator(SMTPUsername, SMTPPassword));
        }
        //create message
        MimeMessage msg = new MimeMessage(session);
        try {
            //Multipart is used to store many BodyPart objects.
            Multipart multipart=new MimeMultipart();
            
            BodyPart part = new MimeBodyPart();
            part.setContent(content,"text/html;charset=gb2312");
            //add email content part.
            multipart.addBodyPart(part);
            
            //add attachment parts.
            if(attachments != null && attachments.length > 0) {
                for(File attachment: attachments) {
                    String fileName = attachment.getName();
                    DataSource dataSource = new FileDataSource(attachment);
                    DataHandler dataHandler = new DataHandler(dataSource);
                    part = new MimeBodyPart();
                    part.setDataHandler(dataHandler);
                    //solve encoding problem of attachments file name.
                    try {
                        fileName = MimeUtility.encodeText(fileName);
                    } catch (UnsupportedEncodingException e) {
                        LOGGER.error(
                            "Cannot convert the encoding of attachments file name.", e);
                    }
                    //set attachments the original file name. if not set, 
                    //an auto-generated name would be used.
                    part.setFileName(fileName);
                    multipart.addBodyPart(part);
                }
            }
            msg.setSubject(subject);
            msg.setSentDate(new Date());
            //set sender
            msg.setFrom(new InternetAddress(from));
            //set receiver, 
            for(String to: tos) {
                msg.addRecipient(RecipientType.TO, new InternetAddress(to));
            }
            if(ccs != null && ccs.length > 0) {
                for(String cc: ccs) {
                    msg.addRecipient(RecipientType.CC, new InternetAddress(cc));
                }
            }
            if(bccs != null && bccs.length > 0) {
                for(String bcc: bccs) {
                    msg.addRecipient(RecipientType.BCC, new InternetAddress(bcc));
                }
            }
            msg.setContent(multipart);
            //save the changes of email first.
            msg.saveChanges();
            //to see what commands are used when sending a email, use session.setDebug(true)
            //session.setDebug(true);
            //send email
            Transport.send(msg); 
            LOGGER.info("Send email success.");
            System.out.println("Send html email success.");
        } catch (NoSuchProviderException e) {
            LOGGER.error("Email provider config error.", e);
        } catch (MessagingException e) {
            LOGGER.error("Send email error.", e);
        }
    }
    

    /**
     * Receive Email from POPServer. Use POP3 protocal by default. Thus,
     * call this method, you need to provide a pop3 mail server address.
     * @param emailAddress The email account in the POPServer.
     * @param password The password of email address.
     */
    public static void receiveEmail(String host, String username, String password) {
        //param check. If param is null, use the default configured value.
        if(host == null) {
            host = POP3Server;
        }
        if(username == null) {
            username = POP3Username;
        }
        if(password == null) {
            password = POP3Password;
        }
        Properties props = System.getProperties();
        //MailAuthenticator authenticator = new MailAuthenticator(username, password);
        try {
            Session session = Session.getDefaultInstance(props, null);
            // Store store = session.getStore("imap");
            Store store = session.getStore("pop3");
            // Connect POPServer
            store.connect(host, username, password);
            Folder inbox = store.getFolder("INBOX");
            if (inbox == null) {
                throw new RuntimeException("No inbox existed.");
            }
            // Open the INBOX with READ_ONLY mode and start to read all emails.
            inbox.open(Folder.READ_ONLY);
            System.out.println("TOTAL EMAIL:" + inbox.getMessageCount());
            Message[] messages = inbox.getMessages();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            for (int i = 0; i < messages.length; i++) {
                Message msg = messages[i];
                String from = InternetAddress.toString(msg.getFrom());
                String replyTo = InternetAddress.toString(msg.getReplyTo());
                String to = InternetAddress.toString(
                    msg.getRecipients(Message.RecipientType.TO));
                String subject = msg.getSubject();
                Date sent = msg.getSentDate();
                Date ress = msg.getReceivedDate();
                String type = msg.getContentType();
                System.out.println((i + 1) + ".---------------------------------------------");
                System.out.println("From:" + mimeDecodeString(from));
                System.out.println("Reply To:" + mimeDecodeString(replyTo));
                System.out.println("To:" + mimeDecodeString(to));
                System.out.println("Subject:" + mimeDecodeString(subject));
                System.out.println("Content-type:" + type);
                if (sent != null) {
                    System.out.println("Sent Date:" + sdf.format(sent));
                }
                if (ress != null) {
                    System.out.println("Receive Date:" + sdf.format(ress));
                }
//                //Get message headers.
//                @SuppressWarnings("rawtypes")
//                Enumeration headers = msg.getAllHeaders();
//                while (headers.hasMoreElements()) {
//                    Header h = (Header) headers.nextElement();
//                    String name = h.getName();
//                    String val = h.getValue();
//                    System.out.println(name + ": " + val);
//                }
                
//                //get the email content.
//                Object content = msg.getContent();
//                System.out.println(content);
//                //print content
//                Reader reader = new InputStreamReader(
//                        messages[i].getInputStream());
//                int a = 0;
//                while ((a = reader.read()) != -1) {
//                    System.out.print((char) a);
//                }
            }
            // close connection. param false represents do not delete messaegs on server.
            inbox.close(false);
            store.close();
//        } catch(IOException e) {
//            LOGGER.error("IOException caught while printing the email content", e);
        } catch (MessagingException e) {
            LOGGER.error("MessagingException caught when use message object", e);
        }
    }
    
    /**
     * For receiving an email, the sender, receiver, reply-to and subject may 
     * be messy code. The default encoding of HTTP is ISO8859-1, In this situation, 
     * use MimeUtility.decodeTex() to convert these information to GBK encoding.
     * @param res The String to be decoded.
     * @return A decoded String.
     */
    private static String mimeDecodeString(String res) {
        if(res != null) {
            String from = res.trim();
            try {
                if (from.startsWith("=?GB") || from.startsWith("=?gb")
                        || from.startsWith("=?UTF") || from.startsWith("=?utf")) {
                    from = MimeUtility.decodeText(from);
                }
            } catch (Exception e) {
                LOGGER.error("Decode string error. Origin string is: " + res, e);
            }
            return from;
        }
        return null;
    }
}
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
35岁是技术人的天花板吗?
35岁是技术人的天花板吗?我非常不认同“35岁现象”,人类没有那么脆弱,人类的智力不会说是35岁之后就停止发展,更不是说35岁之后就没有机会了。马云35岁还在教书,任正非35岁还在工厂上班。为什么技术人员到35岁就应该退役了呢?所以35岁根本就不是一个问题,我今年已经37岁了,我发现我才刚刚找到自己的节奏,刚刚上路。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这