补习系列(11)-springboot 文件上传原理
一、文件上傳原理
一個文件上傳的過程如下圖所示:
Content-Type: multipart/form-data
RFC1867 定義了HTML表單文件上傳的處理機制。
通常一個文件上傳的請求內容格式如下:
其中boundary指定了內容分割的邊界字符串;
Content-dispostion 指定了這是一個附件(文件),包括參數名稱、文件名稱;
Content-type 指定了文件類型;
Content-Transfer-Encoding 指定內容傳輸編碼;
二、springboot 文件機制
springboot 的文件上傳處理是基于Servlet 實現的。
在Servlet 2.5 及早期版本之前,文件上傳需要借助 commons-fileupload 組件來實現。
從Servlet 3.0規范之后,提供了對文件上傳的原生支持,進一步簡化了應用程序的實現。
以Tomcat 為例,在文件上傳之后通過將寫入到臨時文件,最終將文件實體傳參到應用層,如下:
Tomcat 實現了 Servlet3.0 規范,通過ApplicationPart對文件上傳流實現封裝,
其中,DiskFileItem 描述了上傳文件實體,在請求解析時生成該對象,
需要關注的是,DiskFileItem 聲明了一個臨時文件,用于臨時存儲上傳文件的內容,
SpringMVC 對上層的請求實體再次封裝,最終構造為MultipartFile傳遞給應用程序。
臨時文件
臨時文件的路徑定義:
{temp_dir}/upload_xx_xxx.tmptemp_dir是臨時目錄,通過 系統屬性java.io.tmpdir指定,默認值為:
| windows | C:Users{username}AppDataLocalTemp\ |
| Linux | /tmp |
定制配置
為了對文件上傳實現定制,可以在application.properties中添加如下配置:
//啟用文件上傳 spring.http.multipart.enabled=true //文件大于該閾值時,將寫入磁盤,支持KB/MB單位 spring.http.multipart.file-size-threshold=0 //自定義臨時路徑 spring.http.multipart.location= //最大文件大小(單個) spring.http.multipart.maxFileSize=10MB //最大請求大小(總體) spring.http.multipart.maxRequestSize=10MB其中 maxFileSize/maxRequestSize 用于聲明大小限制,
當上傳文件超過上面的配置閾值時,會返回400(BadRequest)的錯誤;
file-size-threshold是一個閾值,用于控制是否寫入磁盤;
location是存儲的目錄,如果不指定將使用前面所述的默認臨時目錄。
這幾個參數由SpringMVC控制,用于注入 Servlet3.0 的文件上傳配置,如下:
public class MultipartConfigElement {private final String location;// = "";private final long maxFileSize;// = -1;private final long maxRequestSize;// = -1;private final int fileSizeThreshold;// = 0;三、示例代碼
接下來以簡單的代碼展示文件上傳處理
A. 單文件上傳
@PostMapping(value = "/single", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE }, produces = MediaType.TEXT_PLAIN_VALUE)@ResponseBodypublic ResponseEntity<String> singleUpload(@RequestParam("file") MultipartFile file) {logger.info("file receive {}", file.getOriginalFilename());// 檢查文件內容是否為空if (file.isEmpty()) {return ResponseEntity.badRequest().body("no file input");}// 原始文件名String fileName = file.getOriginalFilename();// 檢查后綴名if (!checkImageSuffix(fileName)) {return ResponseEntity.badRequest().body("the file is not image");}// 檢查大小if (!checkSize(file.getSize())) {return ResponseEntity.badRequest().body("the file is too large");}String name = save(file);URI getUri = ServletUriComponentsBuilder.fromCurrentContextPath().path("/file/get").queryParam("name", name).build(true).toUri();return ResponseEntity.ok(getUri.toString());}在上面的代碼中,我們通過Controller方法傳參獲得MultipartFile實體,而后是一系列的檢查動作:
包括文件為空、文件后綴、文件大小,這里不做展開。
save 方法實現了簡單的本地存儲,如下:
B. 多文件上傳
與單文件類似,只需要聲明MultipartFile數組參數即可:
@PostMapping(value = "/multi", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE }, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)@ResponseBodypublic ResponseEntity<List<String>> multiUpload(@RequestParam("file") MultipartFile[] files) {logger.info("file receive count {}", files.length);List<String> uris = new ArrayList<String>();for (MultipartFile file : files) {C. 文件上傳異常
如前面所述,當文件上傳大小超過限制會返回400錯誤,為了覆蓋默認的行為,可以這樣:
@ControllerAdvice(assignableTypes = FileController.class)public class MultipartExceptionHandler {@ExceptionHandler(MultipartException.class)public ResponseEntity<String> handleUploadError(MultipartException e) {return ResponseEntity.badRequest().body("上傳失敗:" + e.getCause().getMessage());}}D. Bean 配置
SpringBoot 提供了JavaBean配置的方式,前面提到的幾個配置也可以這樣實現:
@Configurationpublic static class FileConfig {@Beanpublic MultipartConfigElement multipartConfigElement() {MultipartConfigFactory factory = new MultipartConfigFactory();factory.setMaxFileSize("10MB");factory.setMaxRequestSize("50MB");return factory.createMultipartConfig();}}四、文件下載
既然解釋了文件上傳,自然避免不了文件下載,
文件下載非常簡單,只需要包括下面兩步:
這樣,嘗試寫一個Controller方法:
@GetMapping(path = "/get")public ResponseEntity<Object> get(@RequestParam("name") String name) throws IOException {...File file = new File(ROOT, name);if (!file.isFile()) {return ResponseEntity.notFound().build();}if (!file.canRead()) {return ResponseEntity.status(HttpStatus.FORBIDDEN).body("no allow to access");}Path path = Paths.get(file.getAbsolutePath());ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(path));return ResponseEntity.ok().contentLength(file.length()).body(resource);}這段代碼通過參數(name)來指定訪問文件,之后將流寫入到Response。
接下來,我們訪問一個確實存在的文件,看看得到了什么?
...
!! 沒錯,這就是文件的內容,瀏覽器嘗試幫你呈現了。
那么,我們所期望的下載呢? 其實,真實的下載過程應該如下圖:
區別就在于,我們在返回響應時添加了Content-Disposition頭,用來告訴瀏覽器響應內容是一個附件。
這樣根據約定的協議,瀏覽器會幫我們完成響應的解析及下載工作。
修改上面的代碼,如下:
繼續嘗試訪問文件,此時應該能看到文件被正確下載了。
小結
文件上傳開發是Web開發的基礎課,從早期的Servlet + common_uploads組件到現在的SpringBoot,文件的處理已經被大大簡化。
這次除了展示SpringBoot 文件上傳的示例代碼之外,也簡單介紹了文件上傳相關的協議知識點。對開發者來說,了解一點內部原理總是有好處的。
總結
以上是生活随笔為你收集整理的补习系列(11)-springboot 文件上传原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CentOS7下搭建Nginx+PHP7
- 下一篇: WPF自定义控件(四)の自定义控件