自建云盘(1):Spring boot 文件上传下载
版本
名称 | 版本 |
---|---|
jdk | 1.8.2 |
spring boot | 2.7.2 |
knife4j | 3.0.3 |
swagger | 3.0.0 |
hutool | 5.8.3 |
maven依赖
<?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.jingjing</groupId>
<artifactId>jingjing-cloud</artifactId>
<version>1.0-SNAPSHOT</version>
<description>京京云盘</description>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<log4j2.version>2.18.0</log4j2.version>
<slf4j-log4j12.version>1.7.36</slf4j-log4j12.version>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
<relativePath/>
</parent>
<dependencies>
<!--spring boot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--工具-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.3</version>
</dependency>
</dependencies>
</project>
文件上传
接口层
@Slf4j
@Validated
@Api(tags = "文件上传")
@RestController
@RequestMapping("upload")
public class UploadController extends BaseController {
@Resource
FileService fileService;
@PutMapping(value = "/file", headers = "content-type=multipart/form-data")
@ApiOperationSupport(order = 1)
@ApiOperation(value = "上传", response = Result.class)
@ApiImplicitParam(name = "file", value = "上传的文件", dataTypeClass = MultipartFile.class, required = true)
public Result<Void> upload(@NotNull(message = "目标文件不能为空") @RequestPart MultipartFile file) {
fileService.uploadFile(file);
return resultSuccess();
}
}
service实现
@Value("${jingjing.file.localBasePath}")
public String baseFilePath;
@Override
public void uploadFile(String path, InputStream inputStream) {
String uploadPath = this.getSystemPath(path);
// 如果目录不存在则创建
String parentPath = FileUtil.getParent(uploadPath, 1);
if (!FileUtil.exist(parentPath)) {
FileUtil.mkdir(parentPath);
}
File tempFile = new File(uploadPath);
BufferedOutputStream outputStream = FileUtil.getOutputStream(tempFile);
IoUtil.copy(inputStream, outputStream);
}
@Override
public void uploadFile(MultipartFile file) {
String name = file.getOriginalFilename();
long size = file.getSize();
log.info("上传文件,文件名={},文件大小={}", name, size);
try {
this.uploadFile(name, file.getInputStream());
} catch (IOException e) {
log.error("文件上传时,解析失败", e);
throw new JingJingException(JingJingErrorCodeEnmu.FILE_PARSE_ERROR);
}
}
关于IO的核心代码,选择使用工具类实现,没有选择自己实现,自己写IO的实现,并不会比现有的工具类好很多,并且需要维护大量的代码。 这里使用的工具方法是: cn.hutool.core.io.IoUtil#copy(java.io.InputStream, java.io.OutputStream); 如果后期发现使用的工具方法有性能问题或者漏洞时,再选择自己重写或更换工具类。
完成后端的接口开发后,同样使用工具来进行基本的调试。此项目选择swagger,但并不是原生的swagger,而是二次封装的knife4j,在配置上,与swagger略有不同,尤其是上传下载类的接口。此处swagger配置可见Controller方法的注解。
验证使用
文件下载
接口层
@Slf4j
@Validated
@Api(tags = "文件上传")
@RestController
@RequestMapping("download")
public class DownloadController extends BaseController {
@Resource
FileService fileService;
/**
* 文件下载*
* 文件流下线
* @param path 文件路径
*/
@ApiOperationSupport(order = 1)
@ApiOperation(value = "下载文件到本地")
@GetMapping("file/{path}")
@ResponseBody
public ResponseEntity<org.springframework.core.io.Resource> downloadFile(@PathVariable("path") String path) {
log.info("文件下载:{}", path);
return fileService.downloadFile(path);
}
}
service实现
@Override
public ResponseEntity<Resource> downloadFile(String path) {
File file = new File(this.getSystemPath(path));
if (!file.exists()) {
throw new JingJingException(JingJingErrorCodeEnmu.FILE_NO_EXISTS);
}
HttpHeaders headers = new HttpHeaders();
String fileName = file.getName();
headers.setContentDispositionFormData("attachment", StringUtils.encodeAllIgnoreSlashes(fileName));
return ResponseEntity
.ok()
.headers(headers)
.contentLength(file.length())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new FileSystemResource(file));
}
此处的实现,没有使用response直接写出去,而选择了spring的工具类org.springframework.http.ResponseEntity。
优化点
文件下载的逻辑是根据文件的绝对路径找到文件,并通过流的形式返回给用户。 此处的实现是直接传递文件路径,不利于信息安全。
可在上传的时候,根据文件的路径,转换成文件唯一标识的映射,映射关系需要借助mysql实现,下载文件时,根据文件的唯一标识获取。
验证使用
关于下载,可以使用swagger进行调试,也可以使用浏览器的get请求直接下载。如图:
文件预览
借助静态资源服务器实现文件预览
可使用nginx搭建一个静态资源服务器,实现文件预览的功能,比较简单好用 操作链接:https://www.cnblogs.com/chenglc/p/16607579.html
效果
文件夹
文件预览
读取磁盘目录
正常读取磁盘目录和文件列表,是根据用户的维度,查询数据库中存储的数据。此处不再展示。
对于云盘的绝对管理员,可以读取磁盘的文件列表。
接口层
@Api(tags = "存储资源")
@Slf4j
@RestController
@RequestMapping("storage")
public class StorageSourceController {
@Resource
StorageSourceService service;
/**
* 读取文件列表*
*
* @param path
* @return
*/
@ApiOperationSupport(order = 1)
@ApiOperation(value = "读取磁盘文件", response = Result.class)
@GetMapping("readPhysicsFiles")
public Result<FileVo> readPhysicsFiles(String path) {
log.info("读取文件列表{}", path);
FileVo vo = service.readFile(path);
return new Result<>(vo);
}
}
servie实现
@Value("${jingjing.file.localBasePath}")
public String baseFilePath;
@Override
public FileVo readFile(String path) {
if (!path.startsWith(baseFilePath)) {
throw new JingJingException(JingJingErrorCodeEnmu.NOT_AUTHORIZED_TO_READ);
}
File file = new File(path);
FileVo vo = buildFileVoByFile(file);
vo.setSubFiles(Arrays.stream(Objects.requireNonNull(file.listFiles()))
.map(this::buildFileVoByFile).collect(Collectors.toList()));
return vo;
}
private final FileVo buildFileVoByFile(File file) {
return FileVo.builder()
.diskPath(file.getPath())
.name(file.getName())
.isDirectory(file.isDirectory())
.parentPath(file.getName())
.path(file.getPath().replace(baseFilePath,""))
.size(file.length())
.build();
}