Java - 解决在一个线程(或进程)中切换多个账号发送邮件的问题

293 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

近期做一个项目中遇到需要使用多个邮箱以负载均衡的方式发邮件(单线程多账号发邮件),遇到一些问题在此做一个记录,主要解决 “501 mail from address must be same as authorization user” 这个错误。

场景

有A、B、C三个邮件账户,由于发信量限制原因,程序需要做到在每次向用户发邮件时,交替使用这三个邮箱,实际就是一种简单的负载均衡轮询。我将A、B、C三个账户设计为互为主备的策略,每次发信失败后,会自动切换为另外两个邮箱发信,都失败时,才会放弃发信,同时保存失败记录。

遇到的问题

切换到单个邮箱发信时,可以正常发送。但是,我将另外两个邮件账户的密码故意设置错误,然后再发信,却提示以下错误:

com.sun.mail.smtp.SMTPSendFailedException: 501 mail from address must be same as authorization user

解决办法

通过搜索资料,发现这是邮件会话设置的问题,是由于获取 Session 时的账号和 Message 中设置的邮箱地址 setFrom 不一致引起的,因此我们需要在获取邮件会话时,不能使用默认的 Session.getDefaultInstance,而是要使用 Session.getInstance,具体请参考以下代码片段:

/**
 * 发送邮件
 */
public void send() {
    // 设置邮件属性
    Properties prop = new Properties();
    prop.setProperty("mail.transport.protocol", PROTOCOL);
    prop.setProperty("mail.smtp.host", HOST);
    prop.setProperty("mail.smtp.port", PORT);
    prop.setProperty("mail.smtp.auth", "true");
    MailSSLSocketFactory sslSocketFactory = null;
    try {
        sslSocketFactory = new MailSSLSocketFactory();
        sslSocketFactory.setTrustAllHosts(true);
    } catch (GeneralSecurityException e1) {
        e1.printStackTrace();
    }
    if (sslSocketFactory == null) {
        System.err.println("开启 MailSSLSocketFactory 失败");
    } else {
        prop.put("mail.smtp.ssl.enable", "true");
        prop.put("mail.smtp.ssl.socketFactory", sslSocketFactory);
        // 创建邮件会话(注意:如果要在一个进程中切换多个邮箱发送,请不要使用 Session.getDefaultInstance)
        Session session = Session.getDefaultInstance(prop, new MyAuthenticator(ACCOUNT, PASSWORD));
        // 开启调试模式(生产环境中请不要开启此项)
        session.setDebug(true);
        try {
            MimeMessage mimeMessage = new MimeMessage(session);
            // 设置发件人别名(如果未设置别名就默认为发件人邮箱)
            if (fromAliasName != null && !fromAliasName.trim().isEmpty()) {
                mimeMessage.setFrom(new InternetAddress(ACCOUNT, fromAliasName));
            }
            // 设置主题和收件人、发信时间等信息
            mimeMessage.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
            mimeMessage.setSubject(subject);
            mimeMessage.setSentDate(new Date());
            // 如果有附件信息,则添加附件
            if (!attachFileList.isEmpty()) {
                Multipart multipart = new MimeMultipart();
                MimeBodyPart body = new MimeBodyPart();
                body.setContent(content, "text/html; charset=UTF-8");
                multipart.addBodyPart(body);
                // 添加所有附件(添加时判断文件是否存在)
                for (String filePath : attachFileList) {
                    if (Files.exists(Paths.get(filePath))) {
                        MimeBodyPart tempBodyPart = new MimeBodyPart();
                        tempBodyPart.attachFile(filePath);
                        multipart.addBodyPart(tempBodyPart);
                    }
                }
                mimeMessage.setContent(multipart);
            } else {
                mimeMessage.setText(content);
            }
            // 开始发信
            mimeMessage.saveChanges();
            Transport.send(mimeMessage);
        } catch (MessagingException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

其他备注

使用多个邮箱发信时,不要在发信代码中 try catch 任何异常,而是将所有的异常抛出,让上一层调用者处理,这样才能够实现多账户交替发信,出错时切换其他账号发信的功能。