解决spring boot JavaMailSender部分收件人错误导致发送失败的问题

Posted lwli

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了解决spring boot JavaMailSender部分收件人错误导致发送失败的问题相关的知识,希望对你有一定的参考价值。

  使用spring boot通常使用spring-boot-starter-mail进行邮件的发送。当进行邮件群发的话,如果一个收件人的地址错误,会导致所有邮件都发送失败。因此我们需要在邮件发送失败的时候把错误的收件人移除,重新发送。

  当邮件发送失败的时候会抛出MailSendException,异常信息中包含错误的收件人信息。

  主要代码如下:

private void sendMail(List<String> mailList, MimeMessageHelper message){
    try{
        this.mailSender.send(message.getMimeMessage());
    }catch (MailSendException e){
        Set<String> tmpInvalidMails = getInvalidAddress(e);
        // 非无效收件人导致的异常,暂不处理
        if (tmpInvalidMails.isEmpty()){
            logger.error(e.getMessage());
            return;
        }
        mailList.removeAll(tmpInvalidMails);
        if(mailList.isEmpty()){
            logger.error("邮件发送失败,无收件人" + e.getMessage());
            return;
        }
        message.setTo(mailList.toArray(new String[0]));
        sendMail(mailList, message)
    }
}

  捕获邮件发送失败的异常,首先判断是否是收件人无效导致的异常。从异常中解析无效收件人,收件人例表中移除无效的收件人,重新发送邮件。

  如何从从异常中获取无效收件人,首先看下JavaMailSenderImpl这个类的doSend方法

技术分享图片
 1 protected void doSend(MimeMessage[] mimeMessages, @Nullable Object[] originalMessages) throws MailException {
 2     Map<Object, Exception> failedMessages = new LinkedHashMap<>();
 3     Transport transport = null;
 4     try {
 5         for (int i = 0; i < mimeMessages.length; i++) {
 6             ...
 7             // Send message via current transport...
 8             MimeMessage mimeMessage = mimeMessages[i];
 9             try {
10                 ...
11             }
12             catch (Exception ex) {
13                 Object original = (originalMessages != null ? originalMessages[i] : mimeMessage);
14                 failedMessages.put(original, ex);
15             }
16         }
17     }
18     finally {
19         try {
20             if (transport != null) {
21                 transport.close();
22             }
23         }
24         catch (Exception ex) {
25             if (!failedMessages.isEmpty()) {
26                 throw new MailSendException("Failed to close server connection after message failures", ex,
27                         failedMessages);
28             }
29             else {
30                 throw new MailSendException("Failed to close server connection after message sending", ex);
31             }
32         }
33     }
34 
35     if (!failedMessages.isEmpty()) {
36         throw new MailSendException(failedMessages);
37     }
38 }
View Code

  当邮件发送过程中遇到异常会保存到failedMessages中,我们需要从中解析收件人无效导致的异常。

  接着继续看源码SMTPTransport的rcptTo方法,会去校验每个收件人,通过向服务器发送RCPT TO:<地址>,根据响应码来判断收件人是否有效。

技术分享图片
protected void rcptTo() throws MessagingException {
    List<InternetAddress> valid = new ArrayList();
    List<InternetAddress> validUnsent = new ArrayList();
    List<InternetAddress> invalid = new ArrayList();
    ...
    int k;
    for(k = 0; k < this.addresses.length; ++k) {
        sfex = null;
        InternetAddress ia = (InternetAddress)this.addresses[k];
        String cmd = "RCPT TO:" + this.normalizeAddress(ia.getAddress());
        if (dsn) {
            cmd = cmd + " NOTIFY=" + notify;
        }

        this.sendCommand(cmd);
        int retCode = this.readServerResponse();
        switch(retCode) {
        case 250:
        case 251:
            valid.add(ia);
            ...
            break;
        case 450:
        case 451:
        case 452:
        case 552:
            ...
            validUnsent.add(ia);
            ...
            break;
        case 501:
        case 503:
        case 550:
        case 551:
        case 553:
            ...
            invalid.add(ia);
            ...
            break;
        default:
            if (retCode >= 400 && retCode <= 499) {
                validUnsent.add(ia);
            } else {
                ...
                invalid.add(ia);
            }
            ...
        }
    }

    if (sendPartial && valid.size() == 0) {
        sendFailed = true;
    }

    int lrc;
    if (sendFailed) {
        this.invalidAddr = new Address[invalid.size()];
        invalid.toArray(this.invalidAddr);
        this.validUnsentAddr = new Address[valid.size() + validUnsent.size()];
        k = 0;

        for(lrc = 0; lrc < valid.size(); ++lrc) {
            this.validUnsentAddr[k++] = (Address)valid.get(lrc);
        }

        for(lrc = 0; lrc < validUnsent.size(); ++lrc) {
            this.validUnsentAddr[k++] = (Address)validUnsent.get(lrc);
        }
    } 
    ...

    if (sendFailed) {
        this.logger.fine("Sending failed because of invalid destination addresses");
        ...
        throw new SendFailedException("Invalid Addresses", (Exception)mex, this.validSentAddr, this.validUnsentAddr, this.invalidAddr);
    }
}
View Code

  当收件人无效发送失败会抛出SendFailedException异常,异常中包含收件人是否有效的信息。

  因此我们只要从failedMessages查找是否含有SendFailedException,然后从SendFailedException直接得到无效的收件人信息。代码如下:

    private Set<String> getInvalidAddress(MailSendException e){
        Set<String> mails = new HashSet<>();
        for(Exception exception: e.getFailedMessages().values()){
            if(exception instanceof SendFailedException){
                for(Address address: ((SendFailedException) exception).getInvalidAddresses()){
                    mails.add(address.toString());
                }
            }
        }
        return mails;
    }

  

 

以上是关于解决spring boot JavaMailSender部分收件人错误导致发送失败的问题的主要内容,如果未能解决你的问题,请参考以下文章

如何解决 CockroachDB 和 Spring-Boot 的密码问题

一个依赖解决 Spring Boot 反爬虫,防止接口盗刷

启动spring boot报错,怎么解决

spring_boot打jar包及打包错误的解决方法

spring boot修改yml 端口号不生效解决方案

spring boot框架学习9-spring boot的web开发-错误解决及跳转页面