Recently, my colleague asked me if I had any e-books about technology. I opened the small library on my computer, but the email was too big to send him, and the company prohibited sharing with folders, so I spent half a day writing a small file upload program and deploying it on my Linux machine.
Provide functions: 1. File upload 2. File list display and download
The original uploaded piece is ugly. I wrote some js code to optimize it. The final interface is shown as follows:
First give the results, and below will demonstrate how to implement them step by step.
1. Create a new project
The first thing is to create a new spring-boot project. You can choose to initialize a project on the website or use the Spring Initialier function of the IDE, and you can create a new project. Here I create a new project from IDEA:
Next, then enter group and artifact and continue to click next:
At this time, this module selection interface appears. Click the web option and check the web to prove that this is a webapp. Then click Template Engines to select the front-end template engine. We choose Thymleaf. Spring-boot official also recommends using this template instead of jsp.
The last step, then wait for the project to be initialized successfully.
2.pom settings
First, check which dependencies you need to add to the project and directly post my pom file:
<?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> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>
You can see that spring-boot-starter-thymeleaf contains webapp, and the last two webjars integrate bootstrap and jquery, and other codes will be used.
The last Spring boot maven plugin is added when the system is created, and it has the following benefits:
1. It can package all jars under classpath and build an executable "über-jar" to facilitate users to transfer services.
2. Automatically search for the public static void main() method and mark it as an executable class
3. According to the spring-boot version, a built-in dependency explanation is provided.
3. Upload file controller
If you are just using SpringMVC to upload files, you need to configure a MultipartResolver bean, or configure a <multipart-config> in web.xml, but with the help of spring-boot's automatic configuration, you don't have to do anything. To write the controller class directly, we create a new controller package under src/main/java and create a new 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.steretype.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(); }}The @Controller annotation is added at the class definition, proving that this is a Controller. Each method is added with @GetMapping and @PostMapping corresponding to Get and Post requests respectively.
First of all, @GetMapping("/") , method listUploadedFiles , as the name implies, displays the file list. Here we use storageService to traverse all files in the folder, and use the map method to synthesize the link and file name list, returning an array of Linker objects. The Linker object is a simple pojo, which only contains the following two parts:
private String fileUrl;private String fileName;
This method includes the use of Stream in Java8. If you don’t understand, you can read this article about Java8 features in detail (II) Stream API.
Next is @GetMapping("/files/{filename:.+}") , which is serveFile , which provides file download function, or uses storageservice, and the storageservice code will be posted later. Finally, use ResponseEntity to return the file as a body to the requesting party.
The handleFileUpload of @PostMapping("/") uses Post request to upload files. The parameter @RequestParam("file") extracts the file object in the web page request, or uses storageService to save the object, and finally uses redirect to refresh the web page, and gives the successfully uploaded message.
4. File processing
Many methods called by the Controller above are provided by StorageService. We define an interface that contains the following methods:
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();}Because I am just using the local file system to process long-term downloads of files, I have the following implementation class:
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.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()); }}This class also basically uses Java's NIO, and uses the Path object to define the default saving path for the location for the file.
Let’s look at the store method first. The store accepts a MultipartFile object as a parameter. Compared with traditional JSP, it is just passing binary byte arrays. MultipartFile provides many convenient methods to call, so that we can obtain various information about the uploaded file:
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;} The code uses the Files copy method to copy the file stream to the path corresponding to the location. Of course, we can also use the transferTo method to save the file, file.transferTo(this.rootLocation.resolve(filename).toFile());
The loadAll method loads all file path information in this path, loadAsResource loads the file as a Resource object, then look at the Controller code, and finally accepts a Resource object as a body to return to the requester.
5. Front-end template
Finally, the front-end template is defined, and here we will still look at the code first:
<html xmlns:th="http://www.thymeleaf.org"><head> <title>Share Files</title></head><body><div th:if="${message}"> <h2 th:text="${message}"/></div><div> <form method="POST" action="/" enctype="multipart/form-data"> <!-- COMPONENT START --> <input type="file" name="file" style="visibility:hidden; height:0"/> <div> <div name="Fichier1"> <input type="text" placeholder='Choose a file...'/> <span> <button type="button">Choose</button> </span> </div> </div> <!-- COMPONENT END --> <div> <button type="submit">Submit</button> <button type="reset">Reset</button> </div> </form></div><div> <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>The important thing here is the content in the <form> tag. <form method="POST" action="/" enctype="multipart/form-data"> enctype must be written as multipart/form-data. Use POST to upload files. The original upload control is ugly, so I made a text+input and placed it on the surface, and placed an invisible upload file input below. You can look at the code by yourself, so I won’t talk about it in this article.
Here we also place a list to display the file list. Here we get the linkers object provided by the server. By constantly foreach, we can get the two elements in fileUrl and fileName.
Here, jquery has been replaced with Microsoft's CDN, and webjars can't be introduced, so I don't know why.
Other settings
Set the upload file size limit in src/main/resources/application.properties
spring.http.multipart.max-file-size=128MBspring.http.multipart.max-request-size=128MB
In addition, the default file saving path is also set:
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; }}Note here that due to the StorageProperties setting, you need to add it to the Application class.
@EnableConfigurationProperties annotation@SpringBootApplication@EnableConfigurationProperties(StorageProperties.class)public class UploadApplication { public static void main(String[] args) { SpringApplication.run(UploadApplication.class, args); }}Summarize
The above is the Spring Boot + thymeleaf that the editor introduced to you to realize the file upload and download function. I hope it will be helpful to you. If you have any questions, please leave me a message and the editor will reply to you in time. Thank you very much for your support to Wulin.com website!