文件下载/上传
文件流式下载
描述
文件流式下载不区分文件类型,只需要文件路径
响应头
| 语句 |
响应头名称 |
作用 |
示例值 |
setContentType("application/octet-stream") |
Content-Type |
告诉浏览器:是二进制流文件 |
application/octet-stream |
setHeader("Content-Disposition", "attachment; filename=...") |
Content-Disposition |
告诉浏览器:是下载附件,并指定下载名 |
attachment; filename="test.pdf" |
setHeader("Content-Length", String.valueOf(Files.size(filePath))) |
Content-Length |
告诉浏览器:文件大小(可显示进度) |
1048576 |
setCharacterEncoding("UTF-8") |
响应字符集 |
保证文件名和文本内容不乱码 |
UTF-8 |
后端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| @RestController public class FileDownloadController {
private final Path baseDir = Paths.get("D:\\Desktop\\测试").toAbsolutePath().normalize();
@GetMapping("/download3") public void downloadFile(@RequestParam("name") String name, HttpServletResponse response) throws IOException {
Path filePath = baseDir.resolve(name).normalize(); if (!filePath.startsWith(baseDir) || !Files.exists(filePath) || Files.isDirectory(filePath)) { response.setStatus(HttpServletResponse.SC_NOT_FOUND); response.getWriter().write("File not found"); return; }
String fileName = filePath.getFileName().toString(); String encodedFileName = URLEncoder.encode(fileName, "UTF-8");
response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\""); response.setHeader("Content-Length", String.valueOf(Files.size(filePath))); response.setCharacterEncoding("UTF-8");
try (InputStream in = Files.newInputStream(filePath); OutputStream out = response.getOutputStream()) {
byte[] buffer = new byte[8192]; int len; while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); out.flush(); } } } }
|
文件流式上传
后端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile;
import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.HashMap; import java.util.Map;
@RestController @RequestMapping("/api") public class FileUploadController {
@Value("${file.upload-dir}") private String uploadDir;
@PostMapping("/upload") public ResponseEntity<?> uploadFile( @RequestParam("file") MultipartFile file, @RequestParam(value = "customPath", required = false) String customPath ) { if (file.isEmpty()) { return ResponseEntity.badRequest().body(Map.of("message", "请选择文件上传")); }
try { String fullPath = uploadDir; if (customPath != null && !customPath.trim().isEmpty()) { fullPath += (customPath.endsWith("/") || customPath.endsWith("\\")) ? customPath : customPath + "\\"; }
Path uploadPath = Paths.get(fullPath); if (!Files.exists(uploadPath)) { Files.createDirectories(uploadPath); }
String originalFilename = file.getOriginalFilename(); Path filePath = uploadPath.resolve(originalFilename); if (Files.exists(filePath)){ Files.delete(filePath); }
try (InputStream in = file.getInputStream()) { Files.copy(in, filePath, StandardCopyOption.REPLACE_EXISTING); }
Map<String, Object> response = new HashMap<>(); response.put("message", "文件上传成功"); response.put("originalFilename", originalFilename); response.put("savedPath", filePath.toString()); response.put("fileSize", file.getSize()); response.put("contentType", file.getContentType());
return ResponseEntity.ok(response); } catch (IOException e) { e.printStackTrace(); return ResponseEntity.internalServerError().body(Map.of("message", "文件上传失败: " + e.getMessage())); } } }
|
配置文件
1 2 3 4
| file.upload-dir=D:\\Desktop\\test\\
file.upload-dir=/Users/username/Desktop/test/
|
数据导出
描述
数据导出常以Excel表格的样式导出,有 Apache POI 和 EasyExcel 两种方式
区别
| 特性 |
Apache POI |
EasyExcel |
| 项目背景 |
Apache 软件基金会的顶级项目,历史悠久,是 Java 操作 Office 文档的事实标准。 |
阿里巴巴开源的项目,旨在解决 POI 在处理大数据量时的内存溢出问题。 |
| 内存模型 |
基于内存的模型。用户模式会将整个文件(所有工作表、行、单元格)一次性加载到内存中,形成对象树。 |
基于事件的流式模型。逐行读取和解析,读取时不会将整个文件加载到内存中。 |
| 内存消耗 |
高。处理大文件(如几十MB以上)时,很容易导致 OOM(内存溢出)。 |
极低。理论上只会在内存中保留一行的数据,非常适合处理超大文件(如百万行)。 |
| 性能 |
对于小文件,性能很好。对于大文件,性能急剧下降,甚至无法完成。 |
对于大文件,性能非常出色且稳定。对于小文件,性能与 POI 相当或略慢(因为涉及模型转换)。 |
| API 与易用性 |
功能强大但繁琐。API 非常底层和灵活,需要编写大量样板代码(如创建行、单元格、设置样式等)。 |
简单易用。提供了高度封装的 API,支持注解驱动模型(通过注解映射Java对象和Excel列),并内置了监听器用于读。 |
| 功能完整性 |
非常全面。支持 Excel 的所有特性,包括 .xls (HSSF) 和 .xlsx (XSSF/SXSSF),以及复杂的图表、样式、公式、宏等。 |
覆盖常用场景。专注于数据的导入导出,支持基本样式和格式。对于非常复杂的 Excel 操作(如图表、合并复杂单元格),功能不如 POI。 |
Apache POI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| @PostMapping("/excel/poi") public void exportWithPoi(HttpServletResponse response) throws IOException { List<Paper> list = paperService.list();
try (Workbook wb = new XSSFWorkbook()) { Sheet sheet = wb.createSheet("数据导出"); CreationHelper helper = wb.getCreationHelper();
CellStyle baseStyle = wb.createCellStyle(); baseStyle.setAlignment(HorizontalAlignment.CENTER); baseStyle.setVerticalAlignment(VerticalAlignment.CENTER); baseStyle.setBorderTop(BorderStyle.THIN); baseStyle.setBorderBottom(BorderStyle.THIN); baseStyle.setBorderLeft(BorderStyle.THIN); baseStyle.setBorderRight(BorderStyle.THIN);
CellStyle headerStyle = wb.createCellStyle(); headerStyle.cloneStyleFrom(baseStyle); headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); Font boldFont = wb.createFont(); boldFont.setBold(true); headerStyle.setFont(boldFont);
CellStyle dateStyle = wb.createCellStyle(); dateStyle.cloneStyleFrom(baseStyle); dateStyle.setDataFormat(helper.createDataFormat().getFormat("yyyy-MM-dd"));
String[] headers = {"ID", "文章名", "创建时间", "浏览数"}; Row headerRow = sheet.createRow(0); for (int i = 0; i < headers.length; i++) { Cell cell = headerRow.createCell(i); cell.setCellValue(headers[i]); cell.setCellStyle(headerStyle); }
int rowNum = 1; for (Paper p : list) { Row row = sheet.createRow(rowNum++); Object[] vals = { p.getPaperId(), p.getPaperTitle(), p.getPublishDate(), p.getViewCount() }; for (int i = 0; i < vals.length; i++) { Cell cell = row.createCell(i); if (vals[i] instanceof Date) { cell.setCellValue((Date) vals[i]); cell.setCellStyle(dateStyle); } else { cell.setCellValue(vals[i] == null ? "" : vals[i].toString()); cell.setCellStyle(baseStyle); } } }
for (int i = 0; i < headers.length; i++) { sheet.autoSizeColumn(i); }
String fileName = URLEncoder.encode("export_data.xlsx", StandardCharsets.UTF_8); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment; filename=" + fileName); wb.write(response.getOutputStream()); } }
|
EasyExcel
使用方式相较于poi更为简洁
EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel 官网
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| import com.alibaba.excel.EasyExcel; import com.alibaba.excel.write.metadata.style.WriteCellStyle; import com.alibaba.excel.write.metadata.style.WriteFont; import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.bind.annotation.*; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.List;
@RestController @RequestMapping("/api") public class EasyExcelExportController {
private final PaperService paperService;
public EasyExcelExportController(PaperService paperService) { this.paperService = paperService; }
@GetMapping("/excel/easy") public void exportWithEasyExcel(HttpServletResponse response) throws IOException { List<PaperExportDTO> list = paperService.list().stream().map(p -> new PaperExportDTO( p.getPaperId(), p.getPaperTitle(), p.getPublishDate(), p.getViewCount() )).toList();
String fileName = URLEncoder.encode("export_data.xlsx", StandardCharsets.UTF_8); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
WriteCellStyle headStyle = new WriteCellStyle(); WriteFont headFont = new WriteFont(); headFont.setBold(true); headFont.setFontHeightInPoints((short) 12); headStyle.setWriteFont(headFont);
WriteCellStyle contentStyle = new WriteCellStyle(); WriteFont contentFont = new WriteFont(); contentFont.setFontHeightInPoints((short) 11); contentStyle.setWriteFont(contentFont);
HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy(headStyle, contentStyle);
EasyExcel.write(response.getOutputStream(), PaperExportDTO.class) .sheet("数据导出") .registerWriteHandler(styleStrategy) .doWrite(list); } }
|