SpringBoot2FileUpload(SpringBoot2文件上傳)

前言

FileUpload文件上傳是開發中經常遇到的事,通常都是網上copy一段代碼來上傳,可是你的代碼足夠完善嗎,可以應對日益增長的文件需求嗎,可以同時當上傳和下載服務器嗎,今天讓我們來跟着Spring官方Uploading Files教程進行優化和改造文件上傳服務器(適應於少量文件上傳,量大請使用DFS)。

項目結構

核心代碼如下:

  • application.yml
  • StorageController
  • StorageService&StorageServiceImpl
  • StorageExcetpion&StorageFileNotFoundExcetpion
  • HTML
    在這裏插入圖片描述
    Application.yml

server:
  port: 9999
  servlet:
      context-path: /fileupload
tomcat:
    remote-ip-header: x-forward-for
    uri-encoding: UTF-8
    max-threads: 10
    background-processor-delay: 30
spring:
    http:
      encoding:
        force: true
        charset: UTF-8
    application:
        name: spring-cloud-study-fileupload
    freemarker:
        request-context-attribute: request
        #prefix: /templates/
        suffix: .html
        content-type: text/html
        enabled: true
        cache: false
        charset: UTF-8
        allow-request-override: false
        expose-request-attributes: true
        expose-session-attributes: true
        expose-spring-macro-helpers: true
        #template-loader-path: classpath:/templates/
    servlet:
      multipart:
        max-file-size: 10MB
        max-request-size: 10MB
    file:
      devPath: C:\workspace\Temp\
      prodPath: /dev/fileupload/

Exception

  • StorageException
public class StorageException extends RuntimeException {

	private static final long serialVersionUID = 1L;

	public StorageException(String message) {
        super(message);
    }

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

  • StorageFileNotFoundException
public class StorageFileNotFoundException extends StorageException {

	private static final long serialVersionUID = 1L;

	public StorageFileNotFoundException(String message) {
        super(message);
    }

    public StorageFileNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
}

Service

  • Interface 接口
public interface StorageService {
    void init();
    void store(MultipartFile file);
    Stream<Path> loadAll();
    Path load(String filename);
    Resource loadAsResource(String filename);
    void deleteAll();
    Path getPath();
}

  • Implement 實現

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;

import org.springframework.beans.factory.annotation.Value;
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 com.softdev.system.demo.entity.StorageException;
import com.softdev.system.demo.entity.StorageFileNotFoundException;
/**
 * FileUpload Service
 * @author zhengkai.blog.csdn.net
 * */
@Service
public class StorageServiceImpl implements StorageService {
	//從application.yml中讀取
	@Value("${spring.file.devPath}")
	private String devPath;
	//從application.yml中讀取
	@Value("${spring.file.prodPath}")
	private String prodPath;
	//請勿直接使用path,應該用getPath()
	private Path path;

	@Override
	public Path getPath() {
		if(path==null) {
			//如果在Window下,用dev路徑,如果在其他系統,則用生產環境路徑prodPath by zhengkai.blog.csdn.net
			if(System.getProperty("os.name").toLowerCase().startsWith("win")) {
				path = Paths.get(devPath);
			}else {
				path = Paths.get(prodPath);
			}
		}
		return path;
	}


	@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);
			}
			try (InputStream inputStream = file.getInputStream()) {
				Files.copy(inputStream, getPath().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(getPath(), 1)
					.filter(path -> !path.equals(getPath()))
					.map(getPath()::relativize);
		}
		catch (IOException e) {
			throw new StorageException("Failed to read stored files", e);
		}

	}

	@Override
	public Path load(String filename) {
		return getPath().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(getPath().toFile());
	}

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

StorageController

文件存儲控制器包含以下REST API方法:

  • GET / 文件上傳頁面,基於Bootstrap+Freemarker,通過storageService.loadAll()瀏覽存儲目錄文件功能,通過MvcUriComponentsBuilder.fromMethodName映射文件提供下載功能(關於該功能更多詳情請見附錄部分)。
  • GET /files/{filename} 文件下載URL,如果文件存在則下載,然後返回"Content-Disposition:attachment; filename=/.../"的響應頭Response Header,在瀏覽器中觸發文件下載。
  • POST / 文件上傳方法,通過StorageService.store(file)提供上傳功能。

import java.io.IOException;
import java.util.stream.Collectors;

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.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.alibaba.fastjson.JSON;
import com.softdev.system.demo.entity.StorageFileNotFoundException;
import com.softdev.system.demo.service.StorageService;


@RestController
@RequestMapping("/storage")
/**
 * SpringBoot2FileUpload文件上傳
 * @author zhengkai.blog.csdn.net
 * */
public class StorageController {

	@Autowired
	private StorageService storageService;

	@GetMapping("/files")
	public ModelAndView listUploadedFiles(ModelAndView modelAndView) throws IOException {
		//返回目錄下所有文件信息
		modelAndView.addObject("files", storageService.loadAll().map(
				path -> MvcUriComponentsBuilder.fromMethodName(StorageController.class,
						"serveFile", path.getFileName().toString()).build().toString())
				.collect(Collectors.toList()));
		//返回目錄信息
		modelAndView.addObject("path",storageService.getPath());
		modelAndView.setViewName("uploadForm");
		//查看ModelAndView包含的內容
		System.out.println(JSON.toJSONString(modelAndView));
		return modelAndView;
	}

	@GetMapping("/files/{filename:.+}")
	@ResponseBody
	public ResponseEntity<Resource> serveFile(@PathVariable String filename) {
		//加載文件
		Resource file = storageService.loadAsResource(filename);
		//attachment附件下載模式,直接下載文件
		return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,
				"attachment; filename=\"" + file.getFilename() + "\"").body(file);
	}

	@PostMapping("/files")
	public ModelAndView handleFileUpload(@RequestParam("file") MultipartFile file,
			RedirectAttributes redirectAttributes) {
		//存儲文件
		storageService.store(file);
		//返回成功消息
		redirectAttributes.addFlashAttribute("message",
				"恭喜你,文件" + file.getOriginalFilename() + "上傳成功!");
		return new ModelAndView("redirect:/storage/files");
	}

	@ExceptionHandler(StorageFileNotFoundException.class)
	public ResponseEntity<?> handleStorageFileNotFound(StorageFileNotFoundException exc) {
		return ResponseEntity.notFound().build();
	}

}

Html

<html>
<head>
	<meta charset="utf-8">
	
	<!-- Bootstrap 4 -->
    <link href="//cdn.staticfile.org/twitter-bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet">
    <!-- jQuery -->
    <script src="//cdn.staticfile.org/jquery/3.3.1/jquery.min.js"></script>
    <!-- Bootstrap -->
    <script src="//cdn.staticfile.org/twitter-bootstrap/4.1.1/js/bootstrap.min.js"></script>
</head>
<body>
	<#if message??>
		${message!!}
	</#if>

	<div>
		
		<form method="POST" enctype="multipart/form-data" action="${request.contextPath}/storage/files">
			<table>
				<tr><td><input value="選擇文件" type="file" name="file" class="btn btn-primary"/></td></tr>
				<tr><td><input type="submit" value="上傳" class="btn btn-primary"/></td></tr>
			</table>
		</form>
		
	</div>
	<div>
		<p>存儲目錄${path!!}有以下文件,可點擊下載:</p>
		<div class="list-group">
		   <#list files as file>
		     <a href="${file!!}" class="list-group-item list-group-item-action">${file!!}</a>
		   </#list >
		</div>
	</div>

</body>
</html>

效果展示

http://localhost:9999/fileupload/storage/files

在這裏插入圖片描述

UriComponentsBuilder.fromMethodName

SpringMvc4提供的新功能,MvcUriComponentsBuilder官方DOC文檔,功能如下:

方法 功能
UriComponentsBuilder.fromMethodName(UriComponentsBuilder builder, Class<?> controllerType, String methodName, Object… args) 通過Controller(控制器名或類)和Method(是方法名不是mapping名),映射該方法到URL上面

例如上文DEMO中
MvcUriComponentsBuilder.fromMethodName(StorageController.class,"serveFile", path.getFileName().toString())

代表
http://domain:post(application.yml的server.port) +
/basePath(application.yml的server.servlet.context-path) +
/Controller(根據控制器名或類找到對應的Mapping名) +
/Method的Mapping(例如“serveFile”method的mapping是@GetMapping("/files/{filename:.+}"),則附加上Object... args所有的參數進去作爲參數,獲得最終的url)

得到最終訪問該方法的url。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章