补习系列(11)-springboot 文件上传原理
一、文件上傳原理
一個(gè)文件上傳的過程如下圖所示:
Content-Type: multipart/form-data
RFC1867 定義了HTML表單文件上傳的處理機(jī)制。
通常一個(gè)文件上傳的請(qǐng)求內(nèi)容格式如下:
其中boundary指定了內(nèi)容分割的邊界字符串;
Content-dispostion 指定了這是一個(gè)附件(文件),包括參數(shù)名稱、文件名稱;
Content-type 指定了文件類型;
Content-Transfer-Encoding 指定內(nèi)容傳輸編碼;
二、springboot 文件機(jī)制
springboot 的文件上傳處理是基于Servlet 實(shí)現(xiàn)的。
在Servlet 2.5 及早期版本之前,文件上傳需要借助 commons-fileupload 組件來實(shí)現(xiàn)。
從Servlet 3.0規(guī)范之后,提供了對(duì)文件上傳的原生支持,進(jìn)一步簡化了應(yīng)用程序的實(shí)現(xiàn)。
以Tomcat 為例,在文件上傳之后通過將寫入到臨時(shí)文件,最終將文件實(shí)體傳參到應(yīng)用層,如下:
Tomcat 實(shí)現(xiàn)了 Servlet3.0 規(guī)范,通過ApplicationPart對(duì)文件上傳流實(shí)現(xiàn)封裝,
其中,DiskFileItem 描述了上傳文件實(shí)體,在請(qǐng)求解析時(shí)生成該對(duì)象,
需要關(guān)注的是,DiskFileItem 聲明了一個(gè)臨時(shí)文件,用于臨時(shí)存儲(chǔ)上傳文件的內(nèi)容,
SpringMVC 對(duì)上層的請(qǐng)求實(shí)體再次封裝,最終構(gòu)造為MultipartFile傳遞給應(yīng)用程序。
臨時(shí)文件
臨時(shí)文件的路徑定義:
{temp_dir}/upload_xx_xxx.tmptemp_dir是臨時(shí)目錄,通過 系統(tǒng)屬性java.io.tmpdir指定,默認(rèn)值為:
| windows | C:Users{username}AppDataLocalTemp\ |
| Linux | /tmp |
定制配置
為了對(duì)文件上傳實(shí)現(xiàn)定制,可以在application.properties中添加如下配置:
//啟用文件上傳 spring.http.multipart.enabled=true //文件大于該閾值時(shí),將寫入磁盤,支持KB/MB單位 spring.http.multipart.file-size-threshold=0 //自定義臨時(shí)路徑 spring.http.multipart.location= //最大文件大小(單個(gè)) spring.http.multipart.maxFileSize=10MB //最大請(qǐng)求大小(總體) spring.http.multipart.maxRequestSize=10MB其中 maxFileSize/maxRequestSize 用于聲明大小限制,
當(dāng)上傳文件超過上面的配置閾值時(shí),會(huì)返回400(BadRequest)的錯(cuò)誤;
file-size-threshold是一個(gè)閾值,用于控制是否寫入磁盤;
location是存儲(chǔ)的目錄,如果不指定將使用前面所述的默認(rèn)臨時(shí)目錄。
這幾個(gè)參數(shù)由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());// 檢查文件內(nèi)容是否為空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實(shí)體,而后是一系列的檢查動(dòng)作:
包括文件為空、文件后綴、文件大小,這里不做展開。
save 方法實(shí)現(xiàn)了簡單的本地存儲(chǔ),如下:
B. 多文件上傳
與單文件類似,只需要聲明MultipartFile數(shù)組參數(shù)即可:
@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. 文件上傳異常
如前面所述,當(dāng)文件上傳大小超過限制會(huì)返回400錯(cuò)誤,為了覆蓋默認(rèn)的行為,可以這樣:
@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配置的方式,前面提到的幾個(gè)配置也可以這樣實(shí)現(xiàn):
@Configurationpublic static class FileConfig {@Beanpublic MultipartConfigElement multipartConfigElement() {MultipartConfigFactory factory = new MultipartConfigFactory();factory.setMaxFileSize("10MB");factory.setMaxRequestSize("50MB");return factory.createMultipartConfig();}}四、文件下載
既然解釋了文件上傳,自然避免不了文件下載,
文件下載非常簡單,只需要包括下面兩步:
這樣,嘗試寫一個(gè)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);}這段代碼通過參數(shù)(name)來指定訪問文件,之后將流寫入到Response。
接下來,我們訪問一個(gè)確實(shí)存在的文件,看看得到了什么?
...
!! 沒錯(cuò),這就是文件的內(nèi)容,瀏覽器嘗試幫你呈現(xiàn)了。
那么,我們所期望的下載呢? 其實(shí),真實(shí)的下載過程應(yīng)該如下圖:
區(qū)別就在于,我們在返回響應(yīng)時(shí)添加了Content-Disposition頭,用來告訴瀏覽器響應(yīng)內(nèi)容是一個(gè)附件。
這樣根據(jù)約定的協(xié)議,瀏覽器會(huì)幫我們完成響應(yīng)的解析及下載工作。
修改上面的代碼,如下:
繼續(xù)嘗試訪問文件,此時(shí)應(yīng)該能看到文件被正確下載了。
小結(jié)
文件上傳開發(fā)是Web開發(fā)的基礎(chǔ)課,從早期的Servlet + common_uploads組件到現(xiàn)在的SpringBoot,文件的處理已經(jīng)被大大簡化。
這次除了展示SpringBoot 文件上傳的示例代碼之外,也簡單介紹了文件上傳相關(guān)的協(xié)議知識(shí)點(diǎn)。對(duì)開發(fā)者來說,了解一點(diǎn)內(nèi)部原理總是有好處的。
總結(jié)
以上是生活随笔為你收集整理的补习系列(11)-springboot 文件上传原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CentOS7下搭建Nginx+PHP7
- 下一篇: git 修改全局配置