前言
来啦老铁!
笔者学习Spring Boot有一段时间了,附上Spring Boot系列学习文章,欢迎取阅、赐教:
- 5分钟入手Spring Boot;
- Spring Boot数据库交互之Spring Data JPA;
- Spring Boot数据库交互之Mybatis;
- Spring Boot视图技术;
- Spring Boot之整合Swagger;
- Spring Boot之junit单元测试踩坑;
- 如何在Spring Boot中使用TestNG;
- Spring Boot之整合logback日志;
- Spring Boot之整合Spring Batch:批处理与任务调度;
- Spring Boot之整合Spring Security: 访问认证;
- Spring Boot之整合Spring Security: 授权管理;
- Spring Boot之多数据库源:极简方案;
- Spring Boot之使用MongoDB数据库源;
- Spring Boot之多线程、异步:@Async;
- Spring Boot之前后端分离(一):Vue前端;
- Spring Boot之前后端分离(二):后端、前后端集成;
- Spring Boot之前后端分离(三):登录、登出、页面认证;
- Spring Boot之面向切面编程:Spring AOP;
- Spring Boot之集成Redis(一):Redis初入门;
- Spring Boot之集成Redis(二):集成Redis;
- Spring Boot之集成Redis(三):Spring Cache + Redis;
- Spring Boot之文件上传、下载;
- Spring Boot中文乱码问题解决方案:亲测有效;
在之前的文章《Spring Boot之文件上传、下载》中,我们一起学会了如何使用Spring Boot制作文件服务器,轻松实现了文件的传输(上传、下载)。今天我打算将其扩展一下,我们一起来实现一个简易的:个人云盘!
实现之前,我们先找一个对标的产品,比如:百度网盘:
百度网盘百度网盘的整体功能还是比较简单的,主要有几部分:
- 文件类型分类模块;
- 文件、文件夹列表模块;
- 文件、文件夹操作模块;
- 鉴权、用户中心模块;
- 文件搜索、展示模块;
- 云盘容量管理模块;
- 引流模块;
百度网盘因为涉及到商业化,所以需要一些商业化功能,而我们做的功能则不涉及到这个,咱们就做个精简版的,自己私底下用用就行了,不是咱不想做,是咱也做不到商业化啊!
在我的印象中网盘、云盘基本都是在亏钱的,这里可以聊一下我经历过的一个故事:
4年前在厦门某公司的时候,当时云盘正火,我所在的公司就做了一个云盘APP,提供给用户免费存放图片、视频,刚开始希望很美好,可是上线了之后,现实很骨感,我们发现用户数据增长太快,文件服务器很快就撑不住了,同时也没有办法快速变现(用户可选择的免费云盘产品太多了),印象中不到一个月,该app就下线了~
因此,我们剔除那些花里胡哨的,做点简单的:
- 文件、文件夹列表模块;
- 简易文件、文件夹操作模块;
- 简易用户鉴权模块;
- 简易容量管理模块;
- 简易文件搜索模块;
截至目前的代码已上传Git Hub仓库,欢迎取阅:
-
https://github.com/dylanz666/spring-boot-cloud-storage
前后端在同一个仓库中分文件夹:
整体步骤
- 技术栈阐述;
- 前端设计;
- 网站鉴权功能;
- 后端文件夹相关功能API;
- 后端文件相关功能API;
- 后端容量信息获取功能;
- 前后端集成及演示;
1. 技术栈阐述;
- 前端:Vue.js + Element;
- 后端:Spring Boot;
- 文件服务器:个人电脑文件系统(若要发布到正式环境,可再修改文件系统);
- 数据库:暂不考虑;
- 其他:无;
2. 前端设计;
(目前同时运行前后端代码可以登录、查看主页面)。
登录页面 云盘主页面3. 网站鉴权功能;
整个网站代码基础来自于之前的实践,网站鉴权更是直接拿来使用,只是我把所有人的初始密码设置为:123。
- 当时的文章为:Spring Boot之前后端分离(三):登录、登出、页面认证;
- 当时的前端代码:https://github.com/dylanz666/spring-boot-vue-frontend
- 当时的后端代码:https://github.com/dylanz666/spring-boot-vue-backend
接下来我先在后端粗略编写一些API,这些API还未与前端集成,到集成阶段也许需要调整、增减哦~
4. 后端文件夹相关功能API;
package com.github.dylanz666.controller;
import com.alibaba.fastjson.JSONArray;
import com.github.dylanz666.constant.APIStatus;
import com.github.dylanz666.domain.FolderInformation;
import com.github.dylanz666.domain.FolderOperationResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.io.File;
import java.util.Arrays;
import java.util.Objects;
/**
* @author : dylanz
* @since : 11/15/2020
*/
@RestController
@RequestMapping("/api/folder")
public class FolderOperationController {
@Value(value = "${file.root.dir}")
private String rootDir;
/**
* 创建文件夹
*
* @param folderName:文件夹名
*/
@PostMapping("")
public FolderOperationResponse createFolder(@RequestParam String folderName) {
FolderOperationResponse folderOperationResponse = new FolderOperationResponse();
File newFolder = new File(rootDir, folderName);
try {
if (newFolder.exists()) {
folderOperationResponse.setStatus(APIStatus.FAIL.toString());
folderOperationResponse.setMessage("The target folder name is already exist.");
folderOperationResponse.setFolderName(folderName);
return folderOperationResponse;
}
newFolder.mkdir();
folderOperationResponse.setStatus(APIStatus.SUCCESS.toString());
folderOperationResponse.setMessage(APIStatus.SUCCESS.toString());
folderOperationResponse.setFolderName(folderName);
} catch (Exception e) {
e.printStackTrace();
folderOperationResponse.setStatus(APIStatus.FAIL.toString());
folderOperationResponse.setMessage(e.getMessage());
folderOperationResponse.setFolderName(folderName);
}
return folderOperationResponse;
}
/**
* 删除文件夹
*
* @param folderName 文件夹名
* @param all 是否递归删除文件夹下文件
*/
@DeleteMapping("")
public FolderOperationResponse deleteFolder(@RequestParam String folderName, @RequestParam String all) {
FolderOperationResponse folderOperationResponse = new FolderOperationResponse();
File folder = new File(rootDir, folderName);
try {
if (!folder.exists()) {
folderOperationResponse.setStatus(APIStatus.FAIL.toString());
folderOperationResponse.setMessage("The folder is not exist.");
folderOperationResponse.setFolderName(folderName);
return folderOperationResponse;
}
if (!folder.isDirectory()) {
folderOperationResponse.setStatus(APIStatus.FAIL.toString());
folderOperationResponse.setMessage("This is not a folder.");
folderOperationResponse.setFolderName(folderName);
return folderOperationResponse;
}
if (all.equalsIgnoreCase("Y") || all.equalsIgnoreCase("yes") || all.equalsIgnoreCase("true")) {
deleteFolder(rootDir + "\\" + folderName);
folderOperationResponse.setStatus(APIStatus.SUCCESS.toString());
folderOperationResponse.setMessage(APIStatus.SUCCESS.toString());
folderOperationResponse.setFolderName(folderName);
return folderOperationResponse;
}
if (Objects.requireNonNull(folder.listFiles()).length > 0) {
folderOperationResponse.setStatus(APIStatus.FAIL.toString());
folderOperationResponse.setMessage("This is not a empty folder, if you still want to delete it, please pass a parameter: all, and it's value to be Y or Yes.");
folderOperationResponse.setFolderName(folderName);
return folderOperationResponse;
}
folder.delete();
folderOperationResponse.setStatus(APIStatus.SUCCESS.toString());
folderOperationResponse.setMessage(APIStatus.SUCCESS.toString());
folderOperationResponse.setFolderName(folderName);
} catch (Exception e) {
e.printStackTrace();
folderOperationResponse.setStatus(APIStatus.FAIL.toString());
folderOperationResponse.setMessage(e.getMessage());
folderOperationResponse.setFolderName(folderName);
}
return folderOperationResponse;
}
/**
* 更新除文件夹名字
*
* @param folderInformation 文件夹信息
*/
@PutMapping("")
public FolderOperationResponse updateFolder(@RequestBody FolderInformation folderInformation) {
FolderOperationResponse folderOperationResponse = new FolderOperationResponse();
String currentFolderName = folderInformation.getCurrentFolderName();
String targetFolderName = folderInformation.getTargetFolderName();
File currentFolder = new File(rootDir, currentFolderName);
File targetFolder = new File(rootDir, targetFolderName);
try {
if (targetFolder.exists()) {
folderOperationResponse.setStatus(APIStatus.FAIL.toString());
folderOperationResponse.setMessage("The target folder name is already exist.");
folderOperationResponse.setFolderName(targetFolderName);
return folderOperationResponse;
}
if (currentFolder.exists()) {
currentFolder.renameTo(targetFolder);
folderOperationResponse.setStatus(APIStatus.SUCCESS.toString());
folderOperationResponse.setMessage(APIStatus.SUCCESS.toString());
folderOperationResponse.setFolderName(targetFolderName);
return folderOperationResponse;
}
folderOperationResponse.setStatus(APIStatus.FAIL.toString());
folderOperationResponse.setMessage("The folder is not exist.");
folderOperationResponse.setFolderName(currentFolderName);
} catch (Exception e) {
e.printStackTrace();
folderOperationResponse.setStatus(APIStatus.FAIL.toString());
folderOperationResponse.setMessage(e.getMessage());
folderOperationResponse.setFolderName(currentFolderName);
}
return folderOperationResponse;
}
/**
* 验证文件夹是否存在
*
* @param folderName 文件夹名
*/
@GetMapping("")
public FolderOperationResponse getFolder(@RequestParam String folderName) {
FolderOperationResponse folderOperationResponse = new FolderOperationResponse();
File folder = new File(rootDir, folderName);
try {
if (!folder.exists()) {
folderOperationResponse.setStatus(APIStatus.FAIL.toString());
folderOperationResponse.setMessage("The folder is not exist.");
folderOperationResponse.setFolderName(folderName);
return folderOperationResponse;
}
if (folder.isDirectory()) {
folderOperationResponse.setStatus(APIStatus.FAIL.toString());
folderOperationResponse.setMessage("This is not a folder.");
folderOperationResponse.setFolderName(folderName);
return folderOperationResponse;
}
folderOperationResponse.setStatus(APIStatus.SUCCESS.toString());
folderOperationResponse.setMessage("The folder is exist.");
folderOperationResponse.setFolderName(folderName);
} catch (Exception e) {
e.printStackTrace();
folderOperationResponse.setStatus(APIStatus.FAIL.toString());
folderOperationResponse.setMessage(e.getMessage());
folderOperationResponse.setFolderName(folderName);
}
return folderOperationResponse;
}
/**
* 获取文件夹下的内容
*
* @param folderName 文件夹名
* @param all 是否包含所有内容,是则获取包含文件夹的所有内容,否则只获取文件
*/
@GetMapping("/children")
public FolderOperationResponse getFiles(@RequestParam String folderName, @RequestParam String all) {
FolderOperationResponse folderOperationResponse = new FolderOperationResponse();
try {
File folder = new File(rootDir, folderName);
if (folder.exists()) {
String[] fileList = folder.list();
JSONArray list = new JSONArray();
assert fileList != null;
if (all.equalsIgnoreCase("Y") || all.equalsIgnoreCase("yes") || all.equalsIgnoreCase("true")) {
list.addAll(Arrays.asList(fileList));
folderOperationResponse.setStatus(APIStatus.SUCCESS.toString());
folderOperationResponse.setMessage(APIStatus.SUCCESS.toString());
folderOperationResponse.setFolderName(folderName);
folderOperationResponse.setFiles(list);
return folderOperationResponse;
}
for (String item : fileList) {
File sub = new File(rootDir, folderName + "\\" + item);
if (sub.isDirectory()) {
list.add(item);
}
}
folderOperationResponse.setStatus(APIStatus.SUCCESS.toString());
folderOperationResponse.setMessage(APIStatus.SUCCESS.toString());
folderOperationResponse.setFolderName(folderName);
folderOperationResponse.setFiles(list);
return folderOperationResponse;
}
folderOperationResponse.setStatus(APIStatus.FAIL.toString());
folderOperationResponse.setMessage("The folder is not exist.");
folderOperationResponse.setFolderName(folderName);
folderOperationResponse.setFiles(new JSONArray());
} catch (Exception e) {
e.printStackTrace();
folderOperationResponse.setStatus(APIStatus.FAIL.toString());
folderOperationResponse.setMessage(e.getMessage());
folderOperationResponse.setFolderName(folderName);
folderOperationResponse.setFiles(new JSONArray());
}
return folderOperationResponse;
}
private void deleteFolder(String path) {
File folder = new File(path);
if (!folder.isFile()) {
File[] files = folder.listFiles();
if (files != null) {
for (File f : files) {
deleteFolder(f.getAbsolutePath());
}
}
}
folder.delete();
}
}
5. 后端文件相关功能API;
package com.github.dylanz666.controller;
import com.github.dylanz666.constant.APIStatus;
import com.github.dylanz666.domain.FileInformation;
import com.github.dylanz666.domain.FileOperationResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.List;
import java.util.Objects;
/**
* @author : dylanz
* @since : 11/02/2020
*/
@RestController
@RequestMapping("/api/file")
public class FileOperationController {
@Value(value = "${file.root.dir}")
private String rootDir;
private FileInputStream fis = null;
private BufferedInputStream bis = null;
/**
* 向系统增加文件(上传)
*
* @param folderName 上传目标子文件夹
* @param file 上传的文件
*/
@PostMapping("")
public FileOperationResponse upload(@RequestParam String folderName, @RequestParam("file") MultipartFile file) {
FileOperationResponse fileOperationMessage = new FileOperationResponse();
String fileName = file.getOriginalFilename();
assert fileName != null;
try {
File dest = new File(rootDir, folderName + "\\" + fileName);
//当文件父文件夹不存在,则创建该文件夹
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdir();
}
file.transferTo(dest);
fileOperationMessage.setStatus(APIStatus.SUCCESS.toString());
fileOperationMessage.setMessage(APIStatus.SUCCESS.toString());
fileOperationMessage.setFolderName(folderName);
fileOperationMessage.setFileName(fileName);
} catch (Exception e) {
e.printStackTrace();
fileOperationMessage.setStatus(APIStatus.FAIL.toString());
fileOperationMessage.setMessage(e.getMessage());
fileOperationMessage.setFolderName(folderName);
fileOperationMessage.setFileName(fileName);
}
return fileOperationMessage;
}
/**
* 批量向系统增加文件(上传)
*
* @param folderName 上传目标子文件夹
* @param files 上传的文件们,调用API时,form-data中可传多个file名字的文件
*/
@PostMapping("/batch")
public FileOperationResponse uploadFiles(@RequestParam String folderName, @RequestParam("file") List<MultipartFile> files) {
FileOperationResponse fileOperationMessage = new FileOperationResponse();
StringBuilder responseFileName = new StringBuilder();
for (MultipartFile file : files) {
String fileName = file.getOriginalFilename();
assert fileName != null;
responseFileName.append(fileName).append(";");
try {
File dest = new File(rootDir, folderName + "\\" + fileName);
//当文件父文件夹不存在,则创建该文件夹
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdir();
}
file.transferTo(dest);
} catch (Exception e) {
e.printStackTrace();
fileOperationMessage.setStatus(APIStatus.FAIL.toString());
fileOperationMessage.setMessage(e.getMessage());
fileOperationMessage.setFolderName(folderName);
fileOperationMessage.setFileName(responseFileName.toString());
}
fileOperationMessage.setStatus(APIStatus.SUCCESS.toString());
fileOperationMessage.setMessage(APIStatus.SUCCESS.toString());
fileOperationMessage.setFolderName(folderName);
fileOperationMessage.setFileName(responseFileName.toString());
}
return fileOperationMessage;
}
/**
* 删除系统中的文件
*
* @param folderName 要删除的文件的目标文件夹
* @param fileName 要删除的文件
*/
@DeleteMapping("")
public FileOperationResponse delete(@RequestParam String folderName, @RequestParam String fileName) {
FileOperationResponse fileOperationMessage = new FileOperationResponse();
File file = new File(rootDir, folderName + "\\" + fileName);
try {
if (file.exists()) {
file.delete();
fileOperationMessage.setStatus(APIStatus.SUCCESS.toString());
fileOperationMessage.setMessage(APIStatus.SUCCESS.toString());
fileOperationMessage.setFolderName(folderName);
fileOperationMessage.setFileName(fileName);
return fileOperationMessage;
}
fileOperationMessage.setStatus(APIStatus.FAIL.toString());
fileOperationMessage.setMessage("The file is not exist.");
fileOperationMessage.setFileName(fileName);
} catch (Exception e) {
e.printStackTrace();
fileOperationMessage.setStatus(APIStatus.FAIL.toString());
fileOperationMessage.setMessage(e.getMessage());
fileOperationMessage.setFolderName(folderName);
fileOperationMessage.setFileName(fileName);
}
return fileOperationMessage;
}
/**
* 修改系统中的文件名
*
* @param fileInformation 文件信息
*/
@PutMapping("")
public FileOperationResponse updateFile(@RequestBody FileInformation fileInformation) {
FileOperationResponse fileOperationMessage = new FileOperationResponse();
String folder = rootDir + "\\" + fileInformation.getFolderName();
String currentFileName = fileInformation.getCurrentFileName();
String targetFileName = fileInformation.getTargetFileName();
File currentFile = new File(folder, currentFileName);
File targetFile = new File(folder, targetFileName);
try {
if (targetFile.exists()) {
fileOperationMessage.setStatus(APIStatus.FAIL.toString());
fileOperationMessage.setMessage("The target file name is already exist.");
fileOperationMessage.setFileName(targetFileName);
return fileOperationMessage;
}
if (currentFile.exists()) {
currentFile.renameTo(targetFile);
fileOperationMessage.setStatus(APIStatus.SUCCESS.toString());
fileOperationMessage.setMessage(APIStatus.SUCCESS.toString());
fileOperationMessage.setFileName(targetFileName);
return fileOperationMessage;
}
fileOperationMessage.setStatus(APIStatus.FAIL.toString());
fileOperationMessage.setMessage("The file is not exist.");
fileOperationMessage.setFileName(currentFileName);
} catch (Exception e) {
e.printStackTrace();
fileOperationMessage.setStatus(APIStatus.FAIL.toString());
fileOperationMessage.setMessage(e.getMessage());
fileOperationMessage.setFileName(currentFileName);
}
return fileOperationMessage;
}
/**
* 下载系统中的文件
*
* @param folderName 被下载的目标子文件夹
* @param fileName 被下载的文件
*/
@GetMapping("/download")
public FileOperationResponse download(@RequestParam String folderName, @RequestParam String fileName) {
FileOperationResponse fileOperationMessage = new FileOperationResponse();
String folder = rootDir + "\\" + folderName;
try {
File file = new File(folder, fileName);
HttpServletResponse response = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getResponse();
assert response != null;
response.setContentType("application/force-download");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(fileName, "UTF-8"));
if (file.exists()) {
byte[] buffer = new byte[1024];
fis = new FileInputStream(file);
bis = new BufferedInputStream(fis);
OutputStream os = response.getOutputStream();
int i = bis.read(buffer);
while (i != -1) {
os.write(buffer, 0, i);
i = bis.read(buffer);
}
fileOperationMessage.setStatus(APIStatus.SUCCESS.toString());
fileOperationMessage.setMessage(APIStatus.SUCCESS.toString());
fileOperationMessage.setFolderName(folderName);
fileOperationMessage.setFileName(fileName);
return fileOperationMessage;
}
fileOperationMessage.setStatus(APIStatus.FAIL.toString());
fileOperationMessage.setMessage("The file is not exist.");
fileOperationMessage.setFileName(fileName);
} catch (Exception e) {
e.printStackTrace();
fileOperationMessage.setStatus(APIStatus.FAIL.toString());
fileOperationMessage.setMessage(e.getMessage());
fileOperationMessage.setFolderName(folderName);
fileOperationMessage.setFileName(fileName);
} finally {
try {
bis.close();
fis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return fileOperationMessage;
}
/**
* 验证文件是否存在
*
* @param folderName 文件的文件夹名
* @param fileName 文件名
*/
@GetMapping("")
public FileOperationResponse getFile(@RequestParam String folderName, @RequestParam String fileName) {
FileOperationResponse fileOperationMessage = new FileOperationResponse();
String folder = rootDir + "\\" + folderName;
try {
File file = new File(folder, fileName);
if (!file.exists()) {
fileOperationMessage.setStatus(APIStatus.FAIL.toString());
fileOperationMessage.setMessage("The file is not exist.");
fileOperationMessage.setFolderName(folderName);
fileOperationMessage.setFileName(fileName);
return fileOperationMessage;
}
fileOperationMessage.setStatus(APIStatus.SUCCESS.toString());
fileOperationMessage.setMessage("The file is exist.");
fileOperationMessage.setFileName(fileName);
} catch (Exception e) {
e.printStackTrace();
fileOperationMessage.setStatus(APIStatus.FAIL.toString());
fileOperationMessage.setMessage(e.getMessage());
fileOperationMessage.setFolderName(folderName);
fileOperationMessage.setFileName(fileName);
}
return fileOperationMessage;
}
}
6. 后端容量信息获取功能;
package com.github.dylanz666.controller;
import com.github.dylanz666.constant.APIStatus;
import com.github.dylanz666.domain.FolderOperationResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
/**
* @author : dylanz
* @since : 11/22/2020
*/
@RestController
@RequestMapping("/api/space")
public class SpaceController {
@Value(value = "${file.root.dir}")
private String rootDir;
/**
* 获取文件系统容量信息
*/
@GetMapping("")
public FolderOperationResponse getSpaceInfo() {
File folder = new File(rootDir);
FolderOperationResponse folderOperationResponse = new FolderOperationResponse();
try {
long free = folder.getFreeSpace();
long total = folder.getTotalSpace();
String freeString = (free / 1024 / 1024 / 1024) + "G";
String totalString = (total / 1024 / 1024 / 1024) + "G";
String message = freeString + "/" + totalString;
folderOperationResponse.setStatus(APIStatus.SUCCESS.toString());
folderOperationResponse.setMessage(message);
return folderOperationResponse;
} catch (Exception e) {
e.printStackTrace();
folderOperationResponse.setStatus(APIStatus.FAIL.toString());
folderOperationResponse.setMessage(e.getMessage());
}
return folderOperationResponse;
}
}
7. 前后端集成及演示;
这一步我们留到下次再来完成吧,今天就到这啦!
今天的文章虽然没有完整的完成一个个人云盘应用,但您从文章中仍可以学到一些东西的,如:
1). Java中/Spring Boot中常用的文件夹操作代码;
2). Java中/Spring Boot中常用的文件操作代码;
3). 个人云盘应用的设计、实现思路;
4). Spring Boot基础对于实现具体应用的重要性;
等。
下个礼拜,我们不见不散!
如果本文对您有帮助,麻烦点赞、关注!
谢谢!
网友评论