1. 项目概述:从零搭建个人云盘系统
这个基于SpringBoot+Vue的全栈个人云盘系统,是我在疫情期间为了解决家庭照片和视频共享需求而开发的。相比市面上的公有云服务,自建云盘不仅能完全掌控数据隐私,还能根据个人需求灵活定制功能模块。系统采用经典的前后端分离架构,后端使用SpringBoot提供RESTful API,前端通过Vue实现响应式交互,数据库选用MySQL存储结构化数据,文件实体则直接存放在服务器本地磁盘(后期可扩展为分布式存储)。
提示:虽然项目使用本地磁盘存储作为演示方案,但在生产环境建议考虑MinIO等对象存储方案,后续会详细说明迁移方法。
整套系统实现了最核心的云盘功能:
- 多级目录管理(支持拖拽排序)
- 文件分片上传与断点续传
- 在线预览常见文档格式(PDF/图片/视频)
- 分享链接生成与密码保护
- 回收站机制与版本控制
开发过程中特别注重了前后端的技术栈纯净度——没有引入冗余的中间件,确保即使是对Java/前端生态了解不深的开发者也能快速上手。源码结构严格遵循Alibaba Java Coding Guidelines和Vue官方风格指南,每个核心功能模块都配有详细的代码注释。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构解析
2.1 后端技术栈设计
SpringBoot 2.7.x作为后端框架的选择经过了多重考量:
- 内嵌Tomcat简化部署(对比传统Spring MVC需要单独部署WAR包)
- 自动配置机制大幅减少XML配置(starter依赖开箱即用)
- Actuator端点提供系统健康监控
- 与MyBatis的整合只需单个注解
@MapperScan
数据库表设计遵循了云盘业务的特殊需求:
sql复制CREATE TABLE `file_metadata` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`file_name` VARCHAR(255) NOT NULL,
`file_path` TEXT NOT NULL COMMENT '相对路径',
`file_size` BIGINT NOT NULL,
`file_type` VARCHAR(50) NOT NULL,
`parent_id` BIGINT DEFAULT NULL COMMENT '父目录ID',
`is_dir` TINYINT(1) DEFAULT 0,
`md5` VARCHAR(32) COMMENT '用于秒传验证',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`parent_id`) REFERENCES `file_metadata`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
文件上传采用了分片处理策略:
- 前端计算文件MD5并查询服务端是否已存在(秒传)
- 将文件切分为2MB的chunk(可配置)
- 并行上传分片到
/api/upload/chunk - 所有分片上传完成后调用
/api/upload/merge合并
2.2 前端工程化实践
Vue 3的组合式API大幅提升了代码组织效率:
- 使用Pinia替代Vuex进行状态管理
- Element Plus作为UI组件库(特别优化了文件树组件)
- axios拦截器统一处理401跳转登录页
- 自定义指令实现拖拽上传
一个典型的文件预览组件实现:
vue复制<template>
<div class="preview-container">
<pdf-viewer v-if="fileType === 'pdf'" :url="fileUrl" />
<video-player v-else-if="isVideoType" :src="fileUrl" />
<image-preview v-else-if="isImageType" :src="fileUrl" />
<div v-else class="unsupported-type">
暂不支持在线预览,请下载查看
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
fileType: String,
fileUrl: String
});
const isVideoType = computed(() =>
['mp4', 'webm', 'ogg'].includes(props.fileType.toLowerCase())
);
</script>
3. 核心功能实现细节
3.1 断点续传的工程实践
后端需要处理几个关键技术点:
- 分片临时存储目录按
userId/day/chunkId三级结构组织 - 使用Redis记录上传进度(Key格式:
upload:${userId}:${fileMd5}) - 合并时校验所有分片的MD5一致性
核心Java代码片段:
java复制@PostMapping("/merge")
public Result mergeChunks(@RequestParam String fileMd5,
@RequestParam String fileName,
@RequestParam Integer totalChunks) {
// 检查所有分片是否已上传完成
String tempDir = getTempDir(fileMd5);
File[] chunks = new File(tempDir).listFiles();
if (chunks == null || chunks.length != totalChunks) {
return Result.fail("分片数量不匹配");
}
// 创建目标文件
Path destPath = Paths.get(uploadRoot, fileName);
try (OutputStream os = Files.newOutputStream(destPath,
StandardOpenOption.CREATE, StandardOpenOption.APPEND)) {
// 按序号合并分片
for (int i = 0; i < totalChunks; i++) {
File chunk = new File(tempDir, i + ".part");
Files.copy(chunk.toPath(), os);
}
// 更新数据库记录...
}
}
3.2 安全控制方案
系统实现了多层次的安全防护:
- 接口级鉴权:Spring Security + JWT
- 登录成功返回accessToken(30分钟过期)和refreshToken(7天过期)
- 接口访问时通过
Authorization: Bearer ${token}验证
- 文件下载签名:
- 生成临时下载链接
/download?token=${临时令牌} - 令牌有效期10分钟且仅能使用一次
- 生成临时下载链接
- 分享链接加密:
- AES加密真实文件路径
- 可选设置密码和过期时间
密码加密采用BCrypt算法:
java复制@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12); // 强度因子12
}
4. 部署与优化指南
4.1 生产环境部署方案
推荐使用Docker Compose编排服务:
yaml复制version: '3'
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
backend:
build: ./server
ports:
- "8080:8080"
depends_on:
- db
frontend:
build: ./client
ports:
- "80:80"
volumes:
mysql_data:
性能调优关键参数:
- SpringBoot应用:
properties复制server.tomcat.max-threads=200 spring.servlet.multipart.max-file-size=5GB spring.datasource.hikari.maximum-pool-size=20 - Nginx前端配置:
nginx复制gzip on; gzip_min_length 1k; gzip_types text/plain application/javascript; location /api { proxy_pass http://backend:8080; proxy_set_header X-Real-IP $remote_addr; }
4.2 常见问题排查
-
上传大文件内存溢出:
- 检查
-Xmx参数是否足够(建议至少512MB) - 确保使用
Streaming方式读取请求体
- 检查
-
视频预览卡顿:
- 前端使用HLS协议转码(可通过FFmpeg实现)
bash复制
ffmpeg -i input.mp4 -c:v libx264 -hls_time 10 -hls_list_size 0 output.m3u8 -
MySQL连接池耗尽:
- 检查是否有未关闭的Connection
- 适当增加
maximum-pool-size - 添加连接存活测试配置:
properties复制spring.datasource.hikari.test-on-borrow=true spring.datasource.hikari.validation-query=SELECT 1
这个项目从第一行代码到最终部署上线,我经历了三次架构调整。最大的教训是早期没有做好文件元数据与实体文件的原子性管理,导致在某些异常情况下出现数据不一致。现在的版本通过数据库事务与文件操作联动解决了这个问题——任何文件操作都会先在数据库创建记录,只有事务提交成功后才执行实际文件操作,并在@Transactional注解中详细配置了异常回滚策略。
