一、设计思路
参考quartz任务调度框架,设计高并发邮件服务器。
1、将邮件发送封装成任务task线程。
2、创建任务调度中心,根据触发器来推送任务到任务执行线程池。
3、创建心跳触发器,来激活和触发任务调度。
二、代码
1、EmailTask (邮件发送任务类)
package com.zcb.monitor.business.utils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.*;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.Date;
/**
* @description:邮件发送任务类
* @author: Administrator
* @date: 2021/4/22
*/
public class EmailTask implements Runnable{
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**发件人账户名*/
String senderAccount;
/**密码或者授权码*/
String senderPassword;
/**发生时间*/
Date sendDate;
/**邮件发送连接session*/
Session session;
/**发件人地址必须和发件人账户名这里是一致,因为一般邮箱登录账户和地址是一致的*/
String senderAddress;
/**收件人地址 多个以英文逗号分割*/
String receiverTo;
/**抄送人 多个以英文逗号分割*/
String receiverCc;
/**内容*/
String content;
/**标题*/
String title ;
/**同类型日志,用来日志信息累计*/
String key;
public EmailTask( Session session,
String senderAddress,
String receiverTo,
String receiverCc,
String content, String title, String key, String senderAccount,String senderPassword,Date sendDate) {
this.session=session;
this.senderAddress=senderAddress;
this.receiverTo=receiverTo;
this.receiverCc=receiverCc;
this.content=content;
this.title=title;
this.senderAccount=senderAccount;
this.senderPassword=senderPassword;
this.sendDate=sendDate;
this.key=key;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getSenderAccount() {
return senderAccount;
}
public void setSenderAccount(String senderAccount) {
this.senderAccount = senderAccount;
}
public String getSenderPassword() {
return senderPassword;
}
public void setSenderPassword(String senderPassword) {
this.senderPassword = senderPassword;
}
public Date getSendDate() {
return sendDate;
}
public void setSendDate(Date sendDate) {
this.sendDate = sendDate;
}
public Session getSession() {
return session;
}
public void setSession(Session session) {
this.session = session;
}
public String getSenderAddress() {
return senderAddress;
}
public void setSenderAddress(String senderAddress) {
this.senderAddress = senderAddress;
}
public String getReceiverTo() {
return receiverTo;
}
public void setReceiverTo(String receiverTo) {
this.receiverTo = receiverTo;
}
public String getReceiverCc() {
return receiverCc;
}
public void setReceiverCc(String receiverCc) {
this.receiverCc = receiverCc;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Override
public String toString() {
return "EmailTask{" +
"senderAccount='" + senderAccount + '\'' +
", senderPassword='" + senderPassword + '\'' +
", sendDate=" + sendDate +
", session=" + session +
", senderAddress='" + senderAddress + '\'' +
", receiverTo='" + receiverTo + '\'' +
", receiverCc='" + receiverCc + '\'' +
", content='" + content + '\'' +
", title='" + title + '\'' +
", key='" + key + '\'' +
'}';
}
public void send() {
Transport transport =null;
try {
transport = session.getTransport();
//设置发件人的账户名和密码
transport.connect(senderAccount, senderPassword);
//设置发送信息
MimeMessage mimeMessage = crateEmailMessage();
if(mimeMessage!=null){
transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients());
logger.info(senderAccount+"给"+receiverTo+"发送邮件标题为:"+title+"内容为:"+content);
}
} catch (MessagingException mex) {
logger.error(new Date() + " send failed, exception: " + mex);
}finally {
if(transport!=null){
try {
transport.close();//关闭通道
} catch (MessagingException mex) {
logger.error(new Date() + " send failed, exception: " + mex);
}
}
}
}
@Override
public void run() {
logger.debug("进入邮箱发送");
send();
}
public MimeMessage crateEmailMessage(){
try{
if(session!=null&& StringUtils.isNotBlank(senderAddress)&&StringUtils.isNotBlank(receiverTo)&&StringUtils.isNotBlank(content)&&StringUtils.isNotBlank(title)){
//创建一封邮件的实例对象
MimeMessage msg = new MimeMessage(session);
//设置发件人地址
msg.setFrom(new InternetAddress(senderAddress));
/**
* 设置收件人地址(可以增加多个收件人、抄送、密送),即下面这一行代码书写多行
* MimeMessage.RecipientType.TO:发送
* MimeMessage.RecipientType.CC:抄送
* MimeMessage.RecipientType.BCC:密送
*/
if(receiverTo!=null&&receiverTo.contains(",")){
InternetAddress[] receiverToS = InternetAddress.parse(receiverTo);//以逗号分隔
msg.setRecipients(MimeMessage.RecipientType.TO,receiverToS);
}else{
msg.setRecipient(MimeMessage.RecipientType.TO,new InternetAddress(receiverTo));
}
if(receiverCc!=null){
if(receiverCc.contains(",")){
InternetAddress[] receiverCcS = InternetAddress.parse(receiverCc);//以逗号分隔
msg.setRecipients(MimeMessage.RecipientType.CC,receiverCcS);
}else{
msg.setRecipient(MimeMessage.RecipientType.CC,new InternetAddress(receiverCc));
}
}
//设置邮件主题
msg.setSubject(title,"UTF-8");
//设置邮件正文
/* if(content.length()>5120){*/
// 设置优先级(1:紧急 3:普通 5:低)
msg.setHeader("X-Priority", "1");
//一个Multipart对象包含一个或多个bodypart对象,组成邮件正文
MimeMultipart multipart = new MimeMultipart();
//创建文本节点
MimeBodyPart text = new MimeBodyPart();
text.setContent("<p>"+title+"</p><p>详情请查看附件:</p>","text/html;charset=UTF-8");
//创建附件节点 读取本地文件,并读取附件名称
MimeBodyPart file1 = new MimeBodyPart();
//创建临时文件
File temp = File.createTempFile("temp", ".txt");
temp.deleteOnExit();
BufferedWriter out = new BufferedWriter(new FileWriter(temp));
out.write(content);
//System.out.println("临时文件已创建:");
out.close();
DataHandler dataHandler2 = new DataHandler(new FileDataSource(temp));
file1.setDataHandler(dataHandler2);
file1.setFileName(MimeUtility.encodeText(dataHandler2.getName()));
multipart.addBodyPart(text);
multipart.addBodyPart(file1);
multipart.setSubType("mixed");//混合关系
msg.setContent(multipart);
/* }else{
msg.setContent(content, "text/html;charset=UTF-8");
}*/
//设置邮件的发送时间,默认立即发送
msg.setSentDate(new Date());
// 保//保存设置
msg.saveChanges();
return msg;
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
2、EmailManage (邮件调度和心跳触发器)
package com.zcb.monitor.business.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.*;
/**
* 邮件任务执行调度类
*
* @description:
* @author: Administrator
* @date: 2021/4/22
*/
public class EmailManage {
private static Logger logger = LoggerFactory.getLogger(EmailManage.class);
private static HashMap<String, EmailTask> map = new HashMap<String, EmailTask>();
private static BlockingQueue<Runnable> workQueue;//任务队列
private static ExecutorService es;//线程池的接口
private static EmailManage emailManage =new EmailManage();
private static Thread thread;
private EmailManage() {
workQueue = new LinkedBlockingQueue<Runnable>();//构造无界的任务队列,资源足够,理论可以支持无线个任务
es = new ThreadPoolExecutor(2, 4, //2 core; 4 max
30, TimeUnit.SECONDS, workQueue, //30s keep alive
new ThreadPoolExecutor.CallerRunsPolicy()); //任务失败重试
thread = startBeating();//启动任务触发线程
}
/* * 发送邮件
*
*/
public static boolean send(String key, EmailTask task) {
try {
if (emailManage == null || es == null || workQueue == null) {
emailManage = new EmailManage();
}
if (thread == null || !thread.isAlive()) {
thread = emailManage.startBeating(); //心跳线程死亡 从新开启
logger.debug("心跳线程死亡 从新开启心跳线程");
}
if (map == null) {
map = new HashMap<String, EmailTask>();
// return false;//发送邮件失败
}
if (key == null || task == null) {
return false;//发送邮件失败
}
if (map.containsKey(key)) {
EmailTask emailTask = map.get(key);
task.setContent(emailTask.getContent() + "\n\n\n" + task.getContent());
}
map.put(key, task);
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
public static HashMap<String, EmailTask> getEmailTaskMap() {
return map;
}
public void close() {// 关闭
es.shutdown();
}
public Thread startBeating() {
Runnable r = new Runnable() {
@Override
public void run() {
while (true) {
try {
if (map != null && map.size() > 0) {
for (Iterator<String> iterator = map.keySet().iterator(); iterator.hasNext(); ) {
String key = iterator.next();
EmailTask emailTask = map.get(key);
if (emailTask != null) {
//if (true) {
if (new Date().getTime() > emailTask.getSendDate().getTime()) {
logger.info(emailTask.toString() + new Date().toString() + "任务放入线程池");
es.execute(emailTask);//将任务放入线程池
iterator.remove();
break;
}
}
}
}
Thread.sleep(1000 * 30);//30秒循环一次
} catch (Exception e) {
e.printStackTrace();//打印异常信息,循环继续执行
}
}
}
};
Thread emailTrigger = new Thread(r);
emailTrigger.start();
return emailTrigger;
}
}
3、EmailMessageProduct (emailMessage消息构建器)
package com.zcb.monitor.business.utils;
import com.zcb.monitor.business.data.EmailLogVo;
import com.zcb.monitor.data.entity.EmailMessageRule;
import com.zcb.monitor.data.entity.EmailReceiver;
import com.zcb.monitor.data.entity.EmailSender;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.mail.Session;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
@Component
public class EmailMessageProduct {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
EmailCache emailCache;
public boolean sendEmail(EmailLogVo baseLog) {
/** 1、加载数据库配置信息
* 2、验证是否是有效消息
* 3、封装消息类EmailTask
* 4、发送
*
*/
try {
if (baseLog != null) {
List<EmailReceiver> emailMessageRule = emailCache.getEmailMessageRule();
EmailSender emailSender = emailCache.getEmailSender();
Session emailSession = emailCache.getEmailSession();
if (emailMessageRule != null && emailMessageRule.size() > 0) {
// emailMessageRule.stream().forEach((emailReceiver)->{
for (EmailReceiver emailReceiver : emailMessageRule) {
if (emailReceiver != null && StringUtils.isNotBlank(emailReceiver.getCusNumber())
&& StringUtils.isNotBlank(emailReceiver.getSystemName()) && StringUtils.isNotBlank(emailReceiver.getSystemType())) {
if (emailReceiver.getCusNumber().equals(baseLog.getCusNum())
&& emailReceiver.getSystemName().equals(baseLog.getSysName()) //系统名称
&& emailReceiver.getSystemType().equals(baseLog.getEnvDef())) {//套别
List<EmailMessageRule> list = emailReceiver.getList();
if (list != null && list.size() > 0) {
// list.stream().forEach((temp)->{
for (EmailMessageRule temp : list) {
if (temp != null && StringUtils.isNotBlank(temp.getLogKey())) {
if (temp.getLogKey().equals(baseLog.getMessagetype())) {
if ("1".equals(temp.getIsHoliday()) && emailCache.getIsHoliday() == 1) {//节假日发送
Date aloudTime = getAloudTime(temp.getTimeSection());
if (aloudTime != null) {
EmailTask emailTask = crateEmailTask(
emailSession,
emailSender.getSenderAddress(),
emailReceiver.getReceiverTo(),
emailReceiver.getReceiverCc(),
baseLog.getMessage(),
"套别为:" + emailReceiver.getSystemType() + ",机构号为:" + emailReceiver.getCusNumber()
+ ",系统名称为:" + emailReceiver.getSystemName() +
",日志码为:" + temp.getLogKey() + ",日志信息为:" + temp.getLogValue(),
emailReceiver.getKey() + temp.getLogKey(),
emailSender.getSenderAccount(),
emailSender.getSenderPassword(),
aloudTime);
if (emailTask != null) {
return EmailManage.send(emailReceiver.getKey() + temp.getLogKey(), emailTask);
}
}
} else if ("2".equals(temp.getIsHoliday()) && emailCache.getIsHoliday() == 0) {//工作发送
Date aloudTime = getAloudTime(temp.getTimeSection());
if (aloudTime != null) {
EmailTask emailTask = crateEmailTask(
emailSession,
emailSender.getSenderAddress(),
emailReceiver.getReceiverTo(),
emailReceiver.getReceiverCc(),
"<p>" + baseLog.getMessage() + "</p><p></p>",
"套别为:" + emailReceiver.getSystemType() + ",机构号为:" + emailReceiver.getCusNumber()
+ ",系统名称为:" + emailReceiver.getSystemName() +
",日志码为:" + temp.getLogKey() + ",日志信息为:" + temp.getLogValue(),
emailReceiver.getKey() + temp.getLogKey(),
emailSender.getSenderAccount(),
emailSender.getSenderPassword(),
aloudTime);
if (emailTask != null) {
return EmailManage.send(emailReceiver.getKey() + temp.getLogKey(), emailTask);
}
}
} else if ("3".equals(temp.getIsHoliday())) {//all
Date aloudTime = getAloudTime(temp.getTimeSection());
if (aloudTime != null) {
EmailTask emailTask = crateEmailTask(
emailSession,
emailSender.getSenderAddress(),
emailReceiver.getReceiverTo(),
emailReceiver.getReceiverCc(),
baseLog.getMessage(),
"套别为:" + emailReceiver.getSystemType() + ",机构号为:" + emailReceiver.getCusNumber()
+ ",系统名称为:" + emailReceiver.getSystemName() +
",日志码为:" + temp.getLogKey() + ",日志信息为:" + temp.getLogValue(),
emailReceiver.getKey() + temp.getLogKey(),
emailSender.getSenderAccount(),
emailSender.getSenderPassword(),
aloudTime);
if (emailTask != null) {
return EmailManage.send(emailReceiver.getKey() + temp.getLogKey(), emailTask);
}
}
}
}
}
}
}
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
public Date getAloudTime(String timeSection) {//eg:09:00:00-10:00:00;11:00:00-12:00:00
try {
if (StringUtils.isNotBlank(timeSection)) {
if (timeSection.contains(";")) {
String[] split = timeSection.split(";");
if (split != null && split.length > 0) {
for (String split1 : split) {
if (split1 != null && split1.contains("-")) {
String[] splits = split1.split("-");
SimpleDateFormat sim1 = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat sim2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
String format1 = sim1.format(date);
if (splits != null && splits.length == 2) {
String stardate = format1 + " " + splits[0];
String enddate = format1 + " " + splits[1];
Date date1 = sim2.parse(stardate);
Date date2 = sim2.parse(enddate);
if (date1.getTime() <= date.getTime() && date.getTime() <= date2.getTime()) {
return date2;
}
}
}
}
}
} else {
if (timeSection != null && timeSection.contains("-")) {
String[] splits = timeSection.split("-");
SimpleDateFormat sim1 = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat sim2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
String format1 = sim1.format(date);
if (splits != null && splits.length == 2) {
String stardate = format1 + " " + splits[0];
String enddate = format1 + " " + splits[1];
Date date1 = sim2.parse(stardate);
Date date2 = sim2.parse(enddate);
if (date1.getTime() <= date.getTime() && date.getTime() <= date2.getTime()) {
System.out.println("通过" + stardate + " " + enddate);
return date2;
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public EmailTask crateEmailTask(Session session,
String senderAddress,
String receiverTo,
String receiverCc,
String content, String title, String key, String senderAccount, String senderPassword, Date sendDate) {
try {
if (session != null && StringUtils.isNotBlank(senderAddress)
&& StringUtils.isNotBlank(receiverTo) && StringUtils.isNotBlank(content) &&
StringUtils.isNotBlank(title) && StringUtils.isNotBlank(senderAccount) && StringUtils.isNotBlank(senderPassword) && sendDate != null) {
return new EmailTask(session,
senderAddress,
receiverTo,
receiverCc,
content, title, key, senderAccount, senderPassword, sendDate);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
4、EmailCache (消息配置缓存)
package com.zcb.monitor.business.utils;
import com.zcb.monitor.data.entity.EmailMessageRule;
import com.zcb.monitor.data.entity.EmailReceiver;
import com.zcb.monitor.data.entity.EmailSender;
import com.zcb.monitor.data.mapper.EmailMessageRuleMapper;
import com.zcb.monitor.data.mapper.EmailReceiverMapper;
import com.zcb.monitor.data.mapper.EmailSenderMapper;
import com.sun.mail.util.MailSSLSocketFactory;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import javax.mail.Session;
import java.util.Date;
import java.util.List;
import java.util.Properties;
@Service
public class EmailCache {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
EmailMessageRuleMapper emailMessageRuleMapper;
@Autowired
EmailReceiverMapper emailReceiverMapper;
@Autowired
EmailSenderMapper emailSenderMapper;
@Cacheable("isHoliday")
public int getIsHoliday(){
return emailSenderMapper.getIsHoliday(new Date());
}
@Cacheable("emailSender")
public EmailSender getEmailSender(){
logger.info("-------------------------修改emailSender------------");
return emailSenderMapper.getEmailConfig();
}
@Cacheable("emailMessageRule")
public List<EmailReceiver> getEmailMessageRule(){
List<EmailReceiver> list = emailReceiverMapper.getList();
if(list!=null&&list.size()>0){
list.stream().forEach((emailReceiver) -> {
if(emailReceiver!=null&& StringUtils.isNotBlank(emailReceiver.getReceiverId())){
List<EmailMessageRule> list1 = emailMessageRuleMapper.getList(emailReceiver.getReceiverId());
if(list1!=null&&list1.size()>0){
emailReceiver.setList(list1);
}
}
});
}
return list;
}
@Cacheable("emailSession")
public Session getEmailSession(){
Session session =null;
EmailSender emailConfig = emailSenderMapper.getEmailConfig();
if(emailConfig==null){
logger.error("查询不到配置信息");
return session;
}
if(StringUtils.isBlank(emailConfig.getSenderAccount())){
logger.error("发送信息账户配置为空");
return session;
}
if(StringUtils.isBlank(emailConfig.getSenderAddress())){
logger.error("发送地址配置为空");
return session;
}
if(StringUtils.isBlank(emailConfig.getSenderName())){
logger.error("发送人名称配置为空");
return session;
}
if(StringUtils.isBlank(emailConfig.getSenderPassword())){
logger.error("邮箱密码配置为空");
return session;
}
if(StringUtils.isBlank(emailConfig.getSmtpHost())){
logger.error("SMTP服务器地址配置为空");
return session;
}
if(StringUtils.isBlank(emailConfig.getSmtpPort())){
logger.error("邮件服务器的端口号配置为空");
return session;
}
MailSSLSocketFactory sf = null;// SSL加密
try {
//1、连接邮件服务器的参数配置
Properties props = new Properties();
sf = new MailSSLSocketFactory();
sf.setTrustAllHosts(true); // 设置信任所有的主机
props.put("mail.smtp.ssl.enable", "true");//设置开启ssl密
props.put("mail.smtp.ssl.socketFactory", sf);//设置加密方式
//设置用户的认证方式
props.setProperty("mail.smtp.auth", "true");
//设置传输协议
props.setProperty("mail.transport.protocol", "smtp");
//设置发件人的SMTP服务器地址
props.setProperty("mail.smtp.host", emailConfig.getSmtpHost());
//设置发送的端口号
props.put("mail.smtp.port", emailConfig.getSmtpPort());
//2、创建定义整个应用程序所需的环境信息的 Session 对象
session = Session.getInstance(props);
//设置调试信息在控制台打印出来
// session.setDebug(true);
} catch (Exception e) {
e.printStackTrace();
}
return session;
}
}
5、修改前端重新加载缓存
package com.zcb.monitor.service;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import java.util.Collection;
/**
* @description:清除邮件信息缓存
* @author: Administrator
* @date: 2021/4/28
*/
@Slf4j
public class MonitorRefreshEmailCacheService extends EnvironmentalRequestResponseService implements IMonitorRefreshEmailCacheService {
CacheManager cacheManager;
@Override
public boolean refreshAll() {
if (cacheManager != null) {
Collection<String> cacheNames = cacheManager.getCacheNames();
if (cacheNames != null && cacheNames.size() > 0) {
for (String key : cacheNames) {
if (StringUtils.isNotBlank(key)) {
Cache cache = cacheManager.getCache(key);
if (cache != null) {
cache.clear();
}
}
}
return true;
}
}
return false;
}
@Override
public boolean refreshByCacheKey(String key) {
if (cacheManager != null) {
if (StringUtils.isNotBlank(key)) {
Cache cache = cacheManager.getCache(key);
if (cache != null) {
cache.clear();
return true;
}
}
}
return false;
}
public CacheManager getCacheManager() {
return cacheManager;
}
public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
}
6、邮件发送入口
@Autowired
protected EmailMessageProduct emailMessageProduct;
boolean b = emailMessageProduct.sendEmail(emailLogVo);
if (b) {
log.info("邮件发送数据[{}]", JSON.toJSONString(emailLogVo));
}