Java中的MultipartFile接口和File類解讀
一、File類
java.io.File是 Java 標(biāo)準(zhǔn)庫(kù)中用于操作文件和目錄路徑的類。它提供了很多方法,用于創(chuàng)建、刪除、重命名、判斷文件是否存在、獲取文件信息等操作。
獲取文件信息
boolean exists()
: 判斷文件或目錄是否存在。boolean isFile()
: 判斷是否是文件。boolean isDirectory()
: 判斷是否是目錄。String getName()
: 獲取文件或目錄的名稱。String getPath()
: 獲取文件或目錄的路徑。String getAbsolutePath()
: 獲取文件或目錄的絕對(duì)路徑。long length()
: 獲取文件的大?。ㄗ止?jié)數(shù))。
文件和目錄操作
boolean createNewFile()
: 創(chuàng)建新文件。如果文件已存在,則不創(chuàng)建,返回 false。boolean mkdir()
: 創(chuàng)建新目錄。如果目錄已存在,則不創(chuàng)建,返回 false。boolean mkdirs()
: 創(chuàng)建新目錄及其父目錄,如果不存在的話。boolean delete()
: 刪除文件或目錄。
文件路徑操作
boolean renameTo(File dest)
: 重命名文件或目錄。如果成功,返回 true;否則,返回 false。String[] list()
: 返回目錄下的文件和目錄名數(shù)組。File[] listFiles()
: 返回目錄下的文件和目錄的 File 對(duì)象數(shù)組。
文件過濾
String[] list(FilenameFilter filter)
: 返回目錄下滿足指定過濾器條件的文件和目錄名數(shù)組。File[] listFiles(FileFilter filter)
: 返回目錄下滿足指定過濾器條件的文件和目錄的 File 對(duì)象數(shù)組。
二、MultipartFile接口
MultipartFile是 Spring 框架提供的一個(gè)接口,用于表示處理文件上傳的對(duì)象。
它通常用于處理multipart/form-data
類型的請(qǐng)求,例如處理文件上傳的表單。
首先我們依舊可以通過源碼的學(xué)習(xí)來進(jìn)一步了解這個(gè)接口。
2.1 源碼和方法功能
public interface MultipartFile extends InputStreamSource { String getName(); @Nullable String getOriginalFilename(); @Nullable String getContentType(); boolean isEmpty(); long getSize(); byte[] getBytes() throws IOException; InputStream getInputStream() throws IOException; default Resource getResource() { return new MultipartFileResource(this); } void transferTo(File dest) throws IOException, IllegalStateException; default void transferTo(Path dest) throws IOException, IllegalStateException { FileCopyUtils.copy(this.getInputStream(), Files.newOutputStream(dest)); } }
String getName()
:獲取上傳文件的表單字段名稱String getOriginalFilename()
:獲取上傳文件的原始文件名String getContentType()
:獲取上傳文件的內(nèi)容類型boolean isEmpty()
:判斷上傳文件是否為空long getSize()
:獲取上傳文件的大小,單位是字節(jié)byte[] getBytes() throws IOException
:獲取上傳文件的字節(jié)數(shù)組表示InputStream getInputStream() throws IOException
:獲取上傳文件的輸入流default Resource getResource()
:將 MultipartFile 封裝成了 Resource 對(duì)象,從而可以使用 Resource 接口提供的方法來操作上傳文件的內(nèi)容。void transferTo(File dest) throws IOException
,IllegalStateException
:將上傳文件保存到指定的文件;default void transferTo(Path dest) throws IOException
,IllegalStateException
:將上傳文件保存在指定的路徑下;
2.2 void transferTo(File dest)
前面我們已經(jīng)介紹了該方法是Spring中提供的將上傳文件保存到指定的文件中的抽象方法,溯源源碼我們可以看到這個(gè)接口方法被三個(gè)實(shí)現(xiàn)類實(shí)現(xiàn)了,分別是CommonsMultipartFile、MockMultipartFile 和 StandardMultipartHttpServletRequest。
CommonsMultipartFile中的方法體
我們可以看到CommonsMultipartFile中的方法體主要是通過檢測(cè)傳進(jìn)來的文件是否可用、是否存在,并在檢測(cè)完成就執(zhí)行寫入的操作
public void transferTo(File dest) throws IOException, IllegalStateException { if (!this.isAvailable()) { throw new IllegalStateException("File has already been moved - cannot be transferred again"); } else if (dest.exists() && !dest.delete()) { throw new IOException("Destination file [" + dest.getAbsolutePath() + "] already exists and could not be deleted"); } else { try { this.fileItem.write(dest); LogFormatUtils.traceDebug(logger, (traceOn) -> { String action = "transferred"; if (!this.fileItem.isInMemory()) { action = this.isAvailable() ? "copied" : "moved"; } return "Part '" + this.getName() + "', filename '" + this.getOriginalFilename() + "'" + (traceOn ? ", stored " + this.getStorageDescription() : "") + ": " + action + " to [" + dest.getAbsolutePath() + "]"; }); } catch (FileUploadException var3) { throw new IllegalStateException(var3.getMessage(), var3); } catch (IOException | IllegalStateException var4) { throw var4; } catch (Exception var5) { throw new IOException("File transfer failed", var5); } } }
上面這段demo中可能對(duì)于this.isAvailable()有疑問,我們知曉這里的this其實(shí)是該類的實(shí)例化對(duì)象,但是這里的this.isAvailable()就是拿來判斷目的文件是否可用,調(diào)用的就是類的內(nèi)部方法,判斷是否可用的條件就是該目標(biāo)文件是否被加載進(jìn)內(nèi)存中!
這里給出一個(gè)使用該方法的例子,需要注意的是這時(shí)候方法要求的是一個(gè)目標(biāo)File對(duì)象,我們需要在調(diào)用該目標(biāo)方法的時(shí)候就根據(jù)目標(biāo)路徑創(chuàng)建了目標(biāo)的File對(duì)象。
// 獲取上傳文件的原始文件名 String originalFilename = StringUtils.cleanPath(file.getOriginalFilename()); // 構(gòu)建目標(biāo)文件對(duì)象 File destFile = new File("/path/to/destination/directory", originalFilename); try { // 將上傳文件保存到目標(biāo)文件 file.transferTo(destFile); return "File uploaded successfully!"; } catch (IOException e) { e.printStackTrace(); return "Failed to upload the file."; }
StandardMultipartHttpServletRequest實(shí)現(xiàn)類
而另一個(gè)實(shí)現(xiàn)類StandardMultipartHttpServletRequest和CommonsMultipartFile的區(qū)別就在于使用StandardMultipartHttpServletRequest直接上傳文件的話可能會(huì)出現(xiàn)目錄跳躍的問題,而CommonsMultipartFile不會(huì),這是因?yàn)槠鋵?duì)路勁分隔符了相關(guān)的限制。
default void transferTo(Path dest)
該默認(rèn)方法在實(shí)現(xiàn)類中被重寫了,但主要的功能還是不變,就是將上傳的文件寫入到指定路徑的Path對(duì)象中實(shí)現(xiàn)文件上傳的功能。
這里給出使用的示例代碼:
// 構(gòu)建目標(biāo)文件路徑 String uploadDirectory = "/path/to/destination/directory"; String originalFilename = file.getOriginalFilename(); Path filePath = Paths.get(uploadDirectory, originalFilename); try { // 將上傳文件保存到目標(biāo)文件 file.transferTo(filePath); return "File uploaded successfully!"; } catch (IOException e) { e.printStackTrace(); return "Failed to upload the file."; }
實(shí)際項(xiàng)目使用文件上傳和下載
@PostMapping("/file/upload") public Result uploadFile(MultipartFile file) { String originalFilename = file.getOriginalFilename(); if (StrUtil.isBlank(originalFilename)) { return Result.error("文件上傳失敗"); } long flag = System.currentTimeMillis(); String filePath = BASE_FILE_PATH + flag + "_" + originalFilename; try { FileUtil.mkParentDirs(filePath); // 創(chuàng)建父級(jí)目錄 file.transferTo(FileUtil.file(filePath)); Admin currentAdmin = TokenUtils.getCurrentAdmin(); String token = TokenUtils.genToken(currentAdmin.getId().toString(), currentAdmin.getPassword(), 15); String url = "http://localhost:9090/api/book/file/download/" + flag + "?&token=" + token; if (originalFilename.endsWith("png") || originalFilename.endsWith("jpg") || originalFilename.endsWith("pdf")) { url += "&play=1"; } return Result.success(url); } catch (Exception e) { log.info("文件上傳失敗", e); } return Result.error("文件上傳失敗"); } @GetMapping("/file/download/{flag}") public void download(@PathVariable String flag, @RequestParam(required = false) String play, HttpServletResponse response) { OutputStream os; List<String> fileNames = FileUtil.listFileNames(BASE_FILE_PATH); String fileName = fileNames.stream().filter(name -> name.contains(flag)).findAny().orElse(""); // System.currentTimeMillis() + originalFilename try { if (StrUtil.isNotEmpty(fileName)) { String realName = fileName.substring(fileName.indexOf("_") + 1); if ("1".equals(play)) { response.addHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode(realName, "UTF-8")); } else { response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(realName, "UTF-8")); } byte[] bytes = FileUtil.readBytes(BASE_FILE_PATH + fileName); os = response.getOutputStream(); os.write(bytes); os.flush(); os.close(); } } catch (Exception e) { log.error("文件下載失敗", e); } }
文件上傳接口
1.接口設(shè)置
- @postMapping(“file/upload”):這個(gè)注解表明這個(gè)方法處理HTTP POST請(qǐng)求,請(qǐng)求路徑是/file/upload。
- Public Result uploadFile(Multipartfile file):Result是一個(gè)自定義的響應(yīng)對(duì)象,包含了操作成功或操作失敗的狀態(tài),以及相關(guān)數(shù)據(jù)。
2.文件處理
- String originalFilename = file.getOriginalFilename():獲取上傳文件的原始文件名。
- if (StrUtil.isBlank(originalFilename)):檢查文件名是否為空,如果為空,返回錯(cuò)誤響應(yīng)(Result.error)。
- long flag = System.currentTimeMillis():獲取當(dāng)前時(shí)間戳(毫秒),用于創(chuàng)建唯一的文件名。
- String filePath = BASE_FILE_PATH + flag + "_" + originalFilename:構(gòu)建完整的文件保存路徑。BASE_FILE_PATH指存儲(chǔ)文件的根目錄,文件名由時(shí)間戳和原始文件名拼接而成。
3.文件保存
- FileUtil.mkParentDirs(filePath):確保所有父目錄都存在,如果不存在,則創(chuàng)建它們。
- file.transferTo(FileUtil.file(filePath)):這行關(guān)鍵代碼將上傳的文件內(nèi)容保存到服務(wù)器上的filePath路徑。
4.令牌生成(可選)
- Admin currentAdmin = TokenUtils.getCurrentAdmin():獲取當(dāng)前登錄的管理員用戶,這意味著實(shí)現(xiàn)了身份驗(yàn)證。
- String token = TokenUtils.genToken(currentAdmin.getId().toString(), currentAdmin.getPassword(), 15):根據(jù)管理員ID、密碼和15分鐘的過期時(shí)間生成一個(gè)jwt令牌。
5.URL構(gòu)建
- String url = "http://localhost:9090/api/book/file/download/" + flag + "?&token=" + token:構(gòu)建一個(gè)指向下載接口(/api/book/file/download/)的URL,包含時(shí)間戳(flag)和生成的令牌。
- if(originalFilename.endsWith("png")||originalFilename.endsWith("jpg") || originalFilename.endsWith("pdf")):如果是圖片(png、jpg)或pdf文件,則在URL后面添加&play=1,這表示服務(wù)器可能提供文件預(yù)覽功能。
6.響應(yīng)
- return Result.success(url):上傳成功后,返回包含下載的Result.success(url)響應(yīng)
- catch (Exception e):處理過程中可能出現(xiàn)的異常,記錄錯(cuò)誤日志并返回一個(gè)通用的Result.error響應(yīng)。
文件下載接口
1.注解
- (1)@GetMapping("/file/download/{flag}"):使用GetingMapping注解處理Get請(qǐng)求,路徑包含一個(gè)變量{flag},將被捕獲并傳遞給方法。
- (2)@PathVariable String flag:此注解將{flag}路徑變量的值綁定到方法的flag參數(shù)。
- (3)@RequestParam(required = false) String play:此注解將可選查詢參數(shù)”play”的值綁定到方法的play參數(shù)。required=false使此參數(shù)成為可選參數(shù)。
2.方法參數(shù):
- (1)String flag:此參數(shù)將包含作為URL中{flag}傳遞的值。它用于標(biāo)識(shí)要下載的文件。
- (2)String play:此可選參數(shù)(來自查詢字符串)可用于控制下載行為。如果其值為“1”,則文件可能會(huì)直接在瀏覽器中打開(內(nèi)聯(lián))。否則,它將被視為常規(guī)下載。
- (3)HttpServletResponse response:此對(duì)象用于操作發(fā)送回客戶端的HTTP響應(yīng)。
3.文件處理
- (1)List<String> fileNames = FileUtil.listFileNames(BASE_FILE_PATH):此行假設(shè)存在一個(gè)FileUtil類,其中包含一個(gè)listFileNames方法,該方法從BASE_FILE_PATH指定的目錄中檢索文件名列表。
- (2)String fileName = fileNames.stream().filter(name -> name.contains(flag)).findAny().orElse(""):此行使用Java流查找包含提供的flag的文件名。
- (3)String realName = fileName.substring(fileName.indexOf("_") + 1):通過刪除找到的文件名中的任何前綴(第一個(gè)”_”之前)來提取實(shí)際文件名。
響應(yīng)處理
if ("1".equals(play)) { response.addHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode(realName, "UTF-8")); } else { response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(realName, "UTF-8")); }
檢查play參數(shù)的值。如果play為“1”,則將Content-Disposition標(biāo)頭設(shè)置為"inline;filename=" + URLEncoder.encode(realName, "UTF-8"),這通常告訴瀏覽器嘗試直接打開文件。否則,他會(huì)將標(biāo)頭設(shè)置為 "attachment;filename=" + URLEncoder.encode(realName, "UTF-8"),這通常會(huì)強(qiáng)制瀏覽器下載文件。
- URLEncoder.encode(realName, "UTF-8")使用UTF-8編碼對(duì)文件名進(jìn)行編碼,以正確處理文件名中的特殊字符和空格。
- byte[] bytes = FileUtil.readBytes(BASE_FILE_PATH + fileName);使用FileUtil類中的另一個(gè)假設(shè)方法將文件內(nèi)容讀入字節(jié)數(shù)組。
- os = response.getOutputStream():從response對(duì)象獲取輸出流,允許代碼將數(shù)據(jù)直接寫入HTTP響應(yīng)體。
- os.write(bytes);os.flush(); os.close();:將文件內(nèi)容(字節(jié)數(shù)組)寫入輸出流,刷新流以確保發(fā)送所有數(shù)據(jù),然后關(guān)閉流。
- catch (Exception e) { log.error("文件下載失敗", e);}:此catch塊捕獲在文件下載過程中拋出的任何異常,并記錄錯(cuò)誤消息。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java從ftp服務(wù)器上傳與下載文件的實(shí)現(xiàn)
這篇文章主要給大家介紹了關(guān)于Java從ftp服務(wù)器上傳與下載文件的實(shí)現(xiàn)方法,最近項(xiàng)目中需要實(shí)現(xiàn)將文件先存放到ftp上,需要的時(shí)候再?gòu)膄tp上下載,做的過程中碰到了問題,所以這里總結(jié)下,需要的朋友可以參考下2023-08-08java工廠實(shí)例BeanFactoryPostProcessor和BeanPostProcessor區(qū)別分析
這篇文章主要為大家介紹了BeanFactoryPostProcessor和BeanPostProcessor區(qū)別示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07Java中實(shí)現(xiàn)WebSocket方法詳解
這篇文章主要介紹了Java中實(shí)現(xiàn)WebSocket方法詳解,WebSocket?是一種新型的網(wǎng)絡(luò)協(xié)議,它允許客戶端和服務(wù)器之間進(jìn)行雙向通信,可以實(shí)現(xiàn)實(shí)時(shí)數(shù)據(jù)交互,需要的朋友可以參考下2023-07-07SpringBoot如何利用Twilio?Verify發(fā)送驗(yàn)證碼短信
Twilio提供了一個(gè)名為?Twilio?Verify?的服務(wù),專門用于處理驗(yàn)證碼的發(fā)送和驗(yàn)證,下面我們就來看看如何使用Twilio?Verify實(shí)現(xiàn)發(fā)送驗(yàn)證碼短信吧2025-03-03Java代碼實(shí)現(xiàn)哈希表(google 公司的上機(jī)題)
這篇文章主要介紹了Java 哈希表詳解(google 公司的上機(jī)題),本文通過圖文實(shí)例相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03SpringBoot整合FastDFS中間件實(shí)現(xiàn)文件分布管理
FastDFS是一個(gè)開源的輕量級(jí)分布式文件系統(tǒng),它對(duì)文件進(jìn)行管理,功能包括:文件存儲(chǔ)、文件同步、文件上傳、文件下載等,解決了大容量存儲(chǔ)和負(fù)載均衡的問題,本文介紹了SpringBoot整合FastDFS中間件實(shí)現(xiàn)文件分布管理,需要的朋友可以參考下2024-08-08MyBatis-Plus 與Druid 數(shù)據(jù)源操作
SpringBoot框架集成MyBatis-Plus和Druid數(shù)據(jù)源,簡(jiǎn)化了數(shù)據(jù)操作與監(jiān)控,MyBatis-Plus作為MyBatis的增強(qiáng)工具,自動(dòng)實(shí)現(xiàn)CRUD操作,減少手寫SQL,提供分頁(yè)、邏輯刪除等功能,本文介紹MyBatis-Plus & Druid 數(shù)據(jù)源總結(jié),感興趣的朋友一起看看吧2024-09-09MyBatisPlus利用Service實(shí)現(xiàn)獲取數(shù)據(jù)列表
這篇文章主要為大家詳細(xì)介紹了怎樣使用 IServer 提供的 list 方法查詢多條數(shù)據(jù),這些方法將根據(jù)查詢條件獲取多條數(shù)據(jù),感興趣的可以了解一下2022-06-06