项目编号: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);
	}

}

Logo

前往低代码交流专区

更多推荐