@Async 异步调用,方法不跑完可以接着跑别的方法,本质通过另起线程池
RateLimiter sendRateLimiter = RateLimiter.create(1.33) 控制每秒最多1.33次,即每分钟80次,可用来限制自身调用其他接口TPS
sendRateLimiter.acquire() //获取该许可证。一旦获取到许可证,不需要再释放许可证
//发送内容放入queue中,启动任务后单跑一个发送线程
@Service("mailService")
public class MailServiceImpl implements MailService {
private static final Logger LOG = LoggerFactory.getLogger(MailServiceImpl.class);
@Resource
private MailProperties mailProperties;
private Thread sendThread;
private BlockingQueue<MailDTO> sendQueue;
@PostConstruct
public void startThread() {
initSendQueue();
sendThread = new Thread(new SendConsumer());
sendThread.start();
LOG.info(String.format("邮件发送线程已启动[%s]", sendThread.getName()));
}
// 追加到发送队列队尾超时时间(单位:秒)
private final long OFFER_SEND_QUEUE_TIMEOUT = 6;
public void offerSendQueue(MailDTO mailDTO) {
initSendQueue();
try {
// 收件人超过30位,拆成多封。
if (mailDTO.getTo().length <= 30) {
boolean insertFlag = sendQueue.offer(mailDTO, OFFER_SEND_QUEUE_TIMEOUT, TimeUnit.SECONDS);
LOG.info(String.format("[%s]插入邮件发送队列[%s]!", JSONObject.toJSONString(mailDTO), insertFlag));
} else {
List<MailDTO> mailList = splitMail(mailDTO);
for (MailDTO tempMail : mailList) {
boolean insertFlag = sendQueue.offer(tempMail, OFFER_SEND_QUEUE_TIMEOUT, TimeUnit.SECONDS);
LOG.info(String.format("[%s]插入邮件发送队列[%s]!", JSONObject.toJSONString(tempMail), insertFlag));
}
}
} catch (InterruptedException e) {
LOG.error("发送邮件插入阻塞队列发生中断异常:", e);
}
}
/**
* 初始化邮件发送阻塞队列
*/
private void initSendQueue() {
if (null != sendQueue) {
return;
}
synchronized (this) {
if (null == sendQueue) {
sendQueue = new LinkedBlockingQueue<>();
}
}
}
/**
* 从阻塞队列中获取队首元素,发送到邮件中继MRU(Mail Relay Unit)
*/
class SendConsumer implements Runnable {
private RateLimiter sendRateLimiter = RateLimiter.create(1.33);
@Override
public void run() {
try {
//如果时长要求不高也可改为定时,下面这个写法如果在FULL GC的时候会发送中断信号
while (!Thread.interrupted()) {
// 控制发送速率,当前设置为每分钟接近80封。
sendRateLimiter.acquire();
// 从发送队列中获取待发送邮件。
MailDTO mailDTO = sendQueue.take();
LOG.info(String.format("邮件发送线程[%s]:[%s]开始发送!", Thread.currentThread().getName(),
JSONObject.toJSONString(mailDTO)));
sendToMRU(mailDTO);
}
LOG.warn(String.format("邮件发送线程已停止[%s]", Thread.currentThread().getName()));
} catch (InterruptedException e) {
LOG.error("发送邮件阻塞队列消费者发生中断异常:", e);
}
}
}
private void sendToMRU(MailDTO mailDTO) {
String defaultFrom = mailProperties.getDefaultMailFrom();
String smtpHost = mailProperties.getSmtpHost();
String smtpPort = mailProperties.getSmtpPort();
LOG.info("邮件发送线程defaultFrom=" + defaultFrom + "smtpHost" + smtpHost + "smtpPort" + smtpPort);
// 如果发件人为null,则设置为默认发件人
String from = mailDTO.getFrom();
if (null == from || "".equals(from)) {
from = defaultFrom;
mailDTO.setFrom(defaultFrom);
}
try {
Properties props = getProperties();
Session mailSession = Session.getDefaultInstance(props);
MimeMessage msg = new MimeMessage(mailSession);
// 发件人
msg.addFrom(InternetAddress.parse(mailDTO.getFrom()));
// 收件人列表
InternetAddress[] addresses = parseAddresses(mailDTO.getTo());
msg.setRecipients(Message.RecipientType.TO, addresses);
// 抄送人邮箱
if (null != mailDTO.getCc() && mailDTO.getCc().length > 0) {
addresses = parseAddresses(mailDTO.getCc());
if (addresses.length > 0) {
msg.setRecipients(Message.RecipientType.CC, addresses);
}
}
// 暗送人邮箱
if (mailDTO.getBcc() != null && mailDTO.getBcc().length > 0) {
addresses = parseAddresses(mailDTO.getBcc());
if (addresses.length > 0) {
msg.setRecipients(Message.RecipientType.BCC, addresses);
}
}
// 发送时间
if (msg.getSentDate() == null) {
msg.setSentDate(new Date());
}
String encoding = "UTF-8";
// 主题
msg.setSubject(mailDTO.getSubject(), encoding);
// 内容
BodyPart bpContent = new MimeBodyPart();
bpContent.setContent(mailDTO.getContent(), "text/html; charset=" + encoding);
Multipart mp = new MimeMultipart();
mp.addBodyPart(bpContent);
msg.setContent(mp);
Transport.send(msg);
} catch (Exception e) {
LOG.error("邮件发送失败,请与管理员联系!", e);
}
}
private Properties getProperties(){
Properties props = new Properties();
String smtpHost = mailProperties.getSmtpHost();
String smtpPort = mailProperties.getSmtpPort();
props.put("mail.transport.protocol", "smtp"); // 发件协议
props.put("mail.smtp.host", smtpHost); // 邮件服务器地址
props.put("mail.smtp.port", smtpPort); // 端口
return props;
}
/**
* 字符串数组转换为 InternetAddress型数组
*/
private InternetAddress[] parseAddresses(String[] to) throws AddressException {
List<InternetAddress> list = new ArrayList<>();
if (null == to) {
return (InternetAddress[]) list.toArray(new InternetAddress[list.size()]);
}
for (int i = 0; i < to.length; i++) {
String address = to[i];
if (null != address && !"".equals(address)) {
InternetAddress[] addresses = InternetAddress.parse(address);
list.addAll(Arrays.asList(addresses));
}
}
return (InternetAddress[]) list.toArray(new InternetAddress[list.size()]);
}
private List<MailDTO> splitMail(MailDTO mailDTO) {
String[] to = mailDTO.getTo();
List<MailDTO> mailList = new ArrayList<>();
int start = 0;
int end = 30;
while (end <= to.length) {
String[] tempTo = Arrays.copyOfRange(to, start, end);
MailDTO tempMail = copyNotToField(mailDTO);
tempMail.setTo(tempTo);
mailList.add(tempMail);
start += 30;
end += 30;
}
if (start < to.length && end > to.length) {
MailDTO tempMail = copyNotToField(mailDTO);
String[] tempTo = Arrays.copyOfRange(to, start, to.length);
tempMail.setTo(tempTo);
mailList.add(tempMail);
}
return mailList;
}
private MailDTO copyNotToField(MailDTO origin) {
MailDTO targetMail = new MailDTO();
targetMail.setSubject(origin.getSubject());
targetMail.setContent(origin.getContent());
targetMail.setFrom(origin.getFrom());
targetMail.setSentDate(origin.getSentDate());
targetMail.setBcc(origin.getBcc());
targetMail.setCc(origin.getCc());
return targetMail;
}
}