欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

javax.mail.Message发送Email,带附件,异步

程序员文章站 2022-04-14 17:52:30
...
package 

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.Address;
import javax.mail.Authenticator;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
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 org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class TestMailSender {
	private static final Logger log = LoggerFactory.getLogger(MailSender.class);
	
	/* 邮箱配置详情 */
	private static final String MAIL_SMTP_AUTH = "true";
	private static final String MAIL_HOST = "";
	private static final String MAIL_TRANSPORT_PROTOCOL = "smtp";
	private static final String MAIL_SMTP_PORT = "25";
	private static final String MAIL_AUTH_NAME = "";
	private static final String MAIL_AUTH_PASSWORD = "";
	private static final String MAIL_DISPLAY_SENDNAME = "发件人";
	private static final String MAIL_DISPLAY_SENDMAIL = "";
	private static final String MAIL_SEND_CHARSET = "UTF-8";
	private static final boolean MAIL_IS_DEBUG = true;
	
	/* 邮箱逻辑配置 */
	private static final int MAIL_FAILED_RESEND_TIMES = 3; 								// 同步、异步发送邮件失败,尝试发送次数
	private static final long MAIL_FAILED_ASYN_RESEND_WAITING_MILLISECONDS = 10000; 	// 异步多线程发送邮件失败,重试间隔时间
	private static final long MAIL_FAILED_SYNC_RESEND_WAITING_MILLISECONDS = 3000;  	// 同步发送邮件失败,线程等待时间
	public static final long MAIL_ASYN_SEND_SPACING_MILLISECONDS = 2000;
	public static final long MAIL_SYNC_SEND_SPACING_MILLISECONDS = 1000;
	
	private static final Message message = initMessage();
	
	// 初始化邮箱配置
	private static final Message initMessage() {
		// 基本配置
		Properties props = new Properties();
		props.setProperty("mail.smtp.auth", MAIL_SMTP_AUTH);
		props.setProperty("mail.host", MAIL_HOST);
		props.setProperty("mail.transport.protocol", MAIL_TRANSPORT_PROTOCOL);
		props.setProperty("mail.smtp.port", MAIL_SMTP_PORT);

		Session session = Session.getInstance(props, new Authenticator() {
			@Override
			protected PasswordAuthentication getPasswordAuthentication() {
				return new PasswordAuthentication(MAIL_AUTH_NAME,
						MAIL_AUTH_PASSWORD);
			}
		});

		session.setDebug(MAIL_IS_DEBUG);
		Message message = new MimeMessage(session);

		try {
			message.setFrom(new InternetAddress(MimeUtility
					.encodeText(MAIL_DISPLAY_SENDNAME)
					+ "<"
					+ MAIL_DISPLAY_SENDMAIL + ">"));
		} catch (AddressException e) {
			log.error("初始化邮箱配置失败", e);
			throw new RuntimeException(e.getMessage());
		} catch (UnsupportedEncodingException e) {
			log.error("初始化邮箱配置失败", e);
			throw new RuntimeException(e.getMessage());
		} catch (MessagingException e) {
			log.error("初始化邮箱配置失败", e);
			throw new RuntimeException(e.getMessage());
		}
		return message;
	}
	
	/**
	 * 异步根据主题和内容发送邮件
	 * 
	 * @param subject 邮件主题
	 * @param content 邮件内容
	 * @param beSentMails 接收邮件的邮箱地址,不合法的邮箱会自动过滤,如果没有合法邮箱不会发送任何邮件
	 */
	public static void asynMail(String subject, String content, String... beSentMails) {
		Thread thread = new MailSenderByContentDetailThread(subject, content, beSentMails);
		thread.start();
	}
	
	/**
	 * 异步根据主题和内容发送邮件(带附件)
	 * 
	 * @param subject 邮件主题
	 * @param content 邮件内容
	 * @param attachmentName 附件文件名
	 * @param attachmentFile 附件地址
	 * @param beSentMails 接收邮件的邮箱地址,不合法的邮箱会自动过滤,如果没有合法邮箱不会发送任何邮件
	 */
	public static void asynMultiMail(String subject, String content, String attachmentName, String attachmentFile, String... beSentMails) {
		Thread thread = new MailSenderByContentDetailThread(subject, content, attachmentName, attachmentFile,beSentMails);
		thread.start();
	}
	
	/**
	 * 内部包装,根据主题和内容发送邮件
	 * 
	 * @param subject 邮件主题
	 * @param content 邮件内容
	 * @param beSentMails 接收邮件的邮箱地址,不合法的邮箱会自动过滤,如果没有合法邮箱不会发送任何邮件
	 */
	private static final synchronized void sendMail(String subject, String content, String... beSentMails) {
		// 过滤不合法邮箱
		beSentMails = validBeSentMails(beSentMails);

		// 检验过滤结果
		if (ArrayUtils.isEmpty(beSentMails)) {
			return;
		}
		
		try {
			message.setSubject(subject);
			message.setContent(content,"text/html;charset=" + MAIL_SEND_CHARSET );
			
			Address[] addresses = new Address[beSentMails.length];
			for (int i = 0; i < beSentMails.length; i++) {
				addresses[i] = new InternetAddress(beSentMails[i]);
			}
			message.setRecipients(Message.RecipientType.TO, addresses);
			Transport.send(message);
		} catch (Exception e) {
			log.error("根据主题和内容发送邮失败", e);
			throw new RuntimeException("resend" + e.getMessage());
		}
		
	}
	
	/**
	 * 内部包装,根据主题和内容发送邮件(带附件)
	 * 
	 * @param subject 邮件主题
	 * @param content 邮件内容
	 * @param attachmentName 附件文件名
	 * @param attachmentFile 附件地址
	 * @param beSentMails 接收邮件的邮箱地址,不合法的邮箱会自动过滤,如果没有合法邮箱不会发送任何邮件
	 */
	private static final synchronized void sendMultiMail(String subject, String content, String attachmentName, String attachmentFile, String... beSentMails) {
		// 过滤不合法邮箱
		beSentMails = validBeSentMails(beSentMails);

		// 检验过滤结果
		if (ArrayUtils.isEmpty(beSentMails)) {
			return;
		}
		
		try {
			//邮件标题
			message.setSubject(subject);
			//message.setContent(content,"text/html;charset=" + MAIL_SEND_CHARSET );
			
			Multipart multipart = new MimeMultipart();
			
			//设置邮件的文本内容
	        BodyPart contentPart = new MimeBodyPart();
	        contentPart.setText(content);
	        multipart.addBodyPart(contentPart);
	        
	        //添加附件
	        BodyPart messageBodyPart= new MimeBodyPart();
	        DataSource source = new FileDataSource(attachmentFile);
	        //添加附件的内容
	        messageBodyPart.setDataHandler(new DataHandler(source));
	        //添加附件的标题
	        messageBodyPart.setFileName(attachmentName);
	        
	        multipart.addBodyPart(messageBodyPart);
	        
	        //将multipart对象放到message中
            message.setContent(multipart);
            //保存邮件
            message.saveChanges();
			
			Address[] addresses = new Address[beSentMails.length];
			for (int i = 0; i < beSentMails.length; i++) {
				addresses[i] = new InternetAddress(beSentMails[i]);
			}
			message.setRecipients(Message.RecipientType.TO, addresses);
			Transport.send(message);
		} catch (Exception e) {
			log.error("根据主题和内容发送邮失败", e);
			throw new RuntimeException("resend" + e.getMessage());
		}
		
	}
	
	// 返回所有合法的邮箱,都不合法则返回空
	private static String[] validBeSentMails(String... beSentMails) {
		// 没有任何接收邮箱
		if (ArrayUtils.isEmpty(beSentMails)) {
			return null;
		}

		// 验证邮箱格式合法性
		Pattern pattern = Pattern.compile("^([a-z0-9_\\.-]+)@([\\da-z\\.-]+)\\.([a-z\\.]{2,6})$");
		List<String> finalBeSentMails = new ArrayList<String>();
		for (String tmp : beSentMails) {
			if (StringUtils.isEmpty(tmp)) {
				continue;
			}

			Matcher matcher = pattern.matcher(tmp);
			if (matcher.matches()) {
				finalBeSentMails.add(tmp);
			}
		}

		// 只给合法的地址发送邮件
		if (finalBeSentMails.size() != beSentMails.length) {
			if (finalBeSentMails.size() == 0) {
				return null;
			}
			return finalBeSentMails.toArray(new String[finalBeSentMails.size()]);
		}
		
		// 所有邮箱均合法
		return beSentMails;
	}
	
	// 根据详情发送邮件线程类
	static final class MailSenderByContentDetailThread extends Thread {
		private String subject;
		private String content;
		private String[] beSentMails;
		private String attachmentName;//附件文件名
		private String attachmentFile;//附件文件位置
		
		MailSenderByContentDetailThread(String subject, String content, String[] beSentMails) {
			this.subject = subject;
			this.content = content;
			this.beSentMails = beSentMails;
			this.attachmentName = null;
			this.attachmentFile = null;
		}
		
		MailSenderByContentDetailThread(String subject, String content, String attachmentName, String attachmentFile, String[] beSentMails) {
			this.subject = subject;
			this.content = content;
			this.beSentMails = beSentMails;
			this.attachmentName = attachmentName;
			this.attachmentFile = attachmentFile;
		}
		
		@Override
		public void run() {
			int sendTimes = 0;
			int i = 0;
			// 发送次数在允许尝试范围内
			do {
				try {
					// 发送邮件
					for (; i < beSentMails.length; i++) {
						if(StringUtils.isNotBlank(attachmentName) && StringUtils.isNotBlank(attachmentFile)){
							TestMailSender.sendMultiMail(subject, content, attachmentName, attachmentFile, beSentMails[i]);
						}else{
							TestMailSender.sendMail(subject, content, beSentMails[i]);
						}
						if (i < beSentMails.length - 1) {
							Thread.sleep(MAIL_ASYN_SEND_SPACING_MILLISECONDS);
						}
					}
					break;
				} catch(Exception e) {
					// 不是邮件发送的异常,直接输出错误信息并跳出
					if (!e.getMessage().startsWith("resend")) {
						log.error("邮件发送失败", e);
						break;
					}
					
					// 发送次数已到达允许尝试范围,记录错误信息
					if (sendTimes == MAIL_FAILED_RESEND_TIMES) {
						log.error("邮件发送失败,错误原因:{}" , e.getMessage().substring(6, e.getMessage().length() - 1));
						log.error("发送主题为:{}" ,subject);
						log.error("发送内容为:{}" , content);
						log.error("接收邮箱有:{}" , Arrays.toString(beSentMails));
						log.error("为<{}>发送邮件时出错,后续邮件均未发送。",beSentMails[i]);
						break;
					}
					
					
					// 线程休眠后重试
					try {
						Thread.sleep(MAIL_FAILED_ASYN_RESEND_WAITING_MILLISECONDS);
					} catch (InterruptedException e1) {
						log.error("线程休眠后重试失败", e1);
					}
				}
			} while (sendTimes++ < MAIL_FAILED_RESEND_TIMES);
		}
	}
	
	
}