日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

补习系列(11)-springboot 文件上传原理

發(fā)布時(shí)間:2025/7/25 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 补习系列(11)-springboot 文件上传原理 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一、文件上傳原理

一個(gè)文件上傳的過程如下圖所示:

  • 瀏覽器發(fā)起HTTP POST請(qǐng)求,指定請(qǐng)求頭:
    Content-Type: multipart/form-data
  • 服務(wù)端解析請(qǐng)求內(nèi)容,執(zhí)行文件保存處理,返回成功消息。
  • RFC1867 定義了HTML表單文件上傳的處理機(jī)制。
    通常一個(gè)文件上傳的請(qǐng)求內(nèi)容格式如下:

    POST /upload HTTP/1.1 Host:xxx.org Content-type: multipart/form-data, boundary="boundaryStr"--boundaryStr content-disposition: form-data; name="name"Name Of Picture --boundaryStr Content-disposition: attachment; name="picfile"; filename="picfile.gif" Content-type: image/gif Content-Transfer-Encoding: binary...contents of picfile.gif...

    其中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.tmp

    temp_dir是臨時(shí)目錄,通過 系統(tǒng)屬性java.io.tmpdir指定,默認(rèn)值為:

    操作系統(tǒng)路徑
    windowsC: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ǔ),如下:

    private String save(MultipartFile file) {if (!ROOT.isDirectory()) {ROOT.mkdirs();}try {String path = UUID.randomUUID().toString() + getSuffix(file.getOriginalFilename());File storeFile = new File(ROOT, path);file.transferTo(storeFile);return path;} catch (IllegalStateException e) {throw new RuntimeException(e);} catch (IOException e) {throw new RuntimeException(e);}}

    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();}}

    四、文件下載

    既然解釋了文件上傳,自然避免不了文件下載,
    文件下載非常簡單,只需要包括下面兩步:

  • 讀文件流;
  • 輸出到Response;
  • 這樣,嘗試寫一個(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)的解析及下載工作。
    修改上面的代碼,如下:

    @GetMapping(path = "/download")public ResponseEntity<Object> download(@RequestParam("name") String name) throws IOException {if (StringUtils.isEmpty(name)) {return ResponseEntity.badRequest().body("name is empty");}if (!checkName(name)) {return ResponseEntity.badRequest().body("name is illegal");}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().header("Content-Disposition", "attachment;fileName=" + name).contentLength(file.length()).contentType(MediaType.APPLICATION_OCTET_STREAM).body(resource);}

    繼續(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)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。