小編給大家分享一下Spring Boot + thymeleaf如何實現(xiàn)文件上傳下載功能,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
成都創(chuàng)新互聯(lián)公司專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于成都網(wǎng)站設(shè)計、網(wǎng)站制作、臨淄網(wǎng)絡(luò)推廣、微信小程序定制開發(fā)、臨淄網(wǎng)絡(luò)營銷、臨淄企業(yè)策劃、臨淄品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運營等,從售前售中售后,我們都將竭誠為您服務(wù),您的肯定,是我們最大的嘉獎;成都創(chuàng)新互聯(lián)公司為所有大學(xué)生創(chuàng)業(yè)者提供臨淄建站搭建服務(wù),24小時服務(wù)熱線:18980820575,官方網(wǎng)址:www.cdcxhl.com
最近同事問我有沒有有關(guān)于技術(shù)的電子書,我打開電腦上的小書庫,但是郵件發(fā)給他太大了,公司又禁止用文件夾共享,于是花半天時間寫了個小的文件上傳程序,部署在自己的Linux機(jī)器上。
提供功能: 1 .文件上傳 2.文件列表展示以及下載
原有的上傳那塊很丑,寫了點js代碼優(yōu)化了下,最后界面顯示如下圖:
先給出成果,下面就一步步演示怎么實現(xiàn)。
1.新建項目
首先當(dāng)然是新建一個spring-boot工程,你可以選擇在網(wǎng)站初始化一個項目或者使用IDE的Spring Initialier功能,都可以新建一個項目。這里我從IDEA新建項目:
下一步,然后輸入group和artifact,繼續(xù)點擊next:
這時候出現(xiàn)這個模塊選擇界面,點擊web選項,勾上Web,證明這是一個webapp,再點擊Template Engines選擇前端的模板引擎,我們選擇Thymleaf,spring-boot官方也推薦使用這個模板來替代jsp。
最后一步,然后等待項目初始化成功。
2.pom設(shè)置
首先檢查項目需要添加哪些依賴,直接貼出我的pom文件:
4.0.0 com.shuqing28 upload 0.0.1-SNAPSHOT jar upload Demo project for Spring Boot org.springframework.boot spring-boot-starter-parent 1.5.9.RELEASE UTF-8 UTF-8 1.8 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-configuration-processor true org.springframework.boot spring-boot-starter-test test org.webjars bootstrap 3.3.5 org.webjars.bower jquery 2.2.4 org.springframework.boot spring-boot-maven-plugin
可以查看到 spring-boot-starter-thymeleaf 包含了webapp,最后兩個webjars整合了bootstrap和jquery,其它的等代碼里用到再說。
最后一個Spring boot maven plugin是系統(tǒng)創(chuàng)建時就添加的,它有以下好處:
1 . 它能夠打包classpath下的所有jar,構(gòu)建成一個可執(zhí)行的“über-jar”,方便用戶轉(zhuǎn)移服務(wù)
2 . 自動搜索 public static void main() 方法并且標(biāo)記為可執(zhí)行類
3 . 根據(jù)spring-boot版本,提供內(nèi)建的依賴解釋。
3. 上傳文件控制器
如果你只是使用SpringMVC上傳文件,是需要配置一個 MultipartResolver 的bean的,或者在 web.xml 里配置一個
package com.shuqing28.upload.controller; import com.shuqing28.uploadfiles.pojo.Linker; import com.shuqing28.uploadfiles.exceptions.StorageFileNotFoundException; import com.shuqing28.uploadfiles.service.StorageService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import java.io.IOException; import java.util.List; import java.util.stream.Collectors; @Controller public class FileUploadController { private final StorageService storageService; @Autowired public FileUploadController(StorageService storageService) { this.storageService = storageService; } @GetMapping("/") public String listUploadedFiles(Model model)throws IOException { Listlinkers = storageService.loadAll().map( path -> new Linker(MvcUriComponentsBuilder.fromMethodName(FileUploadController.class, "serveFile", path.getFileName().toString()).build().toString(), path.getFileName().toString()) ).collect(Collectors.toList()); model.addAttribute("linkers", linkers); return "uploadForm"; } @GetMapping("/files/{filename:.+}") @ResponseBody public ResponseEntity serveFile(@PathVariable String filename) { Resource file = storageService.loadAsResource(filename); return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"").body(file); } @PostMapping("/") public String handleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) { storageService.store(file); redirectAttributes.addFlashAttribute("message", "You successfully uploaded " + file.getOriginalFilename() + "!"); return "redirect:/"; } @ExceptionHandler(StorageFileNotFoundException.class) public ResponseEntity> handleStorageFileNotFound(StorageFileNotFoundException exc) { return ResponseEntity.notFound().build(); } }
類定義處添加了 @Controller 注解,證明這是一個Controller,每個方法前添加了 @GetMapping 和 @PostMapping 分別相應(yīng)Get和Post請求。
首先是 @GetMapping("/") ,方法 listUploadedFiles ,顧名思義,顯示文件列表,這里我們借助于storageService遍歷文件夾下的所有文件,并且用map方法提合成了鏈接和文件名列表,返回了一個Linker對象的數(shù)組,Linker對象是一個簡單pojo,只包含下面兩部分:
private String fileUrl; private String fileName;
這個方法包含了對Java8中Stream的使用,如果有不理解的可以看看這篇文章 Java8 特性詳解(二) Stream API .
接下來是 @GetMapping("/files/{filename:.+}")
,方法是 serveFile ,該方法提供文件下載功能,還是借助于storageservice,后面會貼出storageservice的代碼。最后使用ResponseEntity,把文件作為body返回給請求方。
@PostMapping("/")
的 handleFileUpload 使用Post請求來上傳文件,參數(shù) @RequestParam("file") 提取網(wǎng)頁請求里的文件對象,還是使用storageService來保存對象,最后使用重定向來刷新網(wǎng)頁,并且給出成功上傳的message。
4. 文件處理
上面Controller調(diào)用的很多方法由StorageService提供,我們定義一個接口,包含以下方法:
package com.shuqing28.uploadfiles.service; import org.springframework.core.io.Resource; import org.springframework.web.multipart.MultipartFile; import java.nio.file.Path; import java.util.stream.Stream; public interface StorageService { void init(); void store(MultipartFile file); StreamloadAll(); Path load(String filename); Resource loadAsResource(String filename); void deleteAll(); }
因為我這里只是借助于本地文件系統(tǒng)處理文件的長傳下載,所以有了以下實現(xiàn)類:
package com.shuqing28.uploadfiles.service; import com.shuqing28.uploadfiles.exceptions.StorageException; import com.shuqing28.uploadfiles.exceptions.StorageFileNotFoundException; import com.shuqing28.uploadfiles.config.StorageProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import org.springframework.stereotype.Service; import org.springframework.util.FileSystemUtils; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.net.MalformedURLException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.stream.Stream; @Service public class FileSystemStorageService implements StorageService { private final Path rootLocation; @Autowired public FileSystemStorageService(StorageProperties properties) { this.rootLocation = Paths.get(properties.getLocation()); } @Override public void init() { try { Files.createDirectories(rootLocation); } catch (IOException e) { throw new StorageException("Could not initialize storage", e); } } @Override public void store(MultipartFile file) { String filename = StringUtils.cleanPath(file.getOriginalFilename()); try { if (file.isEmpty()) { throw new StorageException("Failed to store empty file" + filename); } if (filename.contains("..")) { // This is a security check throw new StorageException( "Cannot store file with relative path outside current directory " + filename); } Files.copy(file.getInputStream(), this.rootLocation.resolve(filename), StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { throw new StorageException("Failed to store file" + filename, e); } } @Override public StreamloadAll() { try { return Files.walk(this.rootLocation, 1) .filter(path -> !path.equals(this.rootLocation)) .map(path->this.rootLocation.relativize(path)); } catch (IOException e) { throw new StorageException("Failed to read stored files", e); } } @Override public Path load(String filename) { return rootLocation.resolve(filename); } @Override public Resource loadAsResource(String filename) { try { Path file = load(filename); Resource resource = new UrlResource(file.toUri()); if (resource.exists() || resource.isReadable()) { return resource; } else { throw new StorageFileNotFoundException( "Could not read file: " + filename); } } catch (MalformedURLException e) { throw new StorageFileNotFoundException("Could not read file: " + filename, e); } } @Override public void deleteAll() { FileSystemUtils.deleteRecursively(rootLocation.toFile()); } }
這個類也基本運用了Java的NIO,使用Path對象定義了location用于文件的默認(rèn)保存路徑。
先看 store 方法,store接受一個MultipartFile對象作為參數(shù),想比于傳統(tǒng)JSP中只是傳二進(jìn)制字節(jié)數(shù)組,MultipartFile提供了很多方便調(diào)用的方法讓我們可以獲取到上傳文件的各項信息:
public interface MultipartFile extends InputStreamSource { String getName(); String getOriginalFilename(); String getContentType(); boolean isEmpty(); long getSize(); byte[] getBytes() throws IOException; InputStream getInputStream() throws IOException; void transferTo(File dest) throws IOException, IllegalStateException; }
代碼里使用了Files的copy方法把文件流拷到location對應(yīng)的Path里,當(dāng)然我們也可以使用transferTo方法保存文件, file.transferTo(this.rootLocation.resolve(filename).toFile());
loadAll方法加載該路徑下的所有文件Path信息, loadAsResource 則是加載文件為一個Resource對象,再看Controller的代碼,最后是接受一個Resource對象作為body返回給請求方。
5. 前端模板
最后定義了前端模板,這里依舊先看代碼:
Share Files
這里重要的地方還是