Java实现REST服务依Excel生成时长执行不同响应逻辑的方案咨询
这个需求我之前做类似的报表系统时碰到过,核心就是要把耗时的Excel生成任务异步化,同时做超时判断。下面我用Spring Boot为例,给你一步步拆解实现方式:
核心思路
要实现这个逻辑,我们需要解决两个关键问题:
- 把Excel生成的耗时任务从请求主线程中剥离,放到异步线程池执行,避免阻塞HTTP连接
- 主线程设置3秒超时等待,根据任务是否完成决定直接返回文件还是先返回提示、后续发邮件
具体实现步骤
1. 准备依赖
首先在pom.xml里添加必要的依赖(如果是Gradle的话对应调整):
<dependencies> <!-- Web核心依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 异步任务支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-task</artifactId> </dependency> <!-- 邮件发送 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> <!-- Excel生成工具(Apache POI) --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.5</version> </dependency> </dependencies>
2. 配置异步线程池与邮件服务
异步线程池配置
创建一个配置类开启异步支持,并自定义线程池参数(避免默认线程池的局限性):
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; @Configuration @EnableAsync public class AsyncConfig { @Bean(name = "excelTaskExecutor") public Executor excelTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); // 核心线程数 executor.setMaxPoolSize(10); // 最大线程数 executor.setQueueCapacity(20); // 队列容量 executor.setThreadNamePrefix("Excel-Generator-"); // 线程名称前缀,方便排查问题 executor.initialize(); return executor; } }
邮件服务配置
在application.properties里配置邮件发送的参数(以QQ邮箱为例,其他邮箱类似):
# 邮件服务器配置 spring.mail.host=smtp.qq.com spring.mail.port=587 spring.mail.username=your-qq-email@qq.com spring.mail.password=your-authorization-code spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true
3. 编写异步Excel生成服务
创建一个服务类,用@Async注解标记Excel生成方法,返回Future<File>以便主线程判断任务状态:
import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.scheduling.concurrent.AsyncResult; import java.io.File; import java.io.FileOutputStream; import java.util.concurrent.Future; @Service public class ExcelGeneratorService { @Async("excelTaskExecutor") public Future<File> generateLargeExcel(String userEmail) { try { // 模拟大数据量生成耗时(这里可以替换成真实的业务数据填充逻辑) Thread.sleep(4000); // 模拟4秒耗时,测试超时场景 // 创建Excel文件 Workbook workbook = new XSSFWorkbook(); workbook.createSheet("数据报表"); // 这里省略填充数据的逻辑... // 创建临时文件存储Excel File tempFile = File.createTempFile("report_", ".xlsx"); try (FileOutputStream fos = new FileOutputStream(tempFile)) { workbook.write(fos); } return new AsyncResult<>(tempFile); } catch (Exception e) { // 异常处理:可以记录日志,后续通知用户生成失败 e.printStackTrace(); return new AsyncResult<>(null); } } }
4. 编写REST接口处理请求
在Controller里处理用户请求,设置3秒超时,根据任务完成情况返回不同响应:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.FileSystemResource; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import java.io.File; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @RestController public class ReportController { @Autowired private ExcelGeneratorService excelGeneratorService; @Autowired private JavaMailSender mailSender; @GetMapping("/generate-report") public ResponseEntity<?> generateReport(@RequestParam String userEmail) { Future<File> excelFuture = excelGeneratorService.generateLargeExcel(userEmail); try { // 等待3秒,判断任务是否完成 File excelFile = excelFuture.get(3, TimeUnit.SECONDS); // 如果任务完成,直接返回Excel文件 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); headers.setContentDispositionFormData("attachment", "report.xlsx"); return new ResponseEntity<>(new FileSystemResource(excelFile), headers, HttpStatus.OK); } catch (TimeoutException e) { // 超时:返回提示消息,后续异步发送邮件 new Thread(() -> { try { // 等待任务完成 File excelFile = excelFuture.get(); if (excelFile != null) { // 发送邮件 sendEmailWithAttachment(userEmail, excelFile); // 发送完成后删除临时文件 excelFile.delete(); } else { // 发送生成失败的通知邮件 sendFailureNotification(userEmail); } } catch (InterruptedException | ExecutionException ex) { ex.printStackTrace(); sendFailureNotification(userEmail); } }).start(); return ResponseEntity.ok("报表生成需要较长时间,完成后将发送至您的邮箱:" + userEmail); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("报表生成失败,请稍后重试"); } } // 发送带附件的邮件 private void sendEmailWithAttachment(String toEmail, File attachment) { try { MimeMessage message = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message, true); helper.setTo(toEmail); helper.setSubject("您的报表已生成"); helper.setText("您好,您请求的报表已生成,请查收附件。"); helper.addAttachment("report.xlsx", attachment); mailSender.send(message); } catch (MessagingException e) { e.printStackTrace(); } } // 发送生成失败的通知邮件 private void sendFailureNotification(String toEmail) { try { MimeMessage message = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message); helper.setTo(toEmail); helper.setSubject("报表生成失败通知"); helper.setText("您好,您请求的报表生成失败,请稍后重试。"); mailSender.send(message); } catch (MessagingException e) { e.printStackTrace(); } } }
关键注意事项
- 临时文件管理:示例中用了
File.createTempFile,但要注意任务完成后及时删除,避免磁盘被占满;也可以用分布式文件存储(比如MinIO)来替代本地临时文件,适合集群部署场景 - 异步任务监控:可以集成Spring Actuator来监控异步线程池的状态,方便排查问题
- 邮件可靠性:如果邮件发送失败,建议加入重试机制或者用消息队列(比如RabbitMQ)来异步投递邮件,避免丢失通知
- 用户身份传递:如果需要关联用户信息,不要用ThreadLocal(异步线程会丢失上下文),建议直接把用户ID/邮箱作为参数传递给异步方法
- 异常处理:要覆盖Excel生成失败、邮件发送失败等场景,确保用户能收到反馈
内容的提问来源于stack exchange,提问作者Vijay Kumar Chauhan
相关产品推荐
相关产品推荐

