下面是一个完整的 Spring Boot Controller 示例,根据业务逻辑判断返回 Excel 文件流或失败参数:
方案一:使用 HttpServletResponse 直接输出
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/excel")
public class ExcelExportController {
@PostMapping("/export")
public void exportExcel(@RequestBody ExportRequest request,
HttpServletResponse response) {
try {
// 1. 业务逻辑判断是否成功
BusinessResult result = processBusiness(request);
if (!result.isSuccess()) {
// 2. 失败时返回 JSON 格式的错误信息
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("code", 500);
errorResponse.put("message", result.getErrorMessage());
errorResponse.put("data", result.getErrorData());
// 设置 HTTP 状态码
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
// 写入错误信息
response.getWriter().write(
new com.fasterxml.jackson.databind.ObjectMapper()
.writeValueAsString(errorResponse)
);
return;
}
// 3. 成功时返回 Excel 文件
byte[] excelData = generateExcel(result.getData());
// 设置响应头
String fileName = URLEncoder.encode("报表.xlsx", StandardCharsets.UTF_8.name());
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName);
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
// 写入 Excel 文件流
try (OutputStream os = response.getOutputStream()) {
os.write(excelData);
os.flush();
}
} catch (Exception e) {
// 异常处理
handleException(response, e);
}
}
// 业务处理方法
private BusinessResult processBusiness(ExportRequest request) {
// 模拟业务逻辑
if (request.getData() == null || request.getData().isEmpty()) {
return BusinessResult.failure("导出数据不能为空", null);
}
// 其他业务逻辑...
return BusinessResult.success("处理成功", request.getData());
}
// 生成 Excel
private byte[] generateExcel(List<Data> data) {
// 使用 Apache POI 或其他库生成 Excel
// 这里简化为示例
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
XSSFWorkbook workbook = new XSSFWorkbook()) {
XSSFSheet sheet = workbook.createSheet("Sheet1");
// 填充数据...
workbook.write(baos);
return baos.toByteArray();
} catch (Exception e) {
throw new RuntimeException("生成Excel失败", e);
}
}
// 异常处理方法
private void handleException(HttpServletResponse response, Exception e) {
try {
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
Map<String, Object> error = new HashMap<>();
error.put("code", 500);
error.put("message", "服务器内部错误");
error.put("detail", e.getMessage());
response.getWriter().write(
new com.fasterxml.jackson.databind.ObjectMapper()
.writeValueAsString(error)
);
} catch (Exception ex) {
// 处理异常时的异常
}
}
}
方案二:使用 ResponseEntity 统一返回
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.core.io.ByteArrayResource;
@RestController
@RequestMapping("/api/excel")
public class ExcelExportController2 {
@PostMapping("/export2")
public ResponseEntity<?> exportExcel(@RequestBody ExportRequest request) {
try {
BusinessResult result = processBusiness(request);
if (!result.isSuccess()) {
// 返回失败的 JSON 响应
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("code", 400);
errorResponse.put("message", result.getErrorMessage());
errorResponse.put("timestamp", System.currentTimeMillis());
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.contentType(MediaType.APPLICATION_JSON)
.body(errorResponse);
}
// 生成 Excel
byte[] excelData = generateExcel(result.getData());
// 设置响应头
String fileName = "export_" + System.currentTimeMillis() + ".xlsx";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDisposition(
ContentDisposition.attachment()
.filename(fileName, StandardCharsets.UTF_8)
.build()
);
headers.setCacheControl("no-cache");
ByteArrayResource resource = new ByteArrayResource(excelData);
return ResponseEntity.ok()
.headers(headers)
.contentLength(excelData.length)
.body(resource);
} catch (Exception e) {
Map<String, Object> error = new HashMap<>();
error.put("code", 500);
error.put("message", "导出失败");
error.put("detail", e.getMessage());
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.contentType(MediaType.APPLICATION_JSON)
.body(error);
}
}
}
辅助类定义
// 请求参数类
@Data
public class ExportRequest {
private String startDate;
private String endDate;
private List<Data> data;
@Data
public static class Data {
private String name;
private Integer value;
}
}
// 业务结果类
@Data
@AllArgsConstructor
public class BusinessResult {
private boolean success;
private String errorMessage;
private List<Data> data;
public static BusinessResult success(String message, List<Data> data) {
return new BusinessResult(true, message, data);
}
public static BusinessResult failure(String errorMessage, List<Data> data) {
return new BusinessResult(false, errorMessage, data);
}
}
使用建议
-
方案一 更适合需要精细控制 HTTP 响应的场景
-
方案二 更符合 Spring 的编程风格,代码更简洁
-
文件命名 :使用
filename*=UTF-8''编码文件名,支持中文 -
响应头设置:
-
成功时设置
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet -
失败时设置
application/json
-
-
异常处理:确保异常时也能返回规范的错误信息
客户端调用示例
// 前端调用
fetch('/api/excel/export', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
startDate: '2024-01-01',
endDate: '2024-12-31',
data: [...]
})
})
.then(async response => {
const contentType = response.headers.get('content-type');
if (contentType.includes('application/json')) {
// 错误响应
const error = await response.json();
console.error('导出失败:', error.message);
} else if (contentType.includes('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')) {
// Excel 文件
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = '报表.xlsx';
a.click();
}
});
这样设计可以确保:
-
成功时正确下载 Excel 文件
-
失败时返回结构化的错误信息
-
客户端可以根据 Content-Type 区分处理结果