package top.hunfan.mail.utils; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.util.Properties; import javax.activation.DataHandler; import javax.activation.FileDataSource; 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.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; import javax.mail.internet.MimeUtility; import org.apache.commons.lang3.StringUtils; import org.springframework.core.env.Environment; import lombok.extern.slf4j.Slf4j; import top.hunfan.mail.domain.Constants; import top.hunfan.mail.roundrobin.RoundRobin; import top.hunfan.mail.roundrobin.RoundRobinFactory; /** * 邮件发送,支持多邮箱配置、轮询、加权轮询 * @author hf-hf * @date 2018/12/27 10:58 */ @Slf4j public class MailUtil { private static final String MAIL_PROPERTIES = "mail%d.properties"; private static final String CHART_SET_UTF8 = "UTF-8"; private static final String CONTENT_TYPE_HTML = "text/html; charset=UTF-8"; private static final String READ_SYSTEM_PATH_FILE = "READ_SYSTEM_PATH_FILE"; private static final String READ_CLASS_PATH_FILE = "READ_CLASS_PATH_FILE"; private boolean initComplete = false; private RoundRobin roundRobin; /** * 通过单例对象获取 * @author hf-hf * @date 2018/12/27 14:59 * @param roundRobinType 轮询类型 */ private MailUtil(String roundRobinType) { //优先读取外部配置文件 readConfigFiles(READ_SYSTEM_PATH_FILE); if(MailManager.propertiesMap.isEmpty()){ log.info("read system path is null"); readConfigFiles(READ_CLASS_PATH_FILE); } if(!MailManager.propertiesMap.isEmpty()){ roundRobin = RoundRobinFactory.create(roundRobinType, MailManager.propertiesMap.values()); } if(null != roundRobin){ initComplete = true; } log.info("load mail properties finish,success count={}", MailManager.propertiesMap.size()); } /** * 读取外部配置 * @param fileName * @return */ private InputStream readSystemPath(String fileName){ String path = System.getProperty("user.dir") + File.separator + fileName; InputStream in = null; try { in = new BufferedInputStream(new FileInputStream(path)); } catch (FileNotFoundException e) { // ignore } return in; } /** * 读取内部文件 * @author hf-hf * @date 2018/12/29 9:25 */ private InputStream readClassPath(String fileName){ return this.getClass().getClassLoader().getResourceAsStream(fileName); } /** * 读取配置文件 * @author hf-hf * @date 2018/12/29 9:34 */ private void readConfigFiles(String readPath){ InputStream in = null; Properties properties = null; String fileName = ""; for (int i = 0; ; i++) { fileName = String.format(MAIL_PROPERTIES, i); in = READ_SYSTEM_PATH_FILE.equals(readPath) ? readSystemPath(fileName) : readClassPath(fileName); if (in == null) { break; } log.info("load " + fileName); try (Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) { properties = new Properties(); properties.load(reader); if(!checkMailConifg(properties)){ //throw new PropertyException(readPath + " " + fileName + "config error!"); log.info(readPath + " " + fileName + "config error!"); break; } MailManager.putBoth(properties.get("mail.id"), properties); } catch (Exception e) { log.error("load " + readPath + " " + fileName + "error!",e); } finally { try { in.close(); } catch (IOException e) { log.error("in close error", e); } } } } /** * 校验mail配置项 * @author hf-hf * @date 2018/12/28 16:03 */ private boolean checkMailConifg(Properties properties){ if(null == properties.get("mail.id")){ log.error("mail id is not null!"); return false; } if(null == properties.get("mail.username") || "".equals(properties.get("mail.username"))){ log.error("mail username is not null!"); return false; } if(null == properties.get("mail.password") || "".equals(properties.get("mail.password"))){ log.error("mail password is not null!"); return false; } if(null != properties.get("mail.weight") && !(StringUtils.isNumeric((String) properties.get("mail.weight")))){ log.error("mail weight requirement is number!"); return false; } if(null == properties.getProperty("mail.isAvailable")){ log.error("mail isAvailable is not null!"); return false; } try { Boolean tmp = Boolean.valueOf((String) properties.get("mail.isAvailable")); } catch (Exception e){ log.error("mail isAvailable requirement is boolean!"); return false; } return true; } /** * 发送邮件 * @param to 收件人,多个收件人用 {@code ;} 分隔 * @param subject 主题 * @param content 内容 * @return 如果邮件发送成功,则返回 {@code true},否则返回 {@code false} */ public boolean send(String to, String subject, String content) { return send(to, null, null, subject, content, null, null); } /** * 发送邮件 * @param to 收件人,多个收件人用 {@code ;} 分隔 * @param subject 主题 * @param content 内容 * @param attachment 附件 * @return 如果邮件发送成功,则返回 {@code true},否则返回 {@code false} */ public boolean send(String to, String subject, String content, File attachment) { File[] attachments = null; if(null != attachment){ attachments = new File[]{attachment}; } return send(to, null, null, subject, content, null, attachments); } /** * 发送邮件(负载均衡) * * @param key 负载均衡key * @param to 收件人,多个收件人用 {@code ;} 分隔 * @param cc 抄送人,多个抄送人用 {@code ;} 分隔 * @param bcc 密送人,多个密送人用 {@code ;} 分隔 * @param subject 主题 * @param content 内容,可引用内嵌图片,引用方式:{@code <img src="cid:内嵌图片文件名" />} * @param images 内嵌图片 * @param attachments 附件 * @return 如果邮件发送成功,则返回 {@code true},否则返回 {@code false} */ public boolean sendByLoadBalance(String key, String to, String cc, String bcc, String subject, String content, File[] images, File[] attachments){ log.info("loadBalanceKey={}", key); Properties properties = MailManager.getProperties(key); log.info("properties={}", properties); Session session = MailManager.getSession(key); MimeMessage message = new MimeMessage(session); String username = properties.getProperty("mail.username"); ThreadLocalUtils.put(Constants.CURRENT_MAIL_FROM, username); try { message.setFrom(new InternetAddress(username)); addRecipients(message, Message.RecipientType.TO, to); if (cc != null) { addRecipients(message, Message.RecipientType.CC, cc); } if (bcc != null) { addRecipients(message, Message.RecipientType.BCC, bcc); } message.setSubject(subject, CHART_SET_UTF8); // 最外层部分 MimeMultipart wrapPart = new MimeMultipart("mixed"); MimeMultipart htmlWithImageMultipart = new MimeMultipart("related"); // 文本部分 MimeBodyPart htmlPart = new MimeBodyPart(); htmlPart.setContent(content, CONTENT_TYPE_HTML); htmlWithImageMultipart.addBodyPart(htmlPart); // 内嵌图片部分 addImages(images, htmlWithImageMultipart); MimeBodyPart htmlWithImageBodyPart = new MimeBodyPart(); htmlWithImageBodyPart.setContent(htmlWithImageMultipart); wrapPart.addBodyPart(htmlWithImageBodyPart); // 附件部分 addAttachments(attachments, wrapPart); message.setContent(wrapPart); Transport.send(message); return true; } catch (Exception e) { log.error("sendByLoadBalance error!", e.getMessage(), "loadBalanceKey={}, properties={}, to={}, cc={}, " + "bcc={}, subject={}, content={}, images={}, attachments={}", key, properties, to, cc, bcc, subject, content, images, attachments, e); } return false; } /** * 发送邮件 * * @param to 收件人,多个收件人用 {@code ;} 分隔 * @param cc 抄送人,多个抄送人用 {@code ;} 分隔 * @param bcc 密送人,多个密送人用 {@code ;} 分隔 * @param subject 主题 * @param content 内容,可引用内嵌图片,引用方式:{@code <img src="cid:内嵌图片文件名" />} * @param images 内嵌图片 * @param attachments 附件 * @return 如果邮件发送成功,则返回 {@code true},否则返回 {@code false} */ public boolean send(String to, String cc, String bcc, String subject, String content, File[] images, File[] attachments) { if(!initComplete){ throw new ExceptionInInitializerError("mail init error!"); } // 负载均衡实现 String key = roundRobin.select().id(); if(StringUtils.isEmpty(key)){ //TODO invoker降级,移除 throw new RuntimeException("轮询异常!"); } return sendByLoadBalance(key, to, cc, bcc, subject, content, images, attachments); } /** * 追加附件 * @author hf-hf * @date 2018/12/27 16:53 * @param attachments * @param wrapPart * @throws MessagingException * @throws UnsupportedEncodingException */ private void addAttachments(File[] attachments, MimeMultipart wrapPart) throws MessagingException, UnsupportedEncodingException { if (null != attachments && attachments.length > 0) { for (int i = 0; i < attachments.length; i++) { MimeBodyPart attachmentBodyPart = new MimeBodyPart(); DataHandler dataHandler = new DataHandler(new FileDataSource(attachments[i])); String fileName = dataHandler.getName(); attachmentBodyPart.setDataHandler(dataHandler); // 显示指定文件名(防止文件名乱码) attachmentBodyPart.setFileName(MimeUtility.encodeText(fileName)); wrapPart.addBodyPart(attachmentBodyPart); } } } /** * 追加内嵌图片 * @author hf-hf * @date 2018/12/27 16:53 * @param images * @param multipart * @throws MessagingException */ private void addImages(File[] images, MimeMultipart multipart) throws MessagingException { if (null != images && images.length > 0) { for (int i = 0; i < images.length; i++) { MimeBodyPart imagePart = new MimeBodyPart(); DataHandler dataHandler = new DataHandler(new FileDataSource(images[i])); imagePart.setDataHandler(dataHandler); imagePart.setContentID(images[i].getName()); multipart.addBodyPart(imagePart); } } } /** * 追加发件人 * @author hf-hf * @date 2018/12/27 15:36 */ private void addRecipients(MimeMessage message, Message.RecipientType type, String recipients) throws MessagingException { String[] addresses = recipients.split(";"); for (int i = 0; i < addresses.length; i++) { message.addRecipients(type, addresses[i]); } } /** * 静态内部类 */ private static class MailUtilHolder { private static Environment env = ApplicationContextUtils.getBean("environment"); private static final MailUtil instance = new MailUtil(env.getProperty("mail.roundrobin.type")); } /** * 获取邮件发送工具 * @return */ public static final MailUtil getInstance() { return MailUtilHolder.instance; } }