1 overview

Spring Boot uploadfile, according to the official uploadfile example modification, can be put into a war server (I use Tomcat). The main steps are to create exception classes, property classes, interface classes, and controller classes, and finally to package and deploy to the server with a few modifications.

2 the environment

  • win10
  • Tomcat 9.0.30
  • The IDEA of 2019.03
  • Spring boot 2.2.2 RELEASE

3 New Construction project

Choose spring, initializer:

Change the package name. The package option can be jar or WAR. If jar is selected, war can be generated during build.

Here we use the template engine Thymeleaf, choose Spring Web and Thymeleaf.

Click Finish.

4 new package

4 bags, service, the properties, the controller, the exception.

5 exception

Handle two exceptions, respectively storage exception and storage file can not find exception.

5.1 StorageException

package kr.test.exception;

public class StorageException extends RuntimeException
{
    public StorageException(String message)
    {
        super(message);
    }

    public StorageException(String message,Throwable cause)
    {
        super(message,cause); }}Copy the code

5.2 StorageFileNotFoundException

package kr.test.exception;

public class StorageFileNotFoundException extends StorageException
{
    public StorageFileNotFoundException(String message)
    {
        super(message);
    }

    public StorageFileNotFoundException(String message,Throwable cause)
    {
        super(message,cause); }}Copy the code
Exception(String message,Throwable cause);
Copy the code

The cause in this constructor is the exception that caused the exception, and a null value is allowed, which indicates that the exception that caused the exception does not exist or is unknown.

6 properties

Create a new storageProperties.java and set the location of the file to be stored. /.. If nothing is added, a new folder will be created in the project path. Any folder with the same name will be deleted and re-created.

Note the permission issue. When deploying to Tomcat, you may not be able to write files because you do not have the write permission. Make sure that the folder has the write permission.

package kr.test.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("storage")
public class StorageProperties {
    private String location = "upload_dir";
    public String getLocation(a)
    {
        return location;
    }
    
    public void setLocation(String location)
    {
        this.location = location; }}Copy the code

Red, used here @ ConfigurationProperties prompt no @ EnableConfigurationProperties:

Can regardless, later in the Main class add @ EnableConfigurationProperties (StorageProperties. Class).

7 service

Add a StorageService interface:

package kr.test.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(a);
    void store(MultipartFile file);
    Stream<Path> loadAll(a);
    Path load(String filename);
    Resource loadAsResource(String filename);
    void deleteAll(a);
}

Copy the code

Then create a FileSystemStorageService to implement this interface:

package kr.test.service;

import kr.test.exception.StorageException;
import kr.test.exception.StorageFileNotFoundException;
import kr.test.properties.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.io.InputStream;
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 FileSystemStroageService implements StorageService
{
    private final Path rootLocation;

    @Autowired
    public FileSystemStroageService(StorageProperties properties)
    {
        this.rootLocation = Paths.get(properties.getLocation());
    }

    @Override
    public void init(a)
    {
        try {
            Files.createDirectories(rootLocation);
        }
        catch (IOException e)
        {
            throw new StorageException("Could not initialize storage",e); }}@Override
    public void deleteAll(a)
    {
        FileSystemUtils.deleteRecursively(rootLocation.toFile());
    }

    @Override
    public Path load(String filename)
    {
        return rootLocation.resolve(filename);
    }

    @Override
    public Stream<Path> loadAll(a)
    {
        try
        {
            return Files.walk(rootLocation,1) .filter(path -> ! path.equals(rootLocation)) .map(rootLocation::relativize); }catch (IOException e)
        {
            throw new StorageException("Failed to read stored file.",e); }}@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 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(".."))
            {
                throw new StorageException("Cannot store file with relative path outside current directory"+filename);
            }
            try(InputStream inputStream = file.getInputStream()) { Files.copy(inputStream,rootLocation.resolve(filename), StandardCopyOption.REPLACE_EXISTING); }}catch (IOException e)
        {
            throw new StorageException("Failed to store file : "+ filename,e); }}}Copy the code

7.1 the init

@Override
public void init(a)
{
    try {
        Files.createDirectories(rootLocation);
    }
    catch (IOException e)
    {
        throw new StorageException("Could not initialize storage",e); }}Copy the code

Using Java. Nio. File. The Files. CreateDirectories () to create storage directory, can build a multi-level directory.

7.2 deleteAll

@Override
public void deleteAll(a)
{
    FileSystemUtils.deleteRecursively(rootLocation.toFile());
}
Copy the code

Delete files and folders recursively using methods of FileSystemUtils. The parameter is a File.

public static boolean deleteRecursively(File root) 
{
    if(root ! =null && root.exists()) 
    {
        if (root.isDirectory()) 
        {
            File[] children = root.listFiles();
            if(children ! =null) 
            {
                for(File child : children) { deleteRecursively(child); }}}return root.delete();
    }
    return false;
}
Copy the code

First, determine whether the root is empty. If it is not empty, determine whether it is a directory. If it is not a directory, delete it directly.

7.3 the load

@Override
public Path load(String filename) {
	return rootLocation.resolve(filename);
}
Copy the code

Path.resolve(String) returns a Path relative to this, specifically, equal to execution

cd rootLocation
cd filename
pwd
Copy the code

Return the value of PWD.

7.4 loadAll

@Override
public Stream<Path> loadAll(a)
{
    try 
    {
        return Files.walk(rootLocation,1) .filter(path -> ! path.equals(rootLocation)) .map(rootLocation::relativize); }catch (IOException e)
    {
        throw new StorageException("Failed to read stored file.",e); }}Copy the code

Files.walk traverses a directory and returns a Stream<Path> that contains a reference to one or more open directories and is closed when the Stream is closed. The second argument, 1, indicates the maximum depth of traversal.

Filter (rootLocation) filter(rootLocation) filter(rootLocation) filter(rootLocation)

Finally, map, and relativize returns the Path of the parameter relative to the caller, in this case the Path of each Path in the Stream relative to the rootLocation. With Relativize, in any case:

Path a = xxxx;
Path b = xxxx;
Copy the code

There are

a.relativize(a.resolve(b)).equals(b)
Copy the code

Is true.

7.5 loadAsResource

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

The Resource here is org. Springframework. Core. IO. Resource, is an interface that allows access to all kinds of resources, the implementation class have UrlResource, InputStreamResource etc, using the Path here. The toUri () After converting file to Resource, determine whether the source exists or is readable and return it, otherwise the storage file cannot be found exception is thrown.

7.6 store

@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(".."))
        {
            throw new StorageException("Cannot store file with relative path outside current directory"+filename);
        }
        try(InputStream inputStream = file.getInputStream()) { Files.copy(inputStream,rootLocation.resolve(filename), StandardCopyOption.REPLACE_EXISTING); }}catch (IOException e)
    {
        throw new StorageException("Failed to store file : "+ filename,e);
    }
Copy the code

GetOriginalFilename () gets the original file name and then normalizes it with stringutils.cleanPath (). Dispose of “.” and “..” Then determine whether the file is empty and contains a relative path. If not, copy the file using files.copy (). Resolve obtains the value of filename relative to rootLocation. StandardCopyOption has three optional values:

  • ATOMIC_MOVE: Atomic move operation, usually used to move files or directories.
  • COPY_ATTRIBUTES: Copy attributes to retain the attributes of the source file or directory.
  • REPLACE_EXISTING: Replaces an existing file.

8 controller

New FileUploadController.

package kr.test.controller;

import kr.test.exception.StorageFileNotFoundException;
import kr.test.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.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)
    {
        model.addAttribute("files",storageService.loadAll().map(
                path -> MvcUriComponentsBuilder.fromMethodName(FileUploadController.class,
                "serveFile",path.getFileName().toString()).build().toString())
                .collect(Collectors.toList()));
        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 successully uploaded "+file.getOriginalFilename()+"!");
        return "redirect:/";
    }

    @ExceptionHandler(StorageFileNotFoundException.class)
    publicResponseEntity<? > handleStorageFileNotFound(StorageFileNotFoundException e) {returnResponseEntity.notFound().build(); }}Copy the code

8.1 listUploadedFiles

@GetMapping("/")
public String listUploadedFiles(Model model)
{
    model.addAttribute("files",storageService.loadAll().map(
            path -> MvcUriComponentsBuilder.fromMethodName(FileUploadController.class,
            "serveFile",path.getFileName().toString()).build().toString())
            .collect(Collectors.toList()));
    return "uploadForm";
}
Copy the code

@getMapping is a simplified version of @requestMapping (method = requestMethod.get), which maps HTTP GET paths to specific processing methods. The parameter to the method is the Spring MVC Model, which is essentially a Map, and the added key can be used in the view

${key}
Copy the code

Get the value, for example, “files” as key, available in the view

${files}
Copy the code

Get the value.

MvcUriComponentsBuilder can specify urIs for controllers,fromMethod simply calls serveFile() of FileUploadController with path.getfilename ().tost Ring (), since serveFile() returns Stream<Path>, use Stream collect to convert it to List and add it to model. Then return uploadForm, which means this is the name of the view, and look under Resource /templates .

RequestMapping and Model

8.1.1 RequestMapping

You can use @requestMapping () to map urls to classes or specific methods.@RequestMapping common attributes include:

  • Value: specifies the requested URL path. URL templates and regular expressions are supported.
  • Method :HTTP request methods, such as GET,POST,PUT, and DELTE.
  • Consumes: The media types are allowed, for example, Consumes =” Application/JSON “. Content-type corresponding to the HTTP request.
  • Produces: Accepts HTTP requests in relation to the media type, for example, Produces =”application/json”.
  • Params: Request parameters, such as params=”action=update”.
  • Headers: request head.

Spring provides simplified @requestMapping, which provides new annotations to identify HTTP methods:

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • .

So @getMapping is a simplified version of @requestMapping.

8.1.2 Model

You can add variables to a Model that are required by the view. The Model has the following methods:

Model addAttribute(Object value);
Model addAttribute(String name,Object value);
Model addAllAttributes(Map attributes);
Model addAllAttributes(Collection
        attributes);
Model mergeAttributes(Map attributes);
boolean containAttribute(String name);
Copy the code

AddAttribute () adds a variable. For two arguments, use name as the variable name followed by the value. For only one Object, the variable name is the Java variable converted to lowercase class name. AddAttributes () adds multiple variables and overwrites them if they exist, where the argument is Collection<? > adds variable names similar to the naming conventions for addAttribute(Object). MergeAttributes () adds variables, but ignores them if they already exist.

8.2 serveFile

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

@responseBody returns the content instead of the view name because the default is the view name. @responseBody returns the String directly, otherwise it defaults to serialization using Jackson.

The parameter name is the same as the parameter name in GetMapping. The value from the parameter is assigned to the ResponseEntity request body after loading the resource through filename. ResponseEntity is inherited from HttpEntity. Responseentity.ok () is a static method that creates a ResponseEntity with the status “OK” and adds the request header.

HttpHeaders.CONTENT_DISPOSITION,"attachment; filename=\""+file.getFilename()+"\" "
Copy the code

Content_disposition indicates whether the file is opened directly in the browser or downloaded,attachment indicates whether to download, and the file is called file.getfilename ().

8.3 handleFileUpload

@PostMapping("/")
public String handleFileUpload(@RequestParam("file") MultipartFile file,RedirectAttributes redirectAttributes)
{
    storageService.store(file);
    redirectAttributes.addFlashAttribute("message"."You successully uploaded "+file.getOriginalFilename()+"!");
    return "redirect:/";
}
Copy the code

@postmapping () is similar to @getMapping () except that instead of GET, POST.@RequestParam represents the request parameters and contains the name of the request parameters. MultipartFile is used to handle file uploads. RedirectAttributes are used for redirection and can take parameters. RedirectAttributes take two forms with parameters:

addAttribute(String name,Object value);
addFlashAttribute(String name,Object value);
Copy the code

AddAttribute () is equivalent to adding directly to the redirected address

name=value
Copy the code

This will expose the parameters to the redirected address.

AddFlashAttribute () hides the parameters and can only be retrieved from the redirected page. If session is used, the object will be deleted when session jumps to the page. HandleFileUpload first saves the file and then adds a message indicating that it saved successfully. Since redirects in the Controller can return URIs prefixed with “redirect:” or “forward:”, it returns “redirect:/”, redirecting to the root.

8.4 handleStorageFileNotFound

@ExceptionHandler(StorageFileNotFoundException.class)
publicResponseEntity<? > handleStorageFileNotFound(StorageFileNotFoundException e) {return ResponseEntity.notFound().build();
}
Copy the code

@ ExceptionHandler () annotations will deal with the Controller layer throw any exceptions that StorageFileNotFoundException class and its subclasses, ResponseEntity. NotFound () is equivalent to return 404 id code.

9 main

package kr.test;

import kr.test.properties.StorageProperties;
import kr.test.service.StorageService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableConfigurationProperties(StorageProperties.class)
public class TestApplication {

    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }

    @Bean
    CommandLineRunner init(StorageService storageService)
    {
        return(args) -> { storageService.deleteAll(); storageService.init(); }; }}Copy the code

Add to the original

@EnableConfigurationProperties(StorageProperties.class)
Copy the code

with

@Bean
CommandLineRunner init(StorageService storageService)
{
    return (args) ->
    {
        storageService.deleteAll();
        storageService.init();
    };
}
Copy the code

@ EnableConfigurationProperties for Bean with @ ConfigurationProperties annotations to provide effective support, injected with @ the class of the Configuration notes for Spring Bean, here is to make Stora The @configurationProperties for geProperties is in effect, and would be red if it were not:

The @bean annotation is methodically equivalent to < Bean > in Spring’s XML configuration file, registering Bean objects. The CommandLineRunner interface is used after an application is initialized to execute a piece of code logic that is executed only once in the entire application cycle.

10 application.properties

Some environment configuration properties can be set here. Spring Boot allows you to prepare multiple configuration files that you can specify at deployment time to override the default application.properties. Here are the Settings for uploading files:

The default is as follows:

spring.servlet.multipart.enabled=true 
spring.servlet.multipart.file-size-threshold=0
spring.servlet.multipart.location=
spring.servlet.multipart.max-file-size=1MB
spring.servlet.multipart.max-request-size=10MB
spring.servlet.multipart.resolve-lazily=false
Copy the code

File-size-threshold Indicates that temporary files will be written when the length of uploaded files exceeds a certain threshold. The unit is MB or KB. Location indicates the directory for storing temporary files The default value is 1MB. Max-request-size indicates the maximum length for uploading a single HTTP request. The default value is 10MB.

In this case, just make the max-size a little bigger.

11 test

This is a local test. Just go to the IDE and click Run, then open your browser and type:

localhost:8080
Copy the code

12 Deploy the package on Tomcat

Spring Boot is usually packaged as a JAR package or a WAR package. In this case, the war package is deployed to Tomcat.

12.1 Changing the Packaging Mode

In pom.xml, change <packaing> to war:

12.2 Removing Tomcat Dependencies

Add <dependencies> to provided. Pom.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
</dependency>
Copy the code

12.3 Modifying the Main Class

Modify the Main class, let its inheritance SpringBootServletInitializer, overloading the configure (), at the same time the Main () remain the same.

@SpringBootApplication
public class MainClass extends SpringBootServletInitializer
{
	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
	{
		return application.sources(MainClass.class);
	}
	/ / the main ()
}
Copy the code

12.4 Path Problems

This is very important, if set improperly will not be accessible, there are four main paths:

  • action:

  • @GetMapping

  • @PostMapping

  • redirect

12.4.1 action

This is the absolute path, plus the /war project name.

/war Project name/upload pathnameCopy the code

For example, the war project name is Kr and the upload path name is upload.

12.4.2 @ GetMapping

This is the relative path, relative to the path of the current project, without the /war project name.

/ Upload path nameCopy the code

Here is the upload.

12.4.3 @ PostMapping

As with @getMapping, upload the pathname.

/ Upload path nameCopy the code

12.4.4 redirect

This is the path name of the redirection returned, and the relative path, like the last two, is also the upload path name.

/ Upload path nameCopy the code

12.5 Setting the Package Name

Add <finalName> to <build> and specify the packaged WAR name. Note that this should be the same as the war project name above, which is kR.

12.6 the Maven packaging

run

mvn package
Copy the code

Lifecycle can be packaged. For IDEA, Lifecycle can be opened in Maven in the right pane of IDEA and select Package:

12.7 Packaging is complete

The packaged WAR is placed under target by default with a name of <artifactId>+<version>.

12.8 Uploading files to a Server

Key authentication SCP:

scp -i xxxx\id_rsa kr.war username@ip:/usr/local/tomcat/webapps
Copy the code

Put it in the Webapps directory under Tomcat on the server.

Open the Tomcat 12.9

Go to the bin directory of Tomcat:

cd /usr/local/tomcat/bin
./startup.sh
Copy the code

You don’t need to start it if it is running, because it will automatically detect changes in the Webapps directory and unpack the new WAR.

12.10 test

This is similar to the local test, but note that the uploaded folder is in Tomcat /bin. If you want to change it, you can change the location of StorageProperties.

13 source

github

Yards cloud

14 the reference

1.ConfigurationProperties

2.CommandLineRunner

3.RedirectAttribute