Java 最好用QQ邮件发送工具类(亲测可用)
QQ邮件发送工具类
- QQ邮箱频率被限制发不了后自动切换邮箱
- 发送失败自动重发(可配置重发尝试次数RETRY_LIMIT)、可设置超时时间(TIMEOUT)
- 连续发送无需重新建立连接,能快速发送
- 支持单个、多个账号
- 支持SpringBoot异步线程池(去掉注释@Async(“AsyncThread”))
- 支持日志(修改log方法)
使用方法
- String nickname=“小明”;
- String mails[] = {“xxxxxxxxxxxx@qq.com”, “xxxxxxxxx@vip.qq.com”};
- String secrets[] = {“zmmhdjgdadaxhjgf”};//邮箱账号的密钥(在QQ邮箱账号设置找到并开启SMTP服务)多个密钥按照账号顺序依次写,密钥相同就写一个
- QQMail mail=new QQMail(nickname,mails,secrets);
- for(int i=0;i<100;i++){
mail.sendQQMail(“邮件标题”,“邮件内容(可HTML)”,“3301633914@qq.com”);
}
构造方法
/**
* QQMail
* @param nickname 邮件署名 字符串
* @param accounts 所有帐户 字符串数组
* @param secrets 所以账号对应密钥 字符串数组
*/
public QQMail(String nickname,String[]accounts,String[]secrets)
调用方法
/**
* 发送邮件方法
* @param title 邮件标题
* @param html_content 邮件内容(支持html,图片等内容可能会被拦截,需要用户点击查看才能看到,或者让用户设置信任这个邮箱)
* @param receiver 收件人邮箱
*/
public synchronized void sendQQMail(String title, String html_content, String receiver)
可调用方法
代码
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Date;
import java.util.Properties;
/**
* author:humorchen
* date:2020-08-30
* mail:humorchen@vip.qq.com
* csdn:https://blog.csdn.net/HumorChen99
*/
/**
* 使用方法
* String Mails[] = {"xxxxxxxxxxxx@qq.com", "xxxxxxxxx@vip.qq.com", "3301633914@qq.com"};//使用的账号,一个QQ可以搞几个账号,主要是腾讯会限制发送频率和数量
* String Secret[] = {"zmmhdjgdadaxhjgf"};邮箱账号的密钥(在QQ邮箱账号设置找到并开启SMTP服务)多个依次写,都是一个就写一个
* QQMail m=new QQMail("小明",Mails,Secret);
* for(int i=0;i<100;i++){
* m.sendQQMail("测试","123","3301633914@qq.com");
* }
*/
public class QQMail {
//发件人署名
private String NICKNAME ;
//可使用的邮箱账号
private String MAILACCOUNTS[] ;
//账号对应的密钥(多个账号密钥按照账号顺序依次写,全部账号密钥都是同一个就写一个)邮箱账号的密钥(在QQ邮箱账号设置找到并开启SMTP服务)
private String MAILSECRET[] ;
//发送失败后重新尝试发送的最大尝试次数
private final int RETRY_LIMIT =3;
//超时时间(单位毫秒)
private final int TIMEOUT =5000;
private int MAIL_INDEX = 0;
private Properties props;
private Session mailSession;
private MimeMessage message;
private Transport transport;
//日志(不管你用log4j Logger 注入还是啥做日志,改动下这个方法就行)
private final void log(String log){
System.out.println(log);
}
/**
* QQMail
* @param nickname 邮件署名 字符串
* @param accounts 所有帐户 字符串数组
* @param secrets 所以账号对应密钥 字符串数组
*/
public QQMail(String nickname,String[]accounts,String[]secrets){
NICKNAME=nickname;
MAILACCOUNTS=accounts;
MAILSECRET=secrets;
init();
}
/**
* 测试
* @param args
*/
public static void main(String[] args) {
String Mails[] = {"xxxxxxxxxxxx@qq.com", "xxxxxxxxx@vip.qq.com", "3301633914@qq.com"};
String Secret[] = {"zmmhdjgdadaxhjgf"};
QQMail m=new QQMail("小明",Mails,Secret);
for(int i=0;i<100;i++){
m.sendQQMail("测试","123","3301633914@qq.com");
}
}
public String getNICKNAME() {
return NICKNAME;
}
public void setNICKNAME(String NICKNAME) {
this.NICKNAME = NICKNAME;
}
public String[] getMAILACCOUNTS() {
return MAILACCOUNTS;
}
public void setMAILACCOUNTS(String[] MAILACCOUNTS) {
this.MAILACCOUNTS = MAILACCOUNTS;
}
public String[] getMAILSECRET() {
return MAILSECRET;
}
public void setMAILSECRET(String[] MAILSECRET) {
this.MAILSECRET = MAILSECRET;
}
public int getRETRY_LIMIT() {
return RETRY_LIMIT;
}
public int getTIMEOUT() {
return TIMEOUT;
}
/**
* 可以不去主动关闭,反正腾讯那边会主动关闭的
*/
public void close() {
try {
transport.close();
} catch (MessagingException e) {
e.printStackTrace();
}
}
private void init() {
log("QQ邮件服务初始化开始:账号" + MAILACCOUNTS[MAIL_INDEX]);
Date start = new Date();
try {
// 创建Properties 类用于记录邮箱的一些属性
props = new Properties();
// 表示SMTP发送邮件,必须进行身份验证
props.put("mail.smtp.auth", "true");
//此处填写SMTP服务器
props.put("mail.smtp.host", "smtp.qq.com");
//端口号,QQ邮箱给出了两个端口,但是另一个我一直使用不了,所以就给出这一个587
props.put("mail.smtp.port", "587");
// 此处填写你的账号
props.put("mail.user", MAILACCOUNTS[MAIL_INDEX]);
// 此处的密码就是前面说的16位STMP口令
props.put("mail.password", MAILSECRET.length==1?MAILSECRET[0]:MAILSECRET[MAIL_INDEX]);
//设置超时时间
props.put("mail.smtp.timeout",""+TIMEOUT);
// 构建授权信息,用于进行SMTP进行身份验证
Authenticator authenticator = new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
// 用户名、密码
String userName = props.getProperty("mail.user");
String password = props.getProperty("mail.password");
return new PasswordAuthentication(userName, password);
}
};
// 使用环境属性和授权信息,创建邮件会话
mailSession = Session.getInstance(props, authenticator);// 创建邮件消息
message = new MimeMessage(mailSession);
// 设置发件人
InternetAddress form = new InternetAddress(
props.getProperty("mail.user"), NICKNAME, "utf-8");
message.setFrom(form);
} catch (Exception e) {
e.printStackTrace();
}
Date end = new Date();
log("QQ邮件发送会话初始化成功,耗时" + ((end.getTime() - start.getTime())) + "毫秒");
}
/**
* 切换发件使用的邮箱
*/
private void changeMailAccount() {
if (MAIL_INDEX ==MAILACCOUNTS.length-1)
MAIL_INDEX =0;
else
MAIL_INDEX++;
init();//更换邮箱之后重新初始化
}
/**
* 与QQ邮箱服务器建立连接
* @throws Exception
*/
private void connect() throws Exception {
log("开始建立链接");
Date start = new Date();
transport = mailSession.getTransport(new InternetAddress(MAILACCOUNTS[MAIL_INDEX]));
transport.connect();
Date end = new Date();
log("链接已经建立,耗时:" + (end.getTime() - start.getTime()) + "毫秒");
}
/**
* 发送邮件时会阻塞,若使用SpringBoot异步处理
* 注册线程池代码
@Configuration
@EnableAsync // 启用异步任务
public class AsyncConfig {
// 声明一个线程池(并指定线程池的名字)
@Bean("AsyncThread")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程数5:线程池创建时候初始化的线程数
executor.setCorePoolSize(3);
//最大线程数5:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(5);
//缓冲队列500:用来缓冲执行任务的队列
executor.setQueueCapacity(200);
//允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(60);
//线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
executor.setThreadNamePrefix("【异步线程】线程");
executor.initialize();
return executor;
}
}
*/
/**
* 发送邮件方法
* @param title 邮件标题
* @param html_content 邮件内容(支持html,图片等内容可能会被拦截,需要用户点击查看才能看到,或者让用户设置信任这个邮箱)
* @param receiver 收件人邮箱
*/
//@Async("AsyncThread")//异步,使用线程池的时候用这个标明这个方法由线程池处理
public synchronized void sendQQMail(String title, String html_content, String receiver) {
try {
if (transport == null || !transport.isConnected())
connect();
// 设置收件人的邮箱
message.setRecipient(Message.RecipientType.TO, new InternetAddress(receiver));
// 设置邮件标题
message.setSubject(title, "utf-8");
// 设置邮件的内容体
message.setContent(html_content, "text/html;charset=UTF-8");
Date start = new Date();
try {
//保存修改
message.saveChanges();
//发送邮件
transport.sendMessage(message, new InternetAddress[]{new InternetAddress(receiver)});
log("使用邮箱:"+ MAILACCOUNTS[MAIL_INDEX] +"发送邮件给"+receiver+"\n标题:\n"+title+"\n内容:\n"+html_content);
} catch (Exception e) {
//由于被腾讯方面因超时被关闭连接属于正常情况
log("邮件发送失败,正在尝试和QQ邮件服务器重新建立链接");
boolean success=false;
for (int i = 1; i<= RETRY_LIMIT; i++)
try {
connect();
log("使用邮箱:"+ MAILACCOUNTS[MAIL_INDEX] +"成功建立链接");
success=true;
break;
} catch (Exception ee) {
changeMailAccount();
log("链接建立失败,切换到邮箱:"+ MAILACCOUNTS[MAIL_INDEX] +",进行第"+i+"次重试...");
}
if (success)
{
message.saveChanges();
transport.sendMessage(message, new InternetAddress[]{new InternetAddress(receiver)});
log("重建链接后使用邮箱:"+ MAILACCOUNTS[MAIL_INDEX] +"发送邮件给"+receiver+"\n标题:\n"+title+"\n内容:\n"+html_content);
}
else
{
log("链接多次尝试后无法建立,邮件发送失败!备注:收信人"+receiver);
return;
}
}
Date end = new Date();
log("成功发送邮件给" + receiver +"标题:"+title+ ",耗时" + ((end.getTime() - start.getTime())) + "毫秒");
} catch (Exception e) {
e.printStackTrace();
}
}
}
新版工具类代码
package cn.freeprogramming.util;
import cn.hutool.core.date.StopWatch;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.locks.ReentrantLock;
/**
* QQ邮件发送工具
*
* @Author:humorchen
* @Date 2022/1/3 20:50
*/
@Slf4j
public class QQMailUtil {
/**
* 超时时间
*/
private long TIMEOUT = 5000;
/**
* 重试次数
*/
private int RETRY_LIMIT = 10;
/**
* 使用第多少个账号(QQ每个账号限制频率了)
*/
private int accountIndex = 0;
/**
* 锁
*/
private ReentrantLock lock = new ReentrantLock(true);
/**
* 账号列表
*/
private List<QQMailAccount> accountList = new ArrayList<>();
/**
* 配置列表
*/
private List<Properties> propertiesList = new ArrayList<>();
/**
* 授权器
*/
private List<Authenticator> authenticatorList = new ArrayList<>();
/**
* 会话列表
*/
private List<Session> sessionList = new ArrayList<>();
/**
* 消息对象列表
*/
private List<MimeMessage> messageList = new ArrayList<>();
/**
* 发件人地址信息列表
*/
private List<InternetAddress> internetAddressArrayList = new ArrayList<>();
/**
* 发送器列表
*/
private List<Transport> transportList = new ArrayList<>();
/**
* 切换发件使用的邮箱下标
*/
private void changeUsingAccountIndex() {
if (accountIndex == accountList.size() - 1) {
accountIndex = 0;
} else {
accountIndex++;
}
}
/**
* 与QQ邮箱服务器建立连接
*
* @throws Exception
*/
private void connect() throws Exception {
QQMailAccount qqMailAccount = accountList.get(accountIndex);
Properties properties = propertiesList.get(accountIndex);
Authenticator authenticator = authenticatorList.get(accountIndex);
Session session = Session.getInstance(properties, authenticator);
MimeMessage message = new MimeMessage(session);
InternetAddress senderInternetAddress = internetAddressArrayList.get(accountIndex);
// 设置发件人
InternetAddress fromInternetAddress = new InternetAddress(
qqMailAccount.getAccount(), qqMailAccount.getNickname(), "utf-8");
message.setFrom(fromInternetAddress);
sessionList.set(accountIndex, session);
messageList.set(accountIndex, message);
Transport transport = session.getTransport(senderInternetAddress);
transport.connect();
log.info("isConnected {}", transport.isConnected());
if (accountIndex < transportList.size()) {
transportList.set(accountIndex, transport);
} else {
transportList.add(transport);
}
}
/**
* 发送邮件方法
* 由线程池处理
*
* @param title 邮件标题
* @param html_content 邮件内容(支持html,图片等内容可能会被拦截,需要用户点击查看才能看到,或者让用户设置信任这个邮箱)
* @param receiver 收件人邮箱
*/
@Async
public void sendQQMail(String title, String html_content, String receiver) {
StopWatch stopWatch = new StopWatch();
try {
lock.lock();
log.info("发送邮件给 {} ,标题:{} ,\n内容:{}", receiver, title, html_content);
stopWatch.start();
QQMailAccount qqMailAccount = accountList.get(accountIndex);
Transport transport = transportList.get(accountIndex);
MimeMessage message = messageList.get(accountIndex);
if (transport == null || !transport.isConnected()) {
connect();
}
stopWatch.stop();
log.info("连接花费 {}ms", stopWatch.getTotalTimeMillis());
stopWatch.start();
// 设置收件人的邮箱
message.setRecipient(Message.RecipientType.TO, new InternetAddress(receiver));
// 设置邮件标题
message.setSubject(title, "utf-8");
// 设置邮件的内容体
message.setContent(html_content, "text/html;charset=UTF-8");
try {
//保存修改
message.saveChanges();
//发送邮件
transport.sendMessage(message, new InternetAddress[]{new InternetAddress(receiver)});
stopWatch.stop();
log.info("使用邮箱:{} 发送成功,花费时间:{}ms", qqMailAccount.getAccount(), stopWatch.getTotalTimeMillis());
} catch (Exception e) {
//由于被腾讯方面因超时被关闭连接属于正常情况
log.info("邮件发送失败,正在尝试和QQ邮件服务器重新建立链接");
stopWatch.stop();
stopWatch.start();
boolean success = false;
for (int i = 1; i <= RETRY_LIMIT; i++) {
try {
connect();
log.info("使用邮箱:{} 成功建立链接", qqMailAccount.getAccount());
transport = transportList.get(accountIndex);
message = messageList.get(accountIndex);
success = true;
break;
} catch (Exception ee) {
changeUsingAccountIndex();
qqMailAccount = accountList.get(accountIndex);
log.info("链接建立失败,切换到邮箱:{} ,进行第 {} 次重试..." + qqMailAccount.getAccount(), i);
}
}
if (success) {
// 设置收件人的邮箱
message.setRecipient(Message.RecipientType.TO, new InternetAddress(receiver));
// 设置邮件标题
message.setSubject(title, "utf-8");
// 设置邮件的内容体
message.setContent(html_content, "text/html;charset=UTF-8");
message.saveChanges();
transport.sendMessage(message, new InternetAddress[]{new InternetAddress(receiver)});
stopWatch.stop();
log.info("重建连接后使用邮箱:{} 发送成功,耗费时间:{}ms", qqMailAccount.getAccount(), stopWatch.getTotalTimeMillis());
} else {
log.error("链接多次尝试后无法建立,邮件发送失败!");
return;
}
}
} catch (Exception e) {
log.error("sendQQMail", e);
} finally {
lock.unlock();
if (stopWatch.isRunning()) {
stopWatch.stop();
}
}
}
/**
* 添加账号
*/
public void addAccount(String account, String authorizationCode, String nickname) {
int oldAccountIndex = accountIndex;
boolean addFinished = false;
try {
lock.lock();
oldAccountIndex = accountIndex;
accountIndex = accountList.size();
QQMailAccount qqMailAccount = new QQMailAccount(account, authorizationCode, nickname);
Properties properties = createProperties(qqMailAccount);
Authenticator authenticator = createAuthenticator(qqMailAccount);
// 使用环境属性和授权信息,创建邮件会话
Session session = Session.getInstance(properties, authenticator);
MimeMessage message = new MimeMessage(session);
// 设置发件人
InternetAddress internetAddress = new InternetAddress(
account, nickname, "utf-8");
message.setFrom(internetAddress);
Transport transport = session.getTransport(new InternetAddress(qqMailAccount.getAccount()));
transport.connect();
accountList.add(qqMailAccount);
propertiesList.add(properties);
authenticatorList.add(authenticator);
sessionList.add(session);
transportList.add(transport);
messageList.add(message);
internetAddressArrayList.add(internetAddress);
addFinished = true;
} catch (Exception e) {
//移除已经加入的
if (addFinished) {
accountList.remove(accountIndex);
propertiesList.remove(accountIndex);
authenticatorList.remove(accountIndex);
sessionList.remove(accountIndex);
transportList.remove(accountIndex);
messageList.remove(accountIndex);
internetAddressArrayList.remove(accountIndex);
}
log.error("addAccount", e);
} finally {
accountIndex = oldAccountIndex;
lock.unlock();
}
}
/**
* 创建配置文件
*
* @param qqMailAccount
* @return
*/
private Properties createProperties(QQMailAccount qqMailAccount) {
// 创建Properties 类用于记录邮箱的一些属性
Properties properties = new Properties();
// 表示SMTP发送邮件,必须进行身份验证
properties.put("mail.smtp.auth", "true");
//此处填写SMTP服务器
properties.put("mail.smtp.host", "smtp.qq.com");
//端口号,QQ邮箱给出了两个端口,但是另一个我一直使用不了,所以就给出这一个587
properties.put("mail.smtp.port", "587");
// 此处填写你的账号
properties.put("mail.user", qqMailAccount.getAccount());
// 此处的密码就是前面说的16位STMP口令
properties.put("mail.password", qqMailAccount.getAuthorizationCode());
//设置超时时间
properties.put("mail.smtp.timeout", "" + TIMEOUT);
return properties;
}
/**
* 创建授权信息对象
*
* @param qqMailAccount
* @return
*/
private Authenticator createAuthenticator(QQMailAccount qqMailAccount) {
// 构建授权信息,用于进行SMTP进行身份验证
Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
// 用户名、密码
String userName = qqMailAccount.getAccount();
String password = qqMailAccount.getAuthorizationCode();
return new PasswordAuthentication(userName, password);
}
};
return authenticator;
}
private static class QQMailAccount {
/**
* 账号
*/
private String account;
/**
* 授权码
*/
private String authorizationCode;
/**
* 发送者昵称
*/
private String nickname;
public QQMailAccount(String account, String authorizationCode, String nickname) {
this.account = account;
this.authorizationCode = authorizationCode;
this.nickname = nickname;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getAuthorizationCode() {
return authorizationCode;
}
public void setAuthorizationCode(String authorizationCode) {
this.authorizationCode = authorizationCode;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
QQMailAccount that = (QQMailAccount) o;
return Objects.equals(account, that.account) && Objects.equals(authorizationCode, that.authorizationCode) && Objects.equals(nickname, that.nickname);
}
@Override
public int hashCode() {
return Objects.hash(account, authorizationCode, nickname);
}
}
}
配置工具类对象到容器
package cn.freeprogramming.config;
import cn.freeprogramming.util.QQMailUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* QQ邮件配置
*
* @Author:humorchen
* @Date 2022/1/3 21:54
*/
@Configuration
public class QQMailConfig {
//授权码,在QQ邮箱里设置
private String authorizationCode = "hkkm123kasbjbf";
private String nickname = "自由编程协会";
@Bean
public QQMailUtil qqMailUtil() {
QQMailUtil qqMailUtil = new QQMailUtil();
qqMailUtil.addAccount("freeprogramming@qq.com", authorizationCode, nickname);
qqMailUtil.addAccount("freeprogramming@foxmail.com", authorizationCode, nickname);
qqMailUtil.addAccount("357341307@qq.com", authorizationCode, nickname);
return qqMailUtil;
}
}
本文来自博客园,作者:HumorChen99,转载请注明原文链接:https://www.cnblogs.com/HumorChen/p/18039718
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~