You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot触发式Task Scheduler批量邮件发送改造咨询

问题

开发Spring Boot应用时,每日需给员工发送超1000封邮件,因有每小时发送数量限制,希望每小时发送99封。现有一段通过线程休眠控制发送频率的代码,想要改用Spring的Task Scheduler改写,且邮件发送需通过前端点击按钮触发(已实现POST接口),不适用固定时间运行的常规cron调度器。

原代码如下:

Runnable task = () -> {

int counter = 0;
long startTime = System.currentTimeMillis();
for (String emailAddress : usersEmailAddresses) {
    counter++;
    bulkEmailService.send(BulkEmailMessage.builder()
            .from(bulkEmailMessage.getFrom())
            .subject(bulkEmailMessage.getSubject())
            .content(bulkEmailMessage.getContent())
            .toAddresses(emailAddress)
            .build());

    if (counter % 99 == 0) {
        long endTime = System.currentTimeMillis() - startTime;
        try {
            Thread.sleep(6000 * 60 * 60 - endTime);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        startTime = System.currentTimeMillis();
    }
}

};
taskExecutor.execute(task);

解决方案

核心思路

不再在循环内用线程休眠阻塞执行,而是将邮件地址列表按99个为一批拆分,通过Spring Task Scheduler延迟调度每一批的发送任务,每批间隔1小时(可扣除当前批次发送耗时以保证精度),整个任务链由前端触发的POST接口启动。

具体实现步骤

  1. 启用Spring Task Scheduler
    在Spring Boot启动类上添加@EnableScheduling注解,开启调度功能:

    @SpringBootApplication
    @EnableScheduling
    public class EmailApplication {
        public static void main(String[] args) {
            SpringApplication.run(EmailApplication.class, args);
        }
    }
    
  2. 封装批量邮件调度组件
    创建独立组件处理分批逻辑与任务调度,解耦业务与控制器:

    @Component
    public class BulkEmailScheduler {
        private final BulkEmailService bulkEmailService;
        private final TaskScheduler taskScheduler;
    
        // 构造注入依赖
        public BulkEmailScheduler(BulkEmailService bulkEmailService, TaskScheduler taskScheduler) {
            this.bulkEmailService = bulkEmailService;
            this.taskScheduler = taskScheduler;
        }
    
        // 接口触发的入口方法
        public void startBulkSend(List<String> emailAddresses, BulkEmailMessage template) {
            List<List<String>> batches = splitIntoBatches(emailAddresses, 99);
            if (batches.isEmpty()) return;
    
            long delayMs = 0; // 第一批立即执行
            for (List<String> batch : batches) {
                // 调度当前批次发送任务
                taskScheduler.schedule(
                        () -> sendBatch(batch, template),
                        new Date(System.currentTimeMillis() + delayMs)
                );
                // 下一批延迟1小时(3600000毫秒)
                delayMs += 3600000;
            }
        }
    
        // 拆分列表为指定大小的批次
        private List<List<String>> splitIntoBatches(List<String> list, int batchSize) {
            List<List<String>> batches = new ArrayList<>();
            for (int i = 0; i < list.size(); i += batchSize) {
                int endIndex = Math.min(i + batchSize, list.size());
                batches.add(list.subList(i, endIndex));
            }
            return batches;
        }
    
        // 发送单批次邮件
        private void sendBatch(List<String> batch, BulkEmailMessage template) {
            long batchStartTime = System.currentTimeMillis();
            for (String email : batch) {
                bulkEmailService.send(BulkEmailMessage.builder()
                        .from(template.getFrom())
                        .subject(template.getSubject())
                        .content(template.getContent())
                        .toAddresses(email)
                        .build());
            }
            // 可选:记录批次耗时,用于后续调整延迟精度
            long costMs = System.currentTimeMillis() - batchStartTime;
            System.out.println("批次发送完成,耗时:" + costMs + "毫秒");
        }
    }
    
  3. 在Controller中触发调度
    在已实现的POST接口中调用调度组件的启动方法:

    @RestController
    @RequestMapping("/emails")
    public class EmailController {
        private final BulkEmailScheduler emailScheduler;
    
        public EmailController(BulkEmailScheduler emailScheduler) {
            this.emailScheduler = emailScheduler;
        }
    
        @PostMapping("/bulk-trigger")
        public ResponseEntity<String> triggerBulkEmail(@RequestBody BulkEmailRequest request) {
            // 假设request包含邮件地址列表和模板信息
            emailScheduler.startBulkSend(request.getEmailAddresses(), request.getTemplateMessage());
            return ResponseEntity.ok("批量邮件任务已启动");
        }
    }
    
  4. 可选优化

    • 精确控制间隔:若需要严格按小时窗口发送,可将下一批延迟调整为3600000 - costMs,扣除当前批次的发送耗时。
    • 自定义线程池:Spring默认TaskScheduler线程池较小,可自定义配置满足并发需求:
      @Configuration
      public class TaskSchedulerConfig {
          @Bean
          public TaskScheduler taskScheduler() {
              ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
              scheduler.setPoolSize(5);
              scheduler.setThreadNamePrefix("email-batch-");
              scheduler.initialize();
              return scheduler;
          }
      }
      
    • 任务状态追踪:添加数据库记录,追踪各批次发送状态,方便失败重试。

优势说明

相比原代码的Thread.sleep()实现,该方案的好处:

  • 避免线程长时间阻塞,更高效利用服务器线程资源。
  • 任务由Spring统一管理,集成异常处理、监控更方便。
  • 批次逻辑拆分清晰,代码可读性与可维护性更强。

内容的提问来源于stack exchange,提问作者Alex Grimaldo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 00:05:21