首页 > 解决方案 > 电子邮件未在 @Async 注释方法中发送

问题描述

我正在尝试实现用户下载文件的功能。一般工作流程如下:

  1. 用户点击前端的下载按钮
  2. 后端会从request中接收下载信息,从数据库中获取数据,然后生成zip文件
  3. 该文件将上传到云存储(本例中为 Google)。
  4. 带有签名 URL 的电子邮件将发送给用户,以从云下载 zip 文件。

所有步骤 2、3 和 4 都将在使用 @Async 注释的方法中执行。问题是如果我重新启动后端服务器并只发送一个下载请求,一切都很好,这意味着可以接收电子邮件。 但是,在发送更多下载请求后它不起作用。不会再发送电子邮件。没有错误,没有警告,但是所有需要的数据都被正确接收,只是没有发送电子邮件。

任何人都知道它的问题是什么?

我的电子邮件发件人:

public class AbstractEmailSender {

    public MimeMessage mimeMessage;
    public MimeMessageHelper mimeMessageHelper;
    public JavaMailSender javaMailSender;
    private final String SENDER = MY_SEND_EMAIL_ADDRESS;
    public final String USER_NAME_KEY = "username";

    public AbstractEmailSender(JavaMailSender javaMailSender) throws MessagingException {
//        this.javaMailSender = new JavaMailSenderImpl();
        this.javaMailSender = javaMailSender;
        this.doInitialization();
    }

    public void doInitialization() throws MessagingException {
        this.mimeMessage = this.javaMailSender.createMimeMessage();
        this.mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
    }

    public void setEmailContext(String receiver, String subject) throws MessagingException {
        this.mimeMessageHelper.setSubject(subject);
        this.mimeMessageHelper.setFrom(SENDER);
        this.mimeMessageHelper.setTo(receiver);
    }
}

@Component
public class DownloadDataSuccessEmailSender extends AbstractEmailSender implements MailService{

    private final TemplateEngine templateEngine;
    private final static String DOWNLOAD_DATA_SUCCESS_EMAIL_SUBJECT = XXXXXX;
    private final static String SIGNED_URL_KEY = "signedUrl";
    private final static String DOWNLOAD_EMAIL_TEMPLATE_NAME = "downloadDataSuccessEmail.html";
    private static final Logger logger = LogManager.getLogger(DownloadDataSuccessEmailSender.class);


    public DownloadDataSuccessEmailSender(JavaMailSender javaMailSender, TemplateEngine templateEngine) throws MessagingException {
        super(javaMailSender);
        this.templateEngine = templateEngine;
    }

    @Override
    public void sendEmailWithSignedUrlToDownloadFile(URL signedUrl, String username, String receiver) {
        // print the result to make sure all data are processed correctly, nothing wrong with 
        //this step
        System.out.println(signedUrl);
        System.out.println(username);
        System.out.println(receiver);
        try{
            super.setEmailContext(receiver, DOWNLOAD_DATA_SUCCESS_EMAIL_SUBJECT);
            Context context = new Context();
            context.setVariable(this.USER_NAME_KEY, username);
            context.setVariable(SIGNED_URL_KEY, signedUrl);
            String email = this.templateEngine.process(DOWNLOAD_EMAIL_TEMPLATE_NAME, context);
            this.mimeMessageHelper.setText(email, true);
            this.javaMailSender.send(mimeMessage);
        }catch (MailException | MessagingException e) {
            logger.error("Email with download data error: ", e);
            throw new EmailSendException(ErrorInfo.EMAIL_SEND_EXCEPTION.getCode(), ErrorInfo.EMAIL_SEND_EXCEPTION.getMessage());
        }
    }
}

电子邮件配置文件:

spring:
  mail:
    host: smtp.gmail.com
    username: MY_EMAIL_ADDRESS
    password: MY_PASSWORD
    properties.mail.smtp:
      auth: true
      connectiontimeout: 60000
      timeout: 60000
      writetimeout: 50000
      starttls.enable: true
    port: 587
    protocol: smtp
  thymeleaf:
    prefix: classpath:/templates/

处理所有逻辑的异步方法

@Override
    @Async
    @Transactional(timeout = DOWNLOAD_DATA_TRANSACTION_TIME_LIMIT)
    public void downloadFile(SearchQuery query, DownloadRequestRecord downloadRecord){
        String username = downloadRecord.getUsername();
        String emailAddress = this.userService.getUserEmailAddressByUsername(username);
        try{
            // here ignore the parts to get data from database and generate zip file and check if the file is uploaded to cloud space successfully here
            // ......
            if (uploadFile == null) { // check if file exists
                logger.error("File: {} does not exist in cloud!", zipFileName);
                downloadRecord.setSuccess(0);
                this.downloadDataFailMessageEmailSender.sendEmailWithDownloadDataFail(emailAddress, username, downloadRecord.getQuery(), downloadRecord.getDownloadTime()); // send email to inform user the download is fail
            } else {
// file exists, generate signed URL and send email with download link
               this.downloadDataSuccessEmailSender.sendEmailWithSignedUrlToDownloadFile(signedUrl, username, emailAddress);
                downloadRecord.setSuccess(1);
            }
        } catch (IOException | EmailSendException ex) {
            logger.error("Exception from downloading data: ", ex);
            downloadRecord.setSuccess(0);
        } finally {
            this.userService.recordDownloadHistoryOfUser(downloadRecord);
        }
    }

我的线程池配置

@Configuration
public class AsyncConfig implements AsyncConfigurer {

    private static final int CORE_POOL_SIZE = 6;
    private static final int MAX_POOL_SIZE = 10;
    private static final int QUEUE_CAPACITY = 100;
    private static final String THREAD_NAME_PREFIX = "ThreadPoolTaskExecutor-";
    private static final Logger logger = LogManager.getLogger(AsyncConfig.class);

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(CORE_POOL_SIZE);
        executor.setMaxPoolSize(MAX_POOL_SIZE);
        executor.setQueueCapacity(QUEUE_CAPACITY);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncUncaughtExceptionHandler() {
            @Override
            public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
                logger.error("ERROR: ", throwable);
            }
        };
    }
}

标签: javaspringspring-bootemail

解决方案


在 sendEmailWithSignedUrlToDownloadFile 创建 MimeMessage 使用

MimeMessage mimeMessage = mailSender.createMimeMessage();

而不是将 MimeMessage 的单个实例作为超类的成员


推荐阅读