Springboot+vue实现前后端分离网盘存储系统
1. 多文件格式分类查看2. 支持网格、表格视图、时间线三种展示视图3. 支持极速秒传功能,提高上传效率4. 多人上传同一文件,可多人并行上传,共享他人上传进度,极大提高上传效率5. 拒绝冗余,每份文件只存一份,提高硬盘使用效率6. 上传文件前台实时显示上传文件进度,上传速率,百分比等信息7. 安全的下载机制,断点下载,权限校验,他人拿到了下载地址也无法下载您的文件
·
项目编号:BS-XX-094
## 功能介绍
1. 多文件格式分类查看
2. 支持网格、表格视图、时间线三种展示视图
3. 支持极速秒传功能,提高上传效率
4. 多人上传同一文件,可多人并行上传,共享他人上传进度,极大提高上传效率
5. 拒绝冗余,每份文件只存一份,提高硬盘使用效率
6. 上传文件前台实时显示上传文件进度,上传速率,百分比等信息
7. 安全的下载机制,断点下载,权限校验,他人拿到了下载地址也无法下载您的文件
8. 支持视频音频播放,进度条拖拽,倍速播放
9. 拥有回收站功能,妈妈再也不怕我误删数据了
10. 高效的垃圾回收机制
## 软件架构
该项目采用前后端分离的方式进行开发和部署,主要用到以下关键技术
**前端**:Element UI、Vue CLI@3、Node.js、Webpack
**后台**:Spring Boot、Spring Data Jpa、Spring Security
**数据库** : MySQL或H2
**数据结构**:递归算法,树的遍历和插入...
下面展示一下系统部分功能:
用户登陆注册:
网盘管理主页
文件上传
新建文件夹
移动文件
视频文件在线播放
回收站管理:可以对文件进行恢复和删除
项目核心代码:
package io.ddisk.controller;
import io.ddisk.domain.dto.PageDTO;
import io.ddisk.domain.enums.FileTypeEnum;
import io.ddisk.domain.vo.DirTreeNode;
import io.ddisk.domain.vo.FileVO;
import io.ddisk.domain.vo.LoginUser;
import io.ddisk.domain.vo.PathNodeVO;
import io.ddisk.domain.vo.base.PageVO;
import io.ddisk.service.UserFileService;
import io.ddisk.utils.SpringWebUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.hibernate.validator.constraints.Length;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import java.util.List;
import java.util.Map;
@Tag(name = "File", description = "该接口为文件接口,主要用来做一些文件的基本操作,如创建目录,删除,移动,复制等。")
@Validated
@RestController
@RequestMapping("/file")
public class FileController {
@Autowired
private UserFileService userFileService;
@Operation(summary = "获取目录及文件", description = "前端提供父目录和分页数据,返回该文件夹下的这些数据", tags = {"file"})
@GetMapping(value = "/list")
@Parameters({
@Parameter(name = "pid", description = "用户文件目录id,需要登录用户的文件", required = true)
})
public ResponseEntity<PageVO<FileVO>> getFileList(@NotNull PageDTO page, @Length(min = 32, max = 32) @RequestParam(required = false) String pid) {
LoginUser user = SpringWebUtils.requireLogin();
PageVO<FileVO> fileListPage = userFileService.listTheDir(user.getId(), pid, page);
return ResponseEntity.ok(fileListPage);
}
@Operation(summary = "获取某类型文件", description = "获取某一类型文件,例如图片、视频、文档等", tags = {"file"})
@GetMapping(value = "/list/type")
@Parameters({
@Parameter(name = "fileType", description = "文件类型,图片文件,视频文件等", required = true)
})
public ResponseEntity<PageVO<FileVO>> getFileListByType(@NotNull PageDTO page, @NotNull FileTypeEnum fileType) {
LoginUser user = SpringWebUtils.requireLogin();
PageVO<FileVO> fileListPage = userFileService.listTheTypeFile(user.getId(), fileType, page);
return ResponseEntity.ok(fileListPage);
}
@Operation(summary = "文件搜索", description = "模糊搜索文件名", tags = {"file"})
@GetMapping(value = "/search")
@Parameters({
@Parameter(name = "filename", description = "模糊搜索文件名", required = true)
})
public ResponseEntity<PageVO<FileVO>> searchFile(@NotNull PageDTO page, @NotBlank String filename) {
PageVO<FileVO> fileListPage = userFileService.searchFile(filename, page);
return ResponseEntity.ok(fileListPage);
}
@Operation(summary = "获取目录树", description = "文件移动的时候需要用到该接口,用来展示目录树,展示机制为饱汉模式", tags = {"file"})
@GetMapping(value = "/dir/tree")
public ResponseEntity<DirTreeNode> getDirTree() {
LoginUser user = SpringWebUtils.requireLogin();
DirTreeNode tree = userFileService.dirTree(user.getId());
return ResponseEntity.ok(tree);
}
@Operation(summary = "查询回收站文件", description = "查询某用户回收站所有文件", tags = {"file"})
@GetMapping(value = "/list/recycle")
public ResponseEntity<PageVO<FileVO>> listRecycleBin(@NotNull PageDTO page) {
LoginUser user = SpringWebUtils.requireLogin();
PageVO<FileVO> fileListPage = userFileService.listRecycle(user.getId(), page);
return ResponseEntity.ok(fileListPage);
}
@Operation(summary = "父节点列表", description = "用于显示父路径,字典key为文件id", tags = {"file"})
@GetMapping(value = "/path/tree")
public ResponseEntity<Map<String, PathNodeVO>> getPathTree() {
LoginUser user = SpringWebUtils.requireLogin();
Map<String, PathNodeVO> pathTreeMap = userFileService.getPathTreeMap(user.getId());
return ResponseEntity.ok(pathTreeMap);
}
@Operation(summary = "移动文件", description = "移动文件", tags = {"file"})
@PostMapping(value = "/move")
@Parameters({
@Parameter(name = "from", description = "移动文件id", required = true),
@Parameter(name = "to", description = "目标文件夹id", required = true)
})
public ResponseEntity<Void> move(@NotBlank @Length(min = 32, max = 32) String from, @Length(min = 32, max = 32) @RequestParam(required = false) String to) {
userFileService.move(from, to);
return ResponseEntity.ok().build();
}
@Operation(summary = "移动文件", description = "批量移动文件", tags = {"file"})
@PostMapping(value = "/batch/move")
@Parameters({
@Parameter(name = "from", description = "移动文件id列表", required = true),
@Parameter(name = "to", description = "目标文件夹id", required = true)
})
public ResponseEntity<Void> batchMove(@RequestParam(value = "from") @NotEmpty List<String> from, @Length(min = 32, max = 32)@RequestParam(required = false) String to) {
userFileService.move(from, to);
return ResponseEntity.ok().build();
}
@Operation(summary = "创建目录", description = "创建文件夹", tags = {"file"})
@PostMapping(value = "/mkdir")
@Parameters({
@Parameter(name = "pid", description = "父目录id", required = false),
@Parameter(name = "name", description = "目录名", required = true),
})
public ResponseEntity<Void> mkdir(@RequestParam(required = false) @Length(min = 32, max = 32) String pid, @NotBlank String name) {
LoginUser user = SpringWebUtils.requireLogin();
userFileService.mkdir(user.getId(), pid, name);
return ResponseEntity.ok().build();
}
@Operation(summary = "重命名", description = "重命名文件名和扩展名", tags = {"file"})
@PostMapping(value = "/rename")
@Parameters({
@Parameter(name = "userFileId", description = "文件id", required = true),
@Parameter(name = "filename", description = "文件基础名", required = true),
@Parameter(name = "extension", description = "文件扩展名", required = true),
})
public ResponseEntity<Void> rename(@NotBlank @Length(min = 32, max = 32) String userFileId, @NotBlank String filename, String extension) {
userFileService.rename(userFileId, filename, extension);
return ResponseEntity.ok().build();
}
@Operation(summary = "删除文件", description = "删除文件或文件夹", tags = {"file"})
@PostMapping(value = "/delete")
@Parameters({
@Parameter(name = "userFileId", description = "文件id", required = true),
})
public ResponseEntity<Void> delete(@NotBlank @Length(min = 32, max = 32) String userFileId) {
userFileService.delete(userFileId);
return ResponseEntity.ok().build();
}
@Operation(summary = "批量删除文件", description = "批量删除文件或文件夹", tags = {"file"})
@PostMapping(value = "/batch/delete")
@Parameters({
@Parameter(name = "userFileIds", description = "文件id列表", required = true),
})
public ResponseEntity<Void> batchDelete(@NotEmpty @RequestParam(value = "userFileIds") List<String> userFileIds) {
userFileService.delete(userFileIds);
return ResponseEntity.ok().build();
}
@Operation(summary = "恢复文件", description = "恢复回收站文件或文件夹", tags = {"file"})
@PostMapping(value = "/recover")
@Parameters({
@Parameter(name = "userFileId", description = "文件id", required = true),
})
public ResponseEntity<Void> recover(@NotBlank @Length(min = 32, max = 32) String userFileId) {
userFileService.recover(userFileId);
return ResponseEntity.ok().build();
}
@Operation(summary = "批量恢复文件", description = "批量恢复回收站文件或文件夹", tags = {"file"})
@PostMapping(value = "/batch/recover")
@Parameters({
@Parameter(name = "userFileIds", description = "文件id列表", required = true),
})
public ResponseEntity<Void> batchRecover(@NotEmpty @RequestParam(value = "userFileIds") List<String> userFileIds) {
userFileService.recover(userFileIds);
return ResponseEntity.ok().build();
}
@Operation(summary = "删除回收站文件", description = "真正的删除文件接口,删除回收站里的文件", tags = {"file"})
@PostMapping(value = "/delete/recycle")
@Parameters({
@Parameter(name = "userFileId", description = "文件id", required = true),
})
public ResponseEntity<Void> deleteFromRecycleBin(@NotBlank @Length(min = 32, max = 32) String userFileId) {
userFileService.deleteFromRecycleBin(userFileId);
return ResponseEntity.ok().build();
}
@Operation(summary = "批量删除回收站文件", description = "真正的删除文件接口,删除回收站里的文件", tags = {"file"})
@PostMapping(value = "/batch/delete/recycle")
@Parameters({
@Parameter(name = "userFileIds", description = "文件id列表", required = true),
})
public ResponseEntity<Void> batchDeleteFromRecycleBin(@RequestParam(value = "userFileIds") @NotEmpty List<String> userFileIds) {
userFileService.deleteFromRecycleBin(userFileIds);
return ResponseEntity.ok().build();
}
}
package io.ddisk.controller;
import io.ddisk.domain.dto.FileDTO;
import io.ddisk.domain.dto.FileUploadDTO;
import io.ddisk.domain.dto.MergeFileDTO;
import io.ddisk.domain.entity.FileEntity;
import io.ddisk.domain.vo.LoginUser;
import io.ddisk.domain.vo.UploadFileVO;
import io.ddisk.service.FileService;
import io.ddisk.service.FileShareService;
import io.ddisk.utils.ResponseUtils;
import io.ddisk.utils.SpringWebUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import jodd.bean.BeanCopy;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.constraints.Length;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.NotBlank;
import java.util.Collection;
import java.util.Objects;
@Slf4j
@Tag(name = "FileTransfer", description = "该接口为文件传输接口,主要用来做文件的上传和下载")
@RequestMapping("transfer")
@Validated
@RestController
public class FileTransferController {
@Autowired
private FileService fileService;
@Autowired
private FileShareService fileShareService;
@Operation(summary = "极速上传", description = "校验文件MD5判断文件是否存在,如果存在直接上传成功并返回skipUpload=true,如果不存在返回skipUpload=false")
@GetMapping("upload")
@ResponseBody
public ResponseEntity<UploadFileVO> uploadFileSpeed(@Validated FileUploadDTO fileUploadDto){
LoginUser user = SpringWebUtils.requireLogin();
FileEntity fileEntity = fileService.speedUpload(user.getId(), fileUploadDto);
Collection<Integer> uploaded = fileService.getUploaded(fileUploadDto.getIdentifier(), fileUploadDto.getChunkSize());
return ResponseEntity.ok(new UploadFileVO(false, Objects.nonNull(fileEntity), uploaded));
}
@Operation(summary = "上传文件", description = "真正的上传文件接口")
@PostMapping("upload")
public ResponseEntity<UploadFileVO> uploadChunk(@Validated FileUploadDTO fileUploadDto) {
LoginUser user = SpringWebUtils.requireLogin();
Collection<Integer> uploaded = fileService.upload(user.getId(), fileUploadDto);
return ResponseEntity.ok(new UploadFileVO(uploaded.size()==fileUploadDto.getTotalChunks(), false, uploaded));
}
@Operation(summary = "下载文件", description = "文件下载接口,保证文件安全,阻止非法用户下载")
@GetMapping("download/{userFileId}")
@Parameters({
@Parameter(name = "userFileId", description = "用户文件id,需要登录用户的文件", required = true)
})
public void downloadFile(@PathVariable @NotBlank @Length(min = 32, max = 32) String userFileId){
LoginUser user = SpringWebUtils.requireLogin();
FileDTO fileDTO = fileService.getFileResource(user.getId(), user.getRole(), userFileId);
ResponseUtils.sendFile(fileDTO);
}
@Operation(summary = "下载文件", description = "文件下载接口,保证文件安全,阻止非法用户下载")
@GetMapping("/anonymous/download/{shareId}")
@Parameters({
@Parameter(name = "userFileId", description = "用户文件id,需要登录用户的文件", required = true)
})
public void anonymousDownloadFile(@PathVariable @NotBlank @Length(min = 32, max = 32) String shareId){
FileDTO fileDTO = fileShareService.getFileResource(shareId);
ResponseUtils.sendFile(fileDTO);
}
@Operation(summary = "获取略缩图", description = "略缩图获取,用于图片类型文件的略缩图和头像")
@GetMapping("thumbnail/{userFileId}")
@Parameters({
@Parameter(name = "userFileId", description = "用户文件id,需要登录用户的文件", required = true)
})
public void thumbnail(@PathVariable @NotBlank @Length(min = 32, max = 32) String userFileId){
LoginUser user = SpringWebUtils.requireLogin();
FileDTO fileDTO = fileService.getThumbnail(user.getId(), userFileId);
ResponseUtils.sendFile(fileDTO);
}
@Operation(summary = "合并", description = "合并上传的切片文件")
@PostMapping("merge")
public ResponseEntity<Void> mergeFile(@Validated @RequestBody MergeFileDTO mergeFileDTO) {
LoginUser user = SpringWebUtils.requireLogin();
fileService.mergeFile(mergeFileDTO);
FileUploadDTO uploadDTO = new FileUploadDTO();
BeanCopy.from(mergeFileDTO).to(uploadDTO).copy();
fileService.speedUpload(user.getId(), uploadDTO);
log.info("用户[{}], 合并[{}]文件", user.getUsername(), uploadDTO.getIdentifier());
return ResponseEntity.ok().build();
}
}
package io.ddisk.controller;
import io.ddisk.domain.dto.RegisterDTO;
import io.ddisk.domain.vo.LoginUser;
import io.ddisk.domain.vo.UserStorageVO;
import io.ddisk.service.UserService;
import io.ddisk.service.UserStorageService;
import io.ddisk.utils.SpringWebUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.hibernate.validator.constraints.Length;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
/**
* 用户的登录,注销交由spring security处理
*
*/
@Tag(name = "User", description = "该接口为用户接口,主要做用户登录,注册和校验token")
@RequestMapping("user")
@Validated
@RestController
public class UserController {
@Autowired
private UserService userService;
@Autowired
private UserStorageService userStorageService;
@Operation(summary = "用户注册, 步骤一", description = "获取注册令牌,令牌发送至用户邮箱", tags = {"user"})
@GetMapping(value = "register")
@Parameters({
@Parameter(name = "email", description = "用户邮箱", required = true)
})
public ResponseEntity<Void> sendRegisterToken(@Email String email) {
userService.sendEmailToRegister(email);
return ResponseEntity.ok().build();
}
@Operation(summary = "用户注册, 步骤二", description = "注册账号", tags = {"user"})
@PostMapping(value = "register")
public ResponseEntity<Void> registerUser(@Validated @RequestBody RegisterDTO registerDTO) {
userService.addUser(registerDTO);
return ResponseEntity.ok().build();
}
@Operation(summary = "设置头像", description = "设置用户头像", tags = {"user"})
@PostMapping(value = "avatar")
@Parameters({
@Parameter(name = "fileId", description = "用户文件id", required = true)
})
public ResponseEntity<Void> setAvator(@NotBlank @Length(min = 32, max = 32) String fileId) {
LoginUser user = SpringWebUtils.requireLogin();
userService.setAvator(user.getId(), fileId);
user.setImgUrl(fileId);
return ResponseEntity.ok().build();
}
@Operation(summary = "获取用户信息", description = "获取用户信息", tags = {"user"})
@GetMapping(value = "info")
public ResponseEntity<LoginUser> getLoginUser() {
return ResponseEntity.ok().body(SpringWebUtils.requireLogin());
}
@Operation(summary = "校验用户名", description = "检查用户是否已被注册", tags = {"user"})
@GetMapping(value = "exist/username")
@Parameters({
@Parameter(name = "username", description = "用户名", required = true)
})
public ResponseEntity<Void> existUsername(
@Length(min = 4, max = 16, message = "用户名长度必须在7-20位") String username) {
userService.notExistUsername(username);
return ResponseEntity.ok().build();
}
@Operation(summary = "校验邮箱", description = "检查邮箱是否已被使用", tags = {"user"})
@GetMapping(value = "exist/email")
@Parameters({
@Parameter(name = "email", description = "用户邮箱", required = true)
})
public ResponseEntity<LoginUser> existEmail(@Email @NotBlank String email) {
userService.notExistUsername(email);
return ResponseEntity.ok().build();
}
@Operation(summary = "忘记密码,步骤1", description = "忘记密码之发送密码重置邮件", tags = {"user"})
@GetMapping(value = "reset/passwd")
@Parameters({
@Parameter(name = "email", description = "用户邮箱", required = true)
})
public ResponseEntity<Void> forgetPasswd(@Email @NotBlank String email) {
userService.sendEmailToResetPassword(email);
return ResponseEntity.ok().build();
}
@Operation(summary = "忘记密码,步骤2", description = "忘记密码之重置密码", tags = {"user"})
@PostMapping(value = "reset/passwd")
@Parameters({
@Parameter(name = "email", description = "用户邮箱", required = true),
@Parameter(name = "password", description = "新密码", required = true),
@Parameter(name = "token", description = "令牌,从邮件中获取", required = true)
})
public ResponseEntity<Void> regeneratePasswd(
@Email @NotBlank String email,
@Length(min = 7, max = 20, message = "密码长度必须在7-20位") String password,
@Length(min = 36, max = 36, message = "密码长度必须在7-20位") String token) {
userService.forgetPassword(email, password, token);
return ResponseEntity.ok().build();
}
@Operation(summary = "修改密码", description = "修改密码", tags = {"user"})
@Parameters({
@Parameter(name = "oldPassword", description = "原密码", required = true),
@Parameter(name = "newPassword", description = "新密码", required = true)
})
@GetMapping(value = "passwd")
public ResponseEntity<Void> passwd(
@Length(min = 7, max = 20, message = "密码长度必须在7-20位") String oldPassword,
@Length(min = 7, max = 20, message = "密码长度必须在7-20位") String newPassword
) {
LoginUser user = SpringWebUtils.requireLogin();
userService.passwd(user.getId(), oldPassword, newPassword);
return ResponseEntity.ok().build();
}
@Operation(summary = "用户内存使用", description = "获取用户磁盘信息,已使用和未使用大小", tags = {"user"})
@GetMapping(value = "storage/info")
public ResponseEntity<UserStorageVO> getStorageInfo() {
UserStorageVO vo = userStorageService.getStorageInfo(SpringWebUtils.requireLogin());
return ResponseEntity.ok().body(vo);
}
}
更多推荐
已为社区贡献20条内容
所有评论(0)