
🏆本文收录于《滚雪球学Spring Boot》,专门攻坚指数提升,2025 年国内最系统+最强(更新中)。
本专栏致力打造最硬核 Spring Boot 从零基础到进阶系列学习内容,🚀均为全网独家首发,打造精品专栏,专栏持续更新中…欢迎大家订阅持续学习。 如果想快速定位学习,可以看这篇【SpringBoot教程导航帖】,你想学习的都被收集在内,快速投入学习!!两不误。
演示环境说明:
- 开发工具:IDEA 2021.3
- JDK版本: JDK 17(推荐使用 JDK 17 或更高版本,因为 Spring Boot 3.x 系列要求 Java 17,Spring Boot 3.5.4 基于 Spring Framework 6.x 和 Jakarta EE 9,它们都要求至少 JDK 17。)
- Spring Boot版本:3.5.4(于25年7月24日发布)
- Maven版本:3.8.2 (或更高)
- Gradle:(如果使用 Gradle 构建工具的话):推荐使用 Gradle 7.5 或更高版本,确保与 JDK 17 兼容。
- 操作系统:Windows 11
全文目录:
🌟 前言:那些年我们一起踩过的文件上传"坑"
哎呀,说起文件上传下载,我这个码了十几年代码的老家伙真是有一肚子话要说!😅 还记得刚入行那会儿,被一个简单的文件上传功能折磨得死去活来,那时候还是SSH框架的天下,配置文件写得我眼花缭乱,结果上传个图片比登天还难。现在好了,SpringBoot这个"救世主"出现了,让文件操作变得so easy!
但是呢,别以为SpringBoot把一切都简化了就可以随便写代码了。我见过太多小伙伴因为文件上传的安全问题被老板骂得狗血淋头,也见过因为文件下载的性能问题让服务器直接"罢工"的惨案。今天咱们就来好好聊聊这个看似简单实则"暗藏杀机"的文件上传下载!🎯
📁 文件上传:从"小白"到"老司机"的必经之路
🎪 基础配置:让SpringBoot认识你的"文件朋友们"
首先,咱们得在application.yml
里给SpringBoot打个招呼,告诉它我们要玩文件上传这个游戏:
spring:
servlet:
multipart:
enabled: true # 启用文件上传支持,这个必须开!
max-file-size: 10MB # 单个文件最大10MB,别太贪心哦
max-request-size: 50MB # 总请求大小最大50MB
file-size-threshold: 1KB # 超过1KB就写入临时文件
location: /tmp # 临时文件存储位置
这配置看起来简单,但每一行都有讲究!我曾经见过一个哥们儿把max-file-size
设成了1GB,结果用户上传大文件直接把服务器内存撑爆了,那场面,啧啧啧…🤦♂️
🎨 控制器编写:让文件"乖乖听话"
@RestController
@RequestMapping("/api/file")
@Slf4j
public class FileController {
// 文件存储根路径,生产环境记得配置到外部!
private static final String UPLOAD_PATH = "/app/uploads/";
/**
* 单文件上传 - 最基础的操作
* 别小看这个简单的功能,里面的门道可多着呢!
*/
@PostMapping("/upload")
public ResponseEntity<Map<String, Object>> uploadFile(
@RequestParam("file") MultipartFile file,
@RequestParam(value = "category", defaultValue = "general") String category) {
Map<String, Object> result = new HashMap<>();
try {
// 第一步:校验文件是否为空,这是最基本的礼貌!
if (file.isEmpty()) {
log.warn("用户尝试上传空文件,这是什么操作?🤷♂️");
result.put("success", false);
result.put("message", "老铁,你上传个空文件是要闹哪样?");
return ResponseEntity.badRequest().body(result);
}
// 第二步:文件类型验证,安全第一!
if (!isValidFileType(file)) {
log.warn("检测到非法文件类型上传尝试: {}", file.getContentType());
result.put("success", false);
result.put("message", "文件类型不合法,别想搞事情!");
return ResponseEntity.badRequest().body(result);
}
// 第三步:生成唯一文件名,避免重名覆盖的悲剧
String originalFilename = file.getOriginalFilename();
String fileExtension = getFileExtension(originalFilename);
String uniqueFilename = System.currentTimeMillis() + "_" +
UUID.randomUUID().toString().substring(0, 8) + fileExtension;
// 第四步:创建目录结构
String categoryPath = UPLOAD_PATH + category + "/";
File categoryDir = new File(categoryPath);
if (!categoryDir.exists()) {
boolean created = categoryDir.mkdirs();
if (!created) {
log.error("创建目录失败: {}", categoryPath);
throw new RuntimeException("目录创建失败,服务器闹脾气了!");
}
}
// 第五步:保存文件
String filePath = categoryPath + uniqueFilename;
file.transferTo(new File(filePath));
log.info("文件上传成功: {} -> {}", originalFilename, filePath);
result.put("success", true);
result.put("message", "上传成功!你的文件已经安全到达目的地 🎉");
result.put("filename", uniqueFilename);
result.put("originalName", originalFilename);
result.put("size", file.getSize());
result.put("url", "/api/file/download/" + category + "/" + uniqueFilename);
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("文件上传失败", e);
result.put("success", false);
result.put("message", "上传失败:" + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
}
}
/**
* 多文件上传 - 批量处理的艺术
* 这个功能在实际项目中超级常用!
*/
@PostMapping("/upload/batch")
public ResponseEntity<Map<String, Object>> uploadFiles(
@RequestParam("files") MultipartFile[] files,
@RequestParam(value = "category", defaultValue = "batch") String category) {
Map<String, Object> result = new HashMap<>();
List<Map<String, Object>> successFiles = new ArrayList<>();
List<Map<String, Object>> failedFiles = new ArrayList<>();
log.info("开始批量上传,文件数量: {}", files.length);
for (MultipartFile file : files) {
Map<String, Object> fileResult = new HashMap<>();
fileResult.put("originalName", file.getOriginalFilename());
try {
if (file.isEmpty()) {
fileResult.put("success", false);
fileResult.put("message", "空文件跳过");
failedFiles.add(fileResult);
continue;
}
if (!isValidFileType(file)) {
fileResult.put("success", false);
fileResult.put("message", "文件类型不合法");
failedFiles.add(fileResult);
continue;
}
// 这里可以复用单文件上传的逻辑
String uniqueFilename = saveFile(file, category);
fileResult.put("success", true);
fileResult.put("filename", uniqueFilename);
fileResult.put("size", file.getSize());
fileResult.put("url", "/api/file/download/" + category + "/" + uniqueFilename);
successFiles.add(fileResult);
} catch (Exception e) {
log.error("批量上传中单个文件失败: {}", file.getOriginalFilename(), e);
fileResult.put("success", false);
fileResult.put("message", e.getMessage());
failedFiles.add(fileResult);
}
}
result.put("totalCount", files.length);
result.put("successCount", successFiles.size());
result.put("failedCount", failedFiles.size());
result.put("successFiles", successFiles);
result.put("failedFiles", failedFiles);
result.put("message", String.format("批量上传完成!成功%d个,失败%d个",
successFiles.size(), failedFiles.size()));
return ResponseEntity.ok(result);
}
/**
* 文件类型验证 - 安全的守护神
* 这个方法救过我无数次!
*/
private boolean isValidFileType(MultipartFile file) {
String contentType = file.getContentType();
if (contentType == null) {
return false;
}
// 定义允许的文件类型,根据业务需求调整
String[] allowedTypes = {
"image/jpeg", "image/png", "image/gif", "image/webp",
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"text/plain"
};
return Arrays.asList(allowedTypes).contains(contentType);
}
/**
* 获取文件扩展名
*/
private String getFileExtension(String filename) {
if (filename == null || !filename.contains(".")) {
return "";
}
return filename.substring(filename.lastIndexOf("."));
}
/**
* 保存文件的通用方法
*/
private String saveFile(MultipartFile file, String category) throws IOException {
String originalFilename = file.getOriginalFilename();
String fileExtension = getFileExtension(originalFilename);
String uniqueFilename = System.currentTimeMillis() + "_" +
UUID.randomUUID().toString().substring(0, 8) + fileExtension;
String categoryPath = UPLOAD_PATH + category + "/";
File categoryDir = new File(categoryPath);
if (!categoryDir.exists()) {
categoryDir.mkdirs();
}
String filePath = categoryPath + uniqueFilename;
file.transferTo(new File(filePath));
return uniqueFilename;
}
}
📥 文件下载:让数据"飞"向用户
说完上传,咱们再来聊聊下载。别以为下载比上传简单,这里面的坑一点都不少!我记得有一次,一个项目的文件下载功能在并发量大的时候直接把服务器搞崩了,原因就是没有考虑到大文件的流式传输。😱
/**
* 文件下载 - 让数据优雅地"飞"向用户
*/
@GetMapping("/download/{category}/{filename}")
public ResponseEntity<Resource> downloadFile(
@PathVariable String category,
@PathVariable String filename,
HttpServletRequest request) {
try {
// 构建文件路径
String filePath = UPLOAD_PATH + category + "/" + filename;
Path path = Paths.get(filePath);
// 检查文件是否存在
if (!Files.exists(path) || !Files.isReadable(path)) {
log.warn("用户尝试下载不存在的文件: {}", filePath);
return ResponseEntity.notFound().build();
}
// 安全检查:防止路径遍历攻击
if (!isSecurePath(filePath)) {
log.warn("检测到路径遍历攻击尝试: {}", filePath);
return ResponseEntity.badRequest().build();
}
// 创建资源对象
Resource resource = new UrlResource(path.toUri());
// 确定文件的MIME类型
String contentType = null;
try {
contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
} catch (IOException ex) {
log.warn("无法确定文件类型: {}", filename);
}
// 如果无法确定类型,默认为二进制流
if (contentType == null) {
contentType = "application/octet-stream";
}
// 设置文件名,支持中文
String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8.toString());
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + encodedFilename + "\"")
.body(resource);
} catch (Exception e) {
log.error("文件下载失败: {}", filename, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
/**
* 在线预览文件 - 让用户"一眼看穿"文件内容
*/
@GetMapping("/preview/{category}/{filename}")
public ResponseEntity<Resource> previewFile(
@PathVariable String category,
@PathVariable String filename,
HttpServletRequest request) {
try {
String filePath = UPLOAD_PATH + category + "/" + filename;
Path path = Paths.get(filePath);
if (!Files.exists(path) || !Files.isReadable(path)) {
return ResponseEntity.notFound().build();
}
if (!isSecurePath(filePath)) {
return ResponseEntity.badRequest().build();
}
Resource resource = new UrlResource(path.toUri());
String contentType = getContentType(request, resource);
// 对于图片和PDF,支持在线预览
if (contentType.startsWith("image/") || contentType.equals("application/pdf")) {
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + filename + "\"")
.body(resource);
} else {
// 其他类型强制下载
return downloadFile(category, filename, request);
}
} catch (Exception e) {
log.error("文件预览失败: {}", filename, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
/**
* 大文件流式下载 - 内存友好的下载方式
* 这个功能在处理视频、大图片时特别有用!
*/
@GetMapping("/stream/{category}/{filename}")
public ResponseEntity<StreamingResponseBody> streamDownload(
@PathVariable String category,
@PathVariable String filename) {
try {
String filePath = UPLOAD_PATH + category + "/" + filename;
Path path = Paths.get(filePath);
if (!Files.exists(path) || !Files.isReadable(path)) {
return ResponseEntity.notFound().build();
}
if (!isSecurePath(filePath)) {
return ResponseEntity.badRequest().build();
}
File file = path.toFile();
long fileSize = file.length();
StreamingResponseBody responseBody = outputStream -> {
try (FileInputStream inputStream = new FileInputStream(file);
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) {
byte[] buffer = new byte[8192]; // 8KB 缓冲区
int bytesRead;
while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
outputStream.flush();
}
log.info("流式下载完成: {}, 大小: {} bytes", filename, fileSize);
} catch (IOException e) {
log.error("流式下载过程中发生错误: {}", filename, e);
throw e;
}
};
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + filename + "\"")
.header(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileSize))
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(responseBody);
} catch (Exception e) {
log.error("流式下载失败: {}", filename, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
/**
* 安全路径检查 - 防止路径遍历攻击
*/
private boolean isSecurePath(String filePath) {
try {
String canonicalPath = new File(filePath).getCanonicalPath();
String basePath = new File(UPLOAD_PATH).getCanonicalPath();
return canonicalPath.startsWith(basePath);
} catch (IOException e) {
log.error("路径安全检查失败", e);
return false;
}
}
/**
* 获取内容类型
*/
private String getContentType(HttpServletRequest request, Resource resource) {
String contentType = null;
try {
contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
} catch (IOException ex) {
log.debug("无法确定文件类型");
}
return contentType == null ? "application/octet-stream" : contentType;
}
🛡️ 高级特性:让你的文件处理"更上一层楼"
🔐 文件安全处理
安全问题可不能马虎!我见过太多因为文件上传漏洞被黑客攻击的案例了。来看看怎么做安全防护:
@Component
@Slf4j
public class FileSecurityService {
// 恶意文件签名检测
private static final Map<String, String> FILE_SIGNATURES = new HashMap<>();
static {
FILE_SIGNATURES.put("jpg", "FFD8FF");
FILE_SIGNATURES.put("png", "89504E47");
FILE_SIGNATURES.put("gif", "47494638");
FILE_SIGNATURES.put("pdf", "255044462D");
// 可以添加更多文件类型的签名
}
/**
* 文件签名验证 - 防止伪造文件扩展名
*/
public boolean validateFileSignature(MultipartFile file) {
try {
String originalFilename = file.getOriginalFilename();
if (originalFilename == null) {
return false;
}
String extension = getFileExtension(originalFilename).toLowerCase().replace(".", "");
String expectedSignature = FILE_SIGNATURES.get(extension);
if (expectedSignature == null) {
// 对于不在检测列表中的文件类型,允许通过
return true;
}
byte[] fileBytes = file.getBytes();
if (fileBytes.length < expectedSignature.length() / 2) {
return false;
}
StringBuilder actualSignature = new StringBuilder();
for (int i = 0; i < expectedSignature.length() / 2; i++) {
actualSignature.append(String.format("%02X", fileBytes[i]));
}
boolean isValid = actualSignature.toString().startsWith(expectedSignature);
if (!isValid) {
log.warn("文件签名验证失败: {} 期望:{} 实际:{}",
originalFilename, expectedSignature, actualSignature.toString());
}
return isValid;
} catch (Exception e) {
log.error("文件签名验证过程中出错", e);
return false;
}
}
/**
* 病毒扫描接口 - 可以集成第三方杀毒引擎
*/
public boolean scanForVirus(MultipartFile file) {
// 这里可以集成ClamAV或其他杀毒软件的API
// 简单示例:检查文件名中是否包含可疑关键词
String filename = file.getOriginalFilename();
if (filename != null) {
String[] suspiciousKeywords = {".exe", ".bat", ".cmd", ".scr", ".vbs"};
for (String keyword : suspiciousKeywords) {
if (filename.toLowerCase().contains(keyword)) {
log.warn("检测到可疑文件: {}", filename);
return false;
}
}
}
return true;
}
private String getFileExtension(String filename) {
if (filename == null || !filename.contains(".")) {
return "";
}
return filename.substring(filename.lastIndexOf("."));
}
}
📊 文件处理进度追踪
对于大文件上传,用户体验很重要!没有进度条的上传就像盲人走路,用户会急死的:
@RestController
@RequestMapping("/api/file")
@Slf4j
public class FileProgressController {
private final Map<String, UploadProgress> uploadProgressMap = new ConcurrentHashMap<>();
/**
* 大文件分片上传 - 让大文件上传变得"丝滑"
*/
@PostMapping("/upload/chunk")
public ResponseEntity<Map<String, Object>> uploadChunk(
@RequestParam("file") MultipartFile chunk,
@RequestParam("chunkIndex") int chunkIndex,
@RequestParam("totalChunks") int totalChunks,
@RequestParam("fileId") String fileId,
@RequestParam("filename") String filename) {
Map<String, Object> result = new HashMap<>();
try {
// 创建临时目录存储分片
String tempDir = UPLOAD_PATH + "temp/" + fileId + "/";
File tempDirFile = new File(tempDir);
if (!tempDirFile.exists()) {
tempDirFile.mkdirs();
}
// 保存当前分片
String chunkPath = tempDir + "chunk_" + chunkIndex;
chunk.transferTo(new File(chunkPath));
// 更新上传进度
UploadProgress progress = uploadProgressMap.computeIfAbsent(fileId,
k -> new UploadProgress(totalChunks, filename));
progress.addCompletedChunk(chunkIndex);
log.info("分片上传进度: {}/{} - {}",
progress.getCompletedChunks(), totalChunks, filename);
result.put("success", true);
result.put("chunkIndex", chunkIndex);
result.put("progress", progress.getProgressPercentage());
result.put("message", String.format("分片 %d/%d 上传完成", chunkIndex + 1, totalChunks));
// 检查是否所有分片都已上传完成
if (progress.isComplete()) {
// 合并分片
String finalFilePath = mergeChunks(fileId, filename, totalChunks);
result.put("finalPath", finalFilePath);
result.put("message", "文件上传完成!所有分片已成功合并 🎉");
// 清理临时文件和进度记录
cleanupTempFiles(fileId);
uploadProgressMap.remove(fileId);
}
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("分片上传失败: chunk={}, fileId={}", chunkIndex, fileId, e);
result.put("success", false);
result.put("message", "分片上传失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
}
}
/**
* 获取上传进度
*/
@GetMapping("/upload/progress/{fileId}")
public ResponseEntity<UploadProgress> getUploadProgress(@PathVariable String fileId) {
UploadProgress progress = uploadProgressMap.get(fileId);
if (progress == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(progress);
}
/**
* 合并分片文件
*/
private String mergeChunks(String fileId, String filename, int totalChunks) throws IOException {
String tempDir = UPLOAD_PATH + "temp/" + fileId + "/";
String finalPath = UPLOAD_PATH + "merged/" + filename;
File finalDir = new File(UPLOAD_PATH + "merged/");
if (!finalDir.exists()) {
finalDir.mkdirs();
}
try (FileOutputStream fos = new FileOutputStream(finalPath);
BufferedOutputStream bos = new BufferedOutputStream(fos)) {
for (int i = 0; i < totalChunks; i++) {
String chunkPath = tempDir + "chunk_" + i;
File chunkFile = new File(chunkPath);
try (FileInputStream fis = new FileInputStream(chunkFile);
BufferedInputStream bis = new BufferedInputStream(fis)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
}
}
}
log.info("文件合并完成: {} -> {}", fileId, finalPath);
return finalPath;
}
/**
* 清理临时文件
*/
private void cleanupTempFiles(String fileId) {
try {
String tempDir = UPLOAD_PATH + "temp/" + fileId + "/";
File tempDirFile = new File(tempDir);
if (tempDirFile.exists()) {
File[] files = tempDirFile.listFiles();
if (files != null) {
for (File file : files) {
file.delete();
}
}
tempDirFile.delete();
}
} catch (Exception e) {
log.error("清理临时文件失败: {}", fileId, e);
}
}
/**
* 上传进度实体类
*/
@Data
public static class UploadProgress {
private final int totalChunks;
private final String filename;
private final Set<Integer> completedChunks = new HashSet<>();
private final long startTime = System.currentTimeMillis();
public UploadProgress(int totalChunks, String filename) {
this.totalChunks = totalChunks;
this.filename = filename;
}
public void addCompletedChunk(int chunkIndex) {
completedChunks.add(chunkIndex);
}
public double getProgressPercentage() {
return (double) completedChunks.size() / totalChunks * 100;
}
public boolean isComplete() {
return completedChunks.size() == totalChunks;
}
public long getElapsedTime() {
return System.currentTimeMillis() - startTime;
}
}
}
💡 实战经验分享:那些年踩过的"坑"
🕳️ 坑位一:文件名乱码问题
这个问题我遇到过无数次!特别是中文文件名,在不同浏览器下表现还不一样,简直是噩梦:
/**
* 解决文件名乱码的终极方案
*/
public String getDownloadFilename(String filename, String userAgent) {
try {
if (userAgent.contains("MSIE") || userAgent.contains("Edge")) {
// IE和Edge浏览器
return URLEncoder.encode(filename, "UTF-8").replace("+", "%20");
} else if (userAgent.contains("Firefox")) {
// Firefox浏览器
return "=?UTF-8?B?" + Base64.getEncoder().encodeToString(filename.getBytes("UTF-8")) + "?=";
} else {
// Chrome、Safari等其他浏览器
return URLEncoder.encode(filename, "UTF-8").replace("+", "%20");
}
} catch (UnsupportedEncodingException e) {
log.error("文件名编码失败", e);
return filename;
}
}
🕳️ 坑位二:临时文件清理不及时
有一次我们的服务器磁盘空间突然爆满,排查后发现是临时文件没有及时清理。从那以后我就特别注意这个问题:
@Component
@Slf4j
public class TempFileCleanupScheduler {
@Scheduled(fixedRate = 3600000) // 每小时执行一次
public void cleanupTempFiles() {
String tempPath = UPLOAD_PATH + "temp/";
File tempDir = new File(tempPath);
if (!tempDir.exists()) {
return;
}
long cutoffTime = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(2); // 2小时前
int deletedCount = 0;
File[] tempFiles = tempDir.listFiles();
if (tempFiles != null) {
for (File file : tempFiles) {
if (file.lastModified() < cutoffTime) {
if (deleteRecursively(file)) {
deletedCount++;
}
}
}
}
if (deletedCount > 0) {
log.info("清理过期临时文件: {} 个", deletedCount);
}
}
private boolean deleteRecursively(File file) {
if (file.isDirectory()) {
File[] children = file.listFiles();
if (children != null) {
for (File child : children) {
deleteRecursively(child);
}
}
}
return file.delete();
}
}
🕳️ 坑位三:并发上传导致的文件损坏
在高并发场景下,如果不做好同步处理,很容易出现文件损坏的问题:
@Service
@Slf4j
public class ConcurrentFileService {
private final ReentrantLock uploadLock = new ReentrantLock();
private final Map<String, ReentrantLock> fileLocks = new ConcurrentHashMap<>();
/**
* 线程安全的文件操作
*/
public void safeFileOperation(String fileId, Runnable operation) {
ReentrantLock lock = fileLocks.computeIfAbsent(fileId, k -> new ReentrantLock());
try {
lock.lock();
operation.run();
} finally {
lock.unlock();
// 操作完成后清理锁,避免内存泄露
if (!lock.hasQueuedThreads()) {
fileLocks.remove(fileId);
}
}
}
}
🎯 性能优化:让文件操作"飞"起来
⚡ 异步处理
对于大文件处理,同步操作会阻塞用户请求,体验很糟糕。异步处理是王道:
@Service
@Slf4j
public class AsyncFileService {
@Async("fileTaskExecutor")
public CompletableFuture<String> processFileAsync(MultipartFile file) {
try {
// 模拟耗时的文件处理操作
Thread.sleep(5000);
String processedFilePath = processFile(file);
log.info("异步文件处理完成: {}", processedFilePath);
return CompletableFuture.completedFuture(processedFilePath);
} catch (Exception e) {
log.error("异步文件处理失败", e);
CompletableFuture<String> future = new CompletableFuture<>();
future.completeExceptionally(e);
return future;
}
}
private String processFile(MultipartFile file) {
// 这里可以进行图片压缩、格式转换等操作
return "processed_" + file.getOriginalFilename();
}
@Bean("fileTaskExecutor")
public Executor fileTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
executor.setThreadNamePrefix("FileAsync-");
executor.initialize();
return executor;
}
}
🎉 总结:文件上传下载的"江湖秘籍"
哎呀,不知不觉又写了这么多!😅 回顾一下我们今天聊的这些内容,从基础的文件上传下载,到安全防护,再到性能优化,每一个环节都很重要。
作为一个在代码世界里摸爬滚打了这么多年的老兵,我想说的是:技术永远在进步,但是踏实的基础和细致的思考永远不会过时。SpringBoot确实让文件操作变得简单了很多,但是我们不能因此就掉以轻心。
安全问题要重视,性能优化要考虑,用户体验要关注。每一行代码都可能影响到用户的使用感受,每一个细节都可能成为系统的瓶颈。
希望我的这些经验分享能帮到正在路上的你们。记住,代码如人生,细节决定成败!加油,各位代码侠们!🚀
最后再啰嗦一句:多写代码,多踩坑,多总结。只有经历过的坑,才能真正成为你的经验财富。愿你在文件上传下载的路上,少踩坑,多成长!✨
PS: 如果你在实际开发中遇到了什么奇葩问题,欢迎留言交流。我们一起在代码的海洋里乘风破浪! 🌊
🧧福利赠与你🧧
无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学SpringBoot」,bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门SpringBoot,就像滚雪球一样,越滚越大, 无边无际,指数级提升。
最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。
同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G PDF编程电子书、简历模板、技术文章Markdown文档等海量资料。
ps:本文涉及所有源代码,均已上传至Gitee开源,供同学们一对一参考 Gitee传送门,同时,原创开源不易,欢迎给个star🌟,想体验下被🌟的感jio,非常感谢❗
✨️ Who am I?
我是bug菌,CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云多年度十佳博主及影响力最佳博主,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+;更多精彩福利点击这里;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿。

-End-