本文只是对方志朋大神写过的一篇关于SpringBoot上传文件的一点补充,文章参考链接:SpringBoot非官方教程 | 第十七篇:上传文件
方志朋大神已经写的很好了,把核心代码写了出来思路已经很清晰,只是缺少一些细节,比如FileUploadController类中用到的StorageService 等对于新手来说不知道从哪里来的,上下文也没有提及。而这个类是个服务层类,不是jar里的,是自己手动创建的,下面我就把缺少的这个类给补全,完善一下:
StorageService 服务层接口
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();
}
FileSystemStorageService 服务层实现
这个类主要用来执行一些文件处理操作,没什么特殊的,
比较坑的是,因为用到配置属性,仅仅用Autowired来注入还是不行的, 还必须使用 @EnableConfigurationProperties(StorageProperties.class) 来声明配置属性类,这个比较坑。。
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;
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;
@Service //声明service
@EnableConfigurationProperties(StorageProperties.class) // 允许使用配置注解
public class FileSystemStorageService implements StorageService {
private final Path rootLocation;
@Autowired
public FileSystemStorageService(StorageProperties properties) {
this.rootLocation = Paths.get(properties.getLocation());
}
@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);
}
// 从 file 输入流复制到目标位置
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 {
// 通过给定的目录和深度来遍历,返回 Stream (集合中包含给定的路径)
// filter 过滤掉指定的路径
// map 将路径处理为相对路径,如: rootLocation = "a/b" path = "a/b/c/img.png" relativize 后,结果为 "c/img.png"
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) {
// 组合一个新的 Path 对象, 如: filename = "gus" rootLocation="a/b", 执行后结果为 "a/b/gus"
return rootLocation.resolve(filename);
}
@Override
public Resource loadAsResource(String filename) {
try {
Path file = load(filename);
// file.toUri 将 Path 转换为 uri
// 如: path = "upload-dir/1.jpg" toUrl 后结果为 "file:///home/maiyo/project/upload-files/upload-dir/1.jpg"
// 通过 UrlResource 创建一个 Srping Resource 对象
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());
}
@Override
public void init() {
try {
// 创建上传目录
Files.createDirectories(rootLocation);
} catch (IOException e) {
throw new StorageException("Could not initialize storage", e);
}
}
}
StorageException 自定义异常类
//自定义异常类
public class StorageException extends RuntimeException {
public StorageException(String message) {
super(message);
}
public StorageException(String message, Throwable cause) {
super(message, cause);
}
}
StorageFileNotFoundException 自定义异常类,继承自 StorageException
// 自定义异常类
public class StorageFileNotFoundException extends StorageException {
public StorageFileNotFoundException(String message) {
super(message);
}
public StorageFileNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
属性配置
使用@ConfigurationProperties声明这是一个属性配置类,参数声明顶级命名空间。
比较坑的是, 需要在pom.xml里引入以下依赖:
<dependency>
<groupId> org.springframework.boot </groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional> true </optional>
</dependency>
然后就可以正常使用了。。。
@ConfigurationProperties("storage")
public class StorageProperties {
/**
* Folder location for storing files
*/
private String location = "upload-dir";
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}
以上配置完成后,可以在application.properties 文件里添加 storage.location = myFiles来指明文件存储路径。。。
其它坑
在学习的过程中可能会出现 “Re-run spring boot configuration annotation”的错误,这个就是因为在service中只用Autowired去自动注入了,而没有使用 EnableConfigurationProperties 去声明属性配置类。 像上面说的一样声明就可以正常运行了