针对QQ邮箱发邮件限制的解决方案

由于QQ邮箱对于SMTP服务发送邮件做了限制,每分钟发送40封之后会被限制不能再发送,对于这样的限制又需要发送大量邮件的时候的解决方案如下

使用多个邮箱轮换使用进行发送

1、将使用的邮箱存储在一个统一的字符串变量中,将所有可使用的邮箱存储在一个字符串数组中(我的三个邮箱授权码相同,如果授权码不同,则建立一个授权码数组,和邮箱切换的解决方案同理)

全局变量(发邮件使用的邮箱)

private static String FPAMail="freeprogramming@qq.com";

可使用的邮箱数组

private static String FPAMailArray[]={"freeprogramming@qq.com","humorchen@vip.qq.com","3301633914@qq.com"};

2、将建立连接的代码封装到一个函数,将连接对象变为成员变量,全局化,即每次调用同一个变量,而变量的对象可能不相同(会变化)

连接相关对象变为全局变量

    private static Properties props;
    private static Session mailSession;
    private static MimeMessage message;
    private static Transport transport;

建立连接的函数

 private void init()
    {
        System.out.println("QQ邮件服务初始化开始:账号"+FPAMail);
        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",FPAMail );
            // 此处的密码就是前面说的16位STMP口令
            props.put("mail.password", FPAMailPwd);

            // 构建授权信息,用于进行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();
        System.out.println("QQ邮件发送会话初始化成功,耗时"+((end.getTime()-start.getTime()))+"毫秒");

    }

(不懂这几个对象是干嘛的百度)

3、设置每发送20封邮件切换一次邮箱,封装成函数

函数如下:

private void switchMail()
{
    int i=0;
    for (;i<FPAMailArray.length;i++)
        if (FPAMail.equals(FPAMailArray[i]))
            break;
    if (i+1==FPAMailArray.length)
        i=0;
    else
        i++;
    FPAMail=FPAMailArray[i];
    init();
}

4、每次发送邮件的时候做判断(i%20==0)

public void sendToAllMember(String title,String html_content)
    {
        System.out.println("发送邮件给所有会员");
        int i=1;
        for (String mail: MemberQQMailData.mails)
        {
            System.out.println("正在处理第"+(i++)+"个"+"剩余"+(MemberQQMailData.mails.length-i+1)+"个,正在发送给:"+mail);
            sendQQMail(title,MailContentGenerator.QQMailNotice(title,html_content),mail);
            if (i%20==0)
                switchMail();
        }
    }

(MemberQQMailData.mails是一个字符串数组,存有所有会员的QQ邮箱)

效果图:每到第20封邮件就换一个邮箱进行发送,完美解决QQ邮箱发送限制问题

1.png

新版解决方案代码工具类

新版工具类代码
 

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;
    }
}

posted @ 2019-11-14 00:53  HumorChen99  阅读(41)  评论(0编辑  收藏  举报  来源