一句话总结
文件存储系统核心功能:上传(普通上传、分块上传、断点续传)、下载(直接下载、CDN加速)、秒传(MD5去重,相同文件不重复存储)。存储方案:对象存储(MinIO/S3,推荐)vs 本地文件系统(简单但不支持分布式)vs FastDFS(传统方案)。大文件用分块上传(每块5MB,并发上传,最后合并),配合断点续传(记录已上传分块,重传时跳过)。
初级理解
存储方案对比
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 本地存储 | 文件存服务器磁盘 | 简单 | 无法分布式、扩容难 |
| FastDFS | 分布式文件系统 | 轻量、适合小文件 | 社区不活跃、功能有限 |
| MinIO | 对象存储(S3兼容) | 高性能、S3兼容、云原生 | 需要额外部署 |
| 云OSS | 阿里云OSS/腾讯COS | 免运维、高可用 | 成本高 |
# MinIO 基本使用
# 安装
docker run -p 9000:9000 -p 9001:9001 \
-e MINIO_ROOT_USER=admin \
-e MINIO_ROOT_PASSWORD=admin123 \
minio/minio server /data --console-address ":9001"
# Java SDK 上传
MinioClient client = MinioClient.builder()
.endpoint("http://localhost:9000")
.credentials("admin", "admin123")
.build();
client.putObject(
PutObjectArgs.builder()
.bucket("my-bucket")
.object("file.jpg")
.stream(inputStream, fileSize, -1)
.build()
);
一句话总结:对象存储(MinIO/S3)是主流方案,支持分布式、高可用。
中级深入
分块上传 + 断点续传
# 分块上传流程
# 1. 初始化:生成 uploadId,确定分块大小(5MB)
# 2. 分块上传:并发上传各分块
# 3. 完成:通知服务端合并分块
# 服务端接口
# POST /upload/init
# { "fileName": "video.mp4", "fileSize": 104857600, "chunkSize": 5242880 }
# → { "uploadId": "xxx", "chunkCount": 20 }
# POST /upload/chunk
# { "uploadId": "xxx", "chunkIndex": 0, "file": binary }
# → { "success": true }
# POST /upload/complete
# { "uploadId": "xxx" }
# → { "url": "https://cdn.example.com/video.mp4" }
# 断点续传
# GET /upload/progress?uploadId=xxx
# → { "uploadedChunks": [0, 1, 2, 5, 6] }
# 客户端跳过已上传的分块,只传缺失的
秒传机制
# 秒传原理:文件 MD5 去重
# 1. 客户端计算文件 MD5
# 2. 上传前先查服务端是否已有该 MD5
# 3. 有 → 直接返回文件 URL(秒传)
# 4. 无 → 正常上传
# 服务端
# POST /upload/check
# { "md5": "d41d8cd98f00b204e9800998ecf8427e" }
# → { "exists": true, "url": "https://cdn.example.com/file.jpg" }
# 数据库设计
CREATE TABLE file_info (
id BIGINT PRIMARY KEY,
file_name VARCHAR(255),
file_size BIGINT,
md5 VARCHAR(32) UNIQUE,
url VARCHAR(500),
created_at DATETIME
);
中级要点:大文件分块上传(5MB/块)+ 断点续传;秒传用 MD5 去重。
高级拓展
CDN 加速
# CDN 加速架构
# 用户 → CDN 边缘节点(就近访问)
# ↓ 缓存未命中
# 源站(MinIO/OSS)
# ↓
# 返回文件 + 缓存到 CDN
# CDN 缓存策略
# 静态资源(图片/CSS/JS):缓存 30 天
# 用户上传文件:缓存 7 天
# 热点文件:预热到 CDN
# 回源策略
# CDN 缓存过期 → 回源站拉取
# 源站设置 Cache-Control 头控制 CDN 缓存时间
图片处理
# 图片处理需求
# 1. 缩略图:原图 → 多种尺寸
# 2. 水印:添加文字/图片水印
# 3. 格式转换:jpg → webp
# 4. 裁剪:按比例/指定区域裁剪
# 方案1:上传时处理(预生成)
# 上传 → 生成多种尺寸 → 存储
# 优点:访问快;缺点:存储空间大
# 方案2:访问时处理(实时)
# 访问 URL 带参数:/image.jpg?w=200&h=200
# CDN/网关实时处理并缓存
# 优点:灵活;缺点:首次访问慢
# 方案3:混合(推荐)
# 常用尺寸预生成 + 非常用尺寸实时处理
实战场景
场景:视频上传服务
@Service
public class VideoUploadService {
@Autowired
private MinioClient minioClient;
# 初始化分块上传
public UploadInitVO initUpload(String fileName, long fileSize) {
String uploadId = UUID.randomUUID().toString();
int chunkSize = 5 * 1024 * 1024; # 5MB
int chunkCount = (int) Math.ceil((double) fileSize / chunkSize);
# 记录上传任务
redis.opsForHash().put("upload:" + uploadId, "fileName", fileName);
redis.opsForHash().put("upload:" + uploadId, "chunkCount", chunkCount);
redis.expire("upload:" + uploadId, 24, TimeUnit.HOURS);
return new UploadInitVO(uploadId, chunkCount, chunkSize);
}
# 上传分块
public void uploadChunk(String uploadId, int chunkIndex, byte[] data) {
String chunkKey = "chunks/" + uploadId + "/" + chunkIndex;
minioClient.putObject(PutObjectArgs.builder()
.bucket("video-uploads")
.object(chunkKey)
.stream(new ByteArrayInputStream(data), data.length, -1)
.build());
# 记录已上传分块
redis.opsForSet().add("upload:chunks:" + uploadId, String.valueOf(chunkIndex));
}
}
面试模拟
面试官:大文件上传怎么设计?
你:分块上传:将大文件切成5MB的块,并发上传,最后合并。配合断点续传:记录已上传分块,重传时跳过。秒传:上传前计算MD5,服务端已有则直接返回URL。存储用MinIO(S3兼容对象存储),配合CDN加速下载。
面试官:秒传怎么实现?
你:客户端计算文件MD5,上传前先请求服务端检查MD5是否存在。存在则直接返回已有文件URL(秒传),不存在则正常上传。数据库MD5字段建唯一索引。注意:MD5有碰撞概率,重要场景可加文件大小双重校验。