Recently a colleague asked me if I had any technical e-books. I opened the small library on my computer, but it was too big to email to him, and the company banned folder sharing, so I spent half a day writing a small file upload program to deploy on my Linux machine.

Provides the following functions: 1. File upload 2. File list display and download

The original uploading piece was very ugly, so I wrote some JS code to optimize it. The final interface display is as follows:

We’ll show you how to do it step by step.

1. Create a project

The first is of course to create a spring-boot project. You can choose to initialize a project on the site or use the IDE’s Spring Initialier function to create a new project. Here I create a new project from IDEA:

Next, enter Group and Artifact and continue by clicking Next:

2. The pom Settings

First check what dependencies need to be added to the project and post my POM file directly:

<? 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 projectforSpring Boot</description> <parent> <groupId>org.springframework.boot</groupId> The < artifactId > spring - the 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> The < 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>Copy the code

You can see that Spring-boot-starter-Thymeleaf includes WebApp. The last two Webjars incorporate Bootstrap and jquery.

The last Spring Boot Maven plugin was added when the system was created and has the following benefits:

1. It packs all the JARS in your CLASspath and builds them into an executable “Uber-Jar” for easy service transfer

2. Automatically search for public static void main() methods and mark them as executable classes

3. Provide built-in dependency explanations based on the Spring-Boot version.

3. Upload the file controller

If you just use SpringMVC to upload files, you will need to configure a MultipartResolver bean, or a

in web.xml, but thanks to spring-boot auto-configuration, you won’t have to do anything. SRC /main/ Java create a package for the controller and create a 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;

@Controller
public 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) {returnResponseEntity.notFound().build(); }}Copy the code

The @Controller annotation is added to the class definition to prove that this is a Controller, and each method is preceded by @getMapping and @postMapping corresponding Get and Post requests, respectively.

The first is @getMapping (“/”). The method listUploadedFiles, as its name implies, displays the file list. Here, we traverse all the files under the folder with the help of storageService, and use the map method to extract the link and file name list. Linker returns an array of Linker objects, a simple POJO containing only the following two parts:

private String fileUrl;
private String fileName;
Copy the code

This method includes the use of Stream in Java8. If you don’t understand it, read this article about the Stream API.

Next is @getMapping (“/files/{filename:.+}”). The method is serveFile, which provides file download function and also uses storageservice. The storageservice code is posted later. Finally, use ResponseEntity to return the file as the body to the requester.

@postMapping (“/”) handleFileUpload uses Post requests to upload files, @requestParam (“file”) extracts file objects from web requests, or storageService stores objects. Finally, a redirection is used to refresh the page and a message that was successfully uploaded is given.

4. Document processing

Many of the methods called by the Controller above are provided by the 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();
}
Copy the code

Since I’m only using the local file system to handle long file downloads, I have the following implementation classes:

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 Stream<Path> loadAll() {
        try {
            returnFiles.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()); }}Copy the code

This class also makes basic use of Java NIO, using the Path object to define the default location for a file.

The store method takes a MultipartFile object as an argument. MultipartFile provides a number of convenient methods to retrieve information about the uploaded file, rather than just passing a binary array of bytes in a traditional JSP:

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;
}
Copy the code

In the code, the copy method of Files is used to copy the file stream to the Path corresponding to location. Of course, we can also use transferTo method to save the file. file.transferTo(this.rootLocation.resolve(filename).toFile());

The loadAll method loads the Path information of all files in the Path. LoadAsResource loads the file as a Resource object. See the Controller code.

5. Front-end templates

Finally, the front-end template is defined. Here we still look at the code:

<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 --> <inputtype="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}" 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;
                   });
                   returnelement; }}); } $(function() {
       bs_input_file();
   });
</script>
<link rel="stylesheet" href="/ webjars/bootstrap / 3.3.5 / CSS/bootstrap. Min. CSS" />
</body>
</html>
Copy the code

The important thing here is what’s inside the

tag, encType must be multipart/form-data. So make a text+input on the surface, put an invisible upload file input below, you can see the code, this article is not worded.

Here we get the linkers object provided by the server. Foreach to get the two elements fileUrl and fileName.

Jquery has been replaced with Microsoft CDN, but WebJars always won’t come in for some reason.

Other Settings

In the SRC/main/resources/application. The properties set upload file size limit

spring.http.multipart.max-file-size=128MB
spring.http.multipart.max-request-size=128MB
Copy the code

Also set the default file save path in ‘ ‘:

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; }}Copy the code

Note here that due to the StorageProperties setting, add to that class in the Application

@ EnableConfigurationProperties annotations

@SpringBootApplication @EnableConfigurationProperties(StorageProperties.class) public class UploadApplication { public static void main(String[] args) { SpringApplication.run(UploadApplication.class, args); }}Copy the code

Speaking of which, the project is basically working, but you can add what you need to improve it.