根据该项目练习可以有效加强Path,Files类方法使用,并通过接口设计理解一个功能的接口拆分思想,和一个项目基本结构的设计思想等等,对初级开发来讲都是很有意义的。自己相对于官网的示例做了一些局部的优化,避免了一些使得示例演示看起来不太舒服小问题。
1.项目依赖
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>uploadfiles</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>uploadfiles</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.example.uploadfiles.UploadfilesApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2.目录结构
3.文件配置
# 应用名称
spring.application.name=uploadfiles
# 应用服务 WEB 访问端口
server.port=8080
# THYMELEAF (ThymeleafAutoConfiguration)
# 开启模板缓存(默认值: true )
spring.thymeleaf.cache=true
# 检查模板是否存在,然后再呈现
spring.thymeleaf.check-template=true
# 检查模板位置是否正确(默认值 :true )
spring.thymeleaf.check-template-location=true
#Content-Type 的值(默认值: text/html )
spring.thymeleaf.content-type=text/html
# 开启 MVC Thymeleaf 视图解析(默认值: true )
spring.thymeleaf.enabled=true
# 模板编码
spring.thymeleaf.encoding=UTF-8
# 要被排除在解析之外的视图名称列表,⽤逗号分隔
spring.thymeleaf.excluded-view-names=
# 要运⽤于模板之上的模板模式。另⻅ StandardTemplate-ModeHandlers( 默认值: HTML5)
spring.thymeleaf.mode=HTML5
# 在构建 URL 时添加到视图名称前的前缀(默认值: classpath:/templates/ )
spring.thymeleaf.prefix=classpath:/templates/
# 在构建 URL 时添加到视图名称后的后缀(默认值: .html )
spring.thymeleaf.suffix=.html
storage.a.location=root
spring.servlet.multipart.max-file-size=100MB
4.核心接口
package com.example.uploadfiles.storage;
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 {
/**
* @Author xiefenghong
* @Description 初始化文件目录
* @Date 13:53 2022/2/24
* @Param []
* @return void
**/
Path init();
/**
* @Author xiefenghong
* @Description 上传文件
* @Date 13:53 2022/2/24
* @Param [file]
* @return void
**/
void store(MultipartFile file);
/**
* @Author xiefenghong
* @Description 获取文件目录下所有文件路径
* @Date 13:53 2022/2/24
* @Param []
* @return java.util.stream.Stream<java.nio.file.Path>
**/
Stream<Path> loadAll();
/*
* @Author xiefenghong
* @Description 获取文件目录下指定文件路径
* @Date 13:54 2022/2/24
* @Param [filename]
* @return java.nio.file.Path
**/
Path load(String filename);
/**
* @Author xiefenghong
* @Description 获取文件目录下指定文件
* @Date 13:55 2022/2/24
* @Param [filename]
* @return org.springframework.core.io.Resource
**/
Resource loadAsResource(String filename);
/**
* @Author xiefenghong
* @Description 删除文件目录下所有文件
* @Date 13:55 2022/2/24
* @Param []
* @return void
**/
void deleteAll();
}
5.配置文件初始化属性——目录路径
package com.example.uploadfiles.storage;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author xiefenghong
* @version V1.0
* @date 2022/2/22
* @description
*/
@ConfigurationProperties(prefix="storage.a",ignoreInvalidFields = true)
@Data
@Component
public class StorageProperties {
private String location ;
}
6.自定义异常类
package com.example.uploadfiles.storage;
/**
* @author xiefenghong
* @version V1.0
* @date 2022/2/22
* @description
*/
public class StorageException extends RuntimeException {
public StorageException(String message){
super(message);
}
public StorageException(String message,Throwable throwable){
super(message,throwable);
}
}
package com.example.uploadfiles.storage;
/**
* @author xiefenghong
* @version V1.0
* @date 2022/2/22
* @description
*/
public class StorageFileNotFoundException extends StorageException {
public StorageFileNotFoundException(String message) {
super(message);
}
public StorageFileNotFoundException(String message, Throwable throwable) {
super(message, throwable);
}
}
7.接口实现类
package com.example.uploadfiles.storage;
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.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
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.util.stream.Stream;
/**
* @author xiefenghong
* @version V1.0
* @date 2022/2/22
* @description
*/
@Service
public class FileSyestemStorageService implements StorageService {
private Path path;
@Autowired
private StorageProperties storageProperties;
@PostConstruct
public void start(){
this.path =Paths.get(storageProperties.getLocation());
}
@Override
public Path init() {
if (Files.notExists(path)){
try {
return Files.createDirectory(path);
} catch (IOException e) {
e.printStackTrace();
}
}
return path;
}
@Override
public void store(MultipartFile file) {
if (file.isEmpty()){
throw new StorageFileNotFoundException("下载的文件为空!");
}
if (!Files.exists(path)){
throw new StorageFileNotFoundException("请先初始化目录!");
}
try {
Files.copy(file.getInputStream(),load(file.getOriginalFilename()));
} catch (IOException e) {
throw new StorageException("文件上传失败!"+e);
}
}
@Override
public Stream<Path> loadAll() {
if (!Files.exists(path)){
return Stream.empty();
}
try {
return Files.walk(this.path,1).filter(path ->!path.equals(this.path))
.map(path -> this.path.relativize(path));
} catch (IOException e) {
throw new StorageException("下载所有文件失败"+e);
}
}
@Override
public Path load(String filename) {
return path.resolve(filename);
}
@Override
public Resource loadAsResource(String filename) {
Path path = load(filename);
try {
UrlResource resource = new UrlResource(path.toUri());
if (!(resource.exists()||resource.isReadable())) {
throw new StorageFileNotFoundException("无法下载该文件:"+filename);
}
return resource;
} catch (MalformedURLException e) {
throw new StorageFileNotFoundException("无法下载该文件:"+filename);
}
}
@Override
public void deleteAll() {
FileSystemUtils.deleteRecursively(path.toFile());
try {
FileSystemUtils.deleteRecursively(path);
} catch (IOException e) {
throw new StorageFileNotFoundException("删除目录失败!");
}
}
}
8.控制层接口
package com.example.uploadfiles;
import com.example.uploadfiles.storage.FileSyestemStorageService;
import com.example.uploadfiles.storage.StorageFileNotFoundException;
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 org.springframework.web.util.UriComponentsBuilder;
import java.nio.file.Path;
import java.util.stream.Collectors;
/**
* @author xiefenghong
* @version V1.0
* @date 2022/2/22
* @description
*/
@Controller
public class FileUploadController {
@Autowired
private FileSyestemStorageService fileService;
/*
* @Author xiefenghong
* @Description 初始化文档根目录
* @Date 15:37 2022/2/23
* @Param []
* @return void
**/
@RequestMapping("init")
public String initDirectory(RedirectAttributes redirectAttributes){
Path path = fileService.init();
redirectAttributes.addFlashAttribute("message","你已成功初始化目录"+path);
return "redirect:/";
}
@GetMapping("/")
public String loadFileHTML(Model model){
model.addAttribute("files",fileService.loadAll().map(path -> {
UriComponentsBuilder serveFile = MvcUriComponentsBuilder.fromMethodName(FileUploadController.class, "serveFile", path.getFileName().toString());
return serveFile.build().toUri().toString();
}).collect(Collectors.toList()));
return "uploadForm";
}
// 正则表达式匹配, 语法: {varName:regex} 前面是变量名,后面式表达式
// 匹配出现过一次或多次.的字符串 如: "xyz.png"
@GetMapping("files/{filename:.+}")
public ResponseEntity serveFile(@PathVariable String filename){
Resource resource = fileService.loadAsResource(filename);
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + filename).body(resource);
}
@PostMapping("/")
public String storeFile(MultipartFile file, RedirectAttributes redirectAttributes){
fileService.store(file);
redirectAttributes.addFlashAttribute("message","你已成功上传文件"+file.getOriginalFilename());
return "redirect:/";
}
@RequestMapping("deleteFileAll")
public String deleteFile(RedirectAttributes redirectAttributes){
fileService.deleteAll();
redirectAttributes.addFlashAttribute("message","你已成功删除所有文件");
return "redirect:/";
}
@ExceptionHandler(StorageFileNotFoundException.class)
public String fileNotFoundExceptionHandler(StorageFileNotFoundException exception, RedirectAttributes redirectAttributes){
redirectAttributes.addFlashAttribute("message","操作异常:"+exception.getMessage());
return "redirect:/";
}
}
9.html页面
<html xmlns:th="https://www.thymeleaf.org">
<body>
<div th:if="${message}">
<h2 th:text="${message}"/>
</div>
<a href="/init">初始化目录</a>
<div>
<form method="POST" enctype="multipart/form-data" action="/">
<table>
<tr><td>File to upload:</td><td><input type="file" name="file" /></td></tr>
<tr><td></td><td><input type="submit" value="Upload" /></td></tr>
</table>
</form>
<form method="POST" action="/deleteFileAll">
<table>
<tr><td></td><td><input type="submit" value="deleteAll" /></td></tr>
</table>
</form>
</div>
<div>
<ul>
<li th:each="file : ${files}">
<a th:href="${file}" th:text="${file}" />
</li>
</ul>
</div>
</body>
</html>