Spring Boot + thymeleaf 實現檔案上傳下載功能,springthymeleaf

來源:互聯網
上載者:User

Spring Boot + thymeleaf 實現檔案上傳下載功能,springthymeleaf

最近同事問我有沒有有關於技術的電子書,我開啟電腦上的小書庫,但是郵件發給他太大了,公司又禁止用檔案夾共用,於是花半天時間寫了個小的檔案上傳程式,部署在自己的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 實現檔案上傳下載功能,希望對大家有所協助,如果大家有任何疑問請給我留言,小編會及時回複大家的。在此也非常感謝大家對幫客之家網站的支援!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.