最近同事問我有沒有有關于技術的電子書,我打開電腦上的小書庫,但是郵件發給他太大了,公司又禁止用文件夾共享,于是花半天時間寫了個小的文件上傳程序,部署在自己的Linux機器上。
提供功能: 1 .文件上傳 2.文件列表展示以及下載
原有的上傳那塊很丑,寫了點js代碼優化了下,最后界面顯示如下圖:
先給出成果,下面就一步步演示怎么實現。
1.新建項目
首先當然是新建一個spring-boot工程,你可以選擇在網站初始化一個項目或者使用IDE的Spring Initialier功能,都可以新建一個項目。這里我從IDEA新建項目:
下一步,然后輸入group和artifact,繼續點擊next:
這時候出現這個模塊選擇界面,點擊web選項,勾上Web,證明這是一個webapp,再點擊Template Engines選擇前端的模板引擎,我們選擇Thymleaf,spring-boot官方也推薦使用這個模板來替代jsp。
最后一步,然后等待項目初始化成功。
2.pom設置
首先檢查項目需要添加哪些依賴,直接貼出我的pom文件:
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.shuqing28</groupId> <artifactId>upload</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>upload</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.webjars/bootstrap --> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>3.3.5</version> </dependency> <!-- https://mvnrepository.com/artifact/org.webjars.bower/jquery --> <dependency> <groupId>org.webjars.bower</groupId> <artifactId>jquery</artifactId> <version>2.2.4</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>
可以查看到 spring-boot-starter-thymeleaf 包含了webapp,最后兩個webjars整合了bootstrap和jquery,其它的等代碼里用到再說。
最后一個Spring boot maven plugin是系統創建時就添加的,它有以下好處:
1 . 它能夠打包classpath下的所有jar,構建成一個可執行的“über-jar”,方便用戶轉移服務
2 . 自動搜索 public static void main() 方法并且標記為可執行類
3 . 根據spring-boot版本,提供內建的依賴解釋。
3. 上傳文件控制器
如果你只是使用SpringMVC上傳文件,是需要配置一個 MultipartResolver 的bean的,或者在 web.xml 里配置一個 <multipart-config> ,不過借助于spring-boot的自動配置,你什么都不必做。直接寫控制器類,我們在 src/main/java 下新建controller的package,并且新建FileUploadController:
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;@Controllerpublic class FileUploadController { private final StorageService storageService; @Autowired public FileUploadController(StorageService storageService) { this.storageService = storageService; } @GetMapping("/") public String listUploadedFiles(Model model)throws IOException { List<Linker> linkers = 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<Resource> 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 分別相應Get和Post請求。
首先是 @GetMapping("/") ,方法 listUploadedFiles ,顧名思義,顯示文件列表,這里我們借助于storageService遍歷文件夾下的所有文件,并且用map方法提合成了鏈接和文件名列表,返回了一個Linker對象的數組,Linker對象是一個簡單pojo,只包含下面兩部分:
private String fileUrl;private String fileName;
這個方法包含了對Java8中Stream的使用,如果有不理解的可以看看這篇文章 Java8 特性詳解(二) Stream API .
接下來是 @GetMapping("/files/{filename:.+}")
,方法是 serveFile ,該方法提供文件下載功能,還是借助于storageservice,后面會貼出storageservice的代碼。最后使用ResponseEntity,把文件作為body返回給請求方。
@PostMapping("/")
的 handleFileUpload 使用Post請求來上傳文件,參數 @RequestParam("file") 提取網頁請求里的文件對象,還是使用storageService來保存對象,最后使用重定向來刷新網頁,并且給出成功上傳的message。
4. 文件處理
上面Controller調用的很多方法由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); Stream<Path> loadAll(); Path load(String filename); Resource loadAsResource(String filename); void deleteAll();}
因為我這里只是借助于本地文件系統處理文件的長傳下載,所以有了以下實現類:
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;@Servicepublic 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 Stream<Path> loadAll() { 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用于文件的默認保存路徑。
先看 store 方法,store接受一個MultipartFile對象作為參數,想比于傳統JSP中只是傳二進制字節數組,MultipartFile提供了很多方便調用的方法讓我們可以獲取到上傳文件的各項信息:
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對應的Path里,當然我們也可以使用transferTo方法保存文件, file.transferTo(this.rootLocation.resolve(filename).toFile());
loadAll方法加載該路徑下的所有文件Path信息, loadAsResource 則是加載文件為一個Resource對象,再看Controller的代碼,最后是接受一個Resource對象作為body返回給請求方。
5. 前端模板
最后定義了前端模板,這里依舊先看代碼:
<html xmlns:th="http://www.thymeleaf.org"><head> <title>Share Files</title></head><body><div class="col-md-8 col-md-offset-2" th:if="${message}"> <h2 th:text="${message}"/></div><div class="col-md-8 col-md-offset-2"> <form method="POST" action="/" enctype="multipart/form-data"> <!-- COMPONENT START --> <input type="file" name="file" class="input-ghost" style="visibility:hidden; height:0"/> <div class="form-group"> <div class="input-group input-file" name="Fichier1"> <input type="text" class="form-control" placeholder='Choose a file...'/> <span class="input-group-btn"> <button class="btn btn-default btn-choose" type="button">Choose</button> </span> </div> </div> <!-- COMPONENT END --> <div class="form-group"> <button type="submit" class="btn btn-primary pull-right">Submit</button> <button type="reset" class="btn btn-danger">Reset</button> </div> </form></div><div class="col-md-8 col-md-offset-2"> <ul> <li th:each="linker: ${linkers}"> <a th:href="${linker.fileUrl}" rel="external nofollow" th:text="${linker.fileName}" /> </li> </ul></div><script src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.min.js"></script><script src="/webjars/bootstrap/3.3.5/js/bootstrap.min.js"></script><script type="text/javascript" th:inline="javascript"> function bs_input_file() { $(".input-file").before( function() { if ( ! $(this).prev().hasClass('input-ghost') ) { var element = $(".input-ghost"); element.change(function(){ element.next(element).find('input').val((element.val()).split('//').pop()); }); $(this).find("button.btn-choose").click(function(){ element.click(); }); $(this).find("button.btn-reset").click(function(){ element.val(null); $(this).parents(".input-file").find('input').val(''); }); $(this).find('input').css("cursor","pointer"); $(this).find('input').mousedown(function() { $(this).parents('.input-file').prev().click(); return false; }); return element; } } ); } $(function() { bs_input_file(); });</script><link rel="stylesheet" href="/webjars/bootstrap/3.3.5/css/bootstrap.min.css" rel="external nofollow" /></body></html>
這里重要的地方還是 <form> 標簽內的內容, <form method="POST" action="/" enctype="multipart/form-data"> enctype 一定要寫成 multipart/form-data ,使用POST上傳文件,原有的上傳控件很丑,所以做了一個text+input放在表面,在下面放了一個隱形的上傳文件的input,可以自己看看代碼,本文就不啰嗦了。
下面還放了一個list用于展示文件列表,這里我們獲取到服務端提供的linkers對象,不斷foreach就可以獲得里面的兩個元素fileUrl和fileName。
這里jquery換成了微軟的CDN,webjars的總是引入不進來,不知道什么原因。
其它設置
在 src/main/resources/application.properties 里設置上傳文件大小限制
spring.http.multipart.max-file-size=128MBspring.http.multipart.max-request-size=128MB
另外在``還設置了文件默認保存路徑:
package com.shuqing28.uploadfiles.config;import org.springframework.boot.context.properties.ConfigurationProperties;@ConfigurationProperties("storage")public class StorageProperties { private String location = "/home/jenkins/upload-files/"; public String getLocation() { return location; } public void setLocation(String location) { this.location = location; }}
這里注意,由于StorageProperties的設置,在Application的那個類中要添加上
@EnableConfigurationProperties注解@SpringBootApplication@EnableConfigurationProperties(StorageProperties.class)public class UploadApplication { public static void main(String[] args) { SpringApplication.run(UploadApplication.class, args); }}
總結
以上所述是小編給大家介紹的Spring Boot + thymeleaf 實現文件上傳下載功能,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對VeVb武林網網站的支持!
新聞熱點
疑難解答
圖片精選