參考:https://javajgs.com/archives/26157 一.背景 1-1 需求 前端上傳Word文檔,後端將接收到的Word文檔①上傳到文件伺服器②將Word轉為Pdf。 1-2 方案 因為Word轉Pdf的耗時較長,為了及時給到前端返回信息,在將文件上傳到文件伺服器後,非同步將W ...
參考:https://javajgs.com/archives/26157
一.背景
1-1 需求
前端上傳Word文檔,後端將接收到的Word文檔①上傳到文件伺服器②將Word轉為Pdf。
1-2 方案
因為Word轉Pdf的耗時較長,為了及時給到前端返回信息,在將文件上傳到文件伺服器後,非同步將Word轉為Pdf。
二.實現
創建一個SpringBoot項目。
1 package com.trent.upload.action; 2 3 import com.trent.upload.service.UploadService; 4 import org.springframework.web.bind.annotation.PostMapping; 5 import org.springframework.web.bind.annotation.RequestMapping; 6 import org.springframework.web.bind.annotation.RestController; 7 import org.springframework.web.multipart.MultipartFile; 8 9 import javax.annotation.Resource; 10 11 /** 12 * 上傳文件的Action層 13 * 14 * @author Hutao 15 * @date 2022/8/16 15:10 16 * @since 1.0 17 */ 18 @RequestMapping 19 @RestController 20 public class UploadAction { 21 22 @Resource 23 private UploadService uploadService; 24 25 /** 26 * 文件上傳介面 27 * @param multipartFile 上傳的文件 28 * @return 上傳結果 29 * 30 * @author Hutao 31 * @date 2022/8/16 15:10 32 * @since 1.0 33 */ 34 @PostMapping("/upload") 35 public String upload(MultipartFile multipartFile) { 36 37 uploadService.dealFile(multipartFile); 38 return "上傳成功"; 39 } 40 }
1 package com.trent.upload.service; 2 3 import org.springframework.stereotype.Service; 4 import org.springframework.web.multipart.MultipartFile; 5 6 import java.util.concurrent.ExecutorService; 7 import java.util.concurrent.Executors; 8 import java.util.concurrent.TimeUnit; 9 10 /** 11 * 上傳文件的Service層 12 * 13 * @author Hutao 14 * @date 2022/8/16 15:11 15 * @since 1.0 16 */ 17 @Service 18 public class UploadService { 19 20 /** 21 * 線程池(僅用於簡單演示) 22 */ 23 private static final ExecutorService EXECUTOR_SERVICE = Executors.newCachedThreadPool(); 24 25 /** 26 * 處理上傳的文件 27 * @param multipartFile 上傳的文件 28 * 29 * @author Hutao 30 * @date 2022/8/16 15:13 31 * @since 1.0 32 */ 33 public void dealFile(MultipartFile multipartFile) { 34 // 模擬上傳Word文件到文件伺服器 35 System.out.println("上傳Word文件到文件伺服器"); 36 37 // 非同步將Word文件轉為Pdf 38 EXECUTOR_SERVICE.execute(() -> convertToPdf(multipartFile)); 39 } 40 41 /** 42 * 將文件轉為Pdf 43 * @param multipartFile 待轉換的源文件 44 * 45 * @author Hutao 46 * @date 2022/8/16 15:13 47 * @since 1.0 48 */ 49 public void convertToPdf(MultipartFile multipartFile) { 50 try { 51 52 // 獲取上傳的文件的輸入流,用於轉為Pdf,如果成功獲取到了輸入流,就認為轉Pdf成功 53 multipartFile.getInputStream(); 54 System.out.println("Word轉Pdf成功"); 55 56 } catch (Exception e) { 57 System.out.println("Word轉Pdf失敗"); 58 e.printStackTrace(); 59 } 60 } 61 }
三.問題
3-1 問題描述
以上是一個簡單的演示,在實際的項目中,會偶現如下異常。
上傳Word文件到文件伺服器 Word轉Pdf失敗 java.io.FileNotFoundException: C:\Users\Liujl\AppData\Local\Temp\tomcat.8080.567748920478140755\work\Tomcat\localhost\ROOT\upload_8e27d0a7_9cf4_4f8a_aecc_ea051653749e_00000006.tmp (系統找不到指定的文件。) at java.io.FileInputStream.open0(Native Method) at java.io.FileInputStream.open(FileInputStream.java:195) at java.io.FileInputStream.<init>(FileInputStream.java:138) at org.apache.tomcat.util.http.fileupload.disk.DiskFileItem.getInputStream(DiskFileItem.java:198) at org.apache.catalina.core.ApplicationPart.getInputStream(ApplicationPart.java:100) at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile.getInputStream(StandardMultipartHttpServletRequest.java:254) at com.trent.upload.service.UploadService.convertToPdf(UploadService.java:57) at com.trent.upload.service.UploadService.lambda$dealFile$0(UploadService.java:43) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748)
意思是找不到某個臨時文件。
3-2 問題復現
在UploadService類的51行添加如下代碼,可保證每次上傳文件操作都會出現3-1中的異常。
1 public void convertToPdf(MultipartFile multipartFile) { 2 try { 3 TimeUnit.MILLISECONDS.sleep(10); 4 // 獲取上傳的文件的輸入流,用於轉為Pdf,如果成功獲取到了輸入流,就認為轉Pdf成功 5 multipartFile.getInputStream(); 6 System.out.println("Word轉Pdf成功"); 7 8 } catch (Exception e) { 9 System.out.println("Word轉Pdf失敗"); 10 e.printStackTrace(); 11 } 12 }
3-3 原因分析
1.後臺用MultipartFile接收到前端傳來的文件後,會在本地生成一個臨時文件,以.tmp結尾;
2.MultipartFile對應的臨時文件的生命周期是一個請求會話,會話結束,MultipartFile的臨時文件會被自動清理;
3.因為將文件轉為Pdf的方法是在請求主線程之外的另一個線程中執行的,所以不在請求會話的生命周期內。如果請求會話的主線程結束了(將請求結果返回給前端了),這個請求傳來的MultipartFile的臨時文件就會被清理掉,在將文件轉Pdf的線程中就拿不到MultipartFile對應的臨時文件,也就獲取不到對應的輸入流,故拋出FileNotFoundException。
四.解決方案
主線程在用MultipartFile接收到前端傳來文件後,立即將MultipartFile保存為本地文件。將文件轉Pdf時,使用保存在本地的文件,轉換完成後,刪除本地文件。
註:
1.可使用MultipartFile的transferTo方法將MultipargFile轉為本地文件,但需要註意,transferTo方法被調用後,也會刪除MultipartFile對應的臨時文件;
2.在使用transferTo的時候可能會出現絕對路徑和相對路徑的問題;
3.鑒於1.2中transferTo方法的局限性,建議手動獲取MultipartFile的輸入流,然後寫到本地文件中。可以使用Hutool的FileUtils.copyInputStreamToFile(final InputStream source, final File destination)方法;
4.本地文件使用完成後,務必刪除本地文件,避免伺服器硬碟被占滿。