1. 为什么大文件上传需要分片和断点续传?
我猜你肯定遇到过这种情况:辛辛苦苦上传一个2GB的视频,进度条走到99%的时候,网络突然断了,或者浏览器崩溃了,结果一切都要从头再来。那种感觉,真的让人想砸键盘。
传统的单文件上传方式,就像是你想把一个巨大的衣柜搬进房间,结果发现门太小,怎么都塞不进去。而分片上传,就是把衣柜拆成一块块木板,分批运进去,最后在房间里再组装起来。这样做有几个明显的好处:
第一,解决网络不稳定的问题。一个分片上传失败了,只需要重传这个分片,而不是整个文件。第二,绕过服务器限制。很多Web服务器和应用服务器对单个请求的大小都有限制(比如Spring Boot默认的multipart.max-file-size),分片上传可以轻松突破这个限制。第三,提升上传效率。多个分片可以并行上传,充分利用带宽。第四,实现断点续传。这是用户体验的飞跃,用户关闭了网页,第二天打开还能接着传。
而Minio,作为一个高性能、兼容S3协议的对象存储,原生就支持分片上传的API。它不像我们自己在服务器上写逻辑去合并文件,而是由存储服务本身来保证分片上传、合并的原子性和可靠性。所以,用Spring Boot整合Minio来实现这个功能,可以说是“专业的人干专业的事”,既省心又高效。
接下来,我会带你从零开始,手把手搭建一个完整的、生产可用的分片上传和断点续传系统。我会分享我实际项目中踩过的坑,以及怎么优化才能让上传又快又稳。
2. 环境准备与Minio基础配置
工欲善其事,必先利其器。我们先得把“战场”布置好。
2.1 启动你的Minio服务
Minio的安装非常简单,用Docker一句话就能搞定:
docker run -p 9000:9000 -p 9001:9001 \
--name minio \
-v /mnt/data:/data \
-e "MINIO_ROOT_USER=admin" \
-e "MINIO_ROOT_PASSWORD=yourstrongpassword" \
minio/minio server /data --console-address ":9001"
跑起来之后,浏览器打开 http://localhost:9001,用上面设置的用户名密码登录,你就能看到Minio的管理控制台了。我建议你第一时间在这里创建一个专门的桶(Bucket),比如就叫 file-uploads,用来存放我们上传的文件。记住,桶名要全局唯一。
2.2 创建你的Spring Boot项目
用你喜欢的IDE或者Spring Initializr创建一个新项目。依赖项除了基础的Spring Web,核心就是Minio的Java SDK:
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.7</version> <!-- 请使用最新稳定版本 -->
</dependency>
我踩过一个坑:Minio客户端的版本和服务器版本最好匹配,否则一些新API可能用不了。所以安装Minio服务时也留意一下版本号。
2.3 配置Minio连接参数
在application.yml里,把这些配置加上:
minio:
endpoint: http://localhost:9000 # Minio服务地址
access-key: admin # 登录用户名
secret-key: yourstrongpassword # 登录密码
bucket-name: file-uploads # 我们刚创建的桶名
secure: false # 如果是http就false,https则true
然后,我们创建一个配置类来初始化Minio客户端Bean。这里有个小技巧,我习惯加上@PostConstruct来验证连接是否成功,避免应用启动后才发现连不上存储服务。
@Configuration
public class MinioConfig {
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.access-key}")
private String accessKey;
@Value("${minio.secret-key}")
private String secretKey;
@Value("${minio.secure}")
private Boolean secure;
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
}
好了,基础环境搭建完毕。你可以写个简单的测试接口,上传一个小文件,确保Minio客户端能正常工作。如果这一步通了,我们就能进入最核心的分片上传逻辑了。
3. 分片上传核心原理与工具类封装
理解了原理,写代码才不会迷糊。Minio的分片上传流程,其实就四步:初始化、传分片、合并、取消。官方SDK提供了对应的方法,我们要做的就是把它封装得更好用。
3.1 分片上传的四个阶段
- 初始化上传 (Initiate): 告诉Minio:“我要开始上传一个叫
video.mp4的大文件了”。Minio会返回一个本次上传任务唯一的uploadId。这个ID是后续所有操作的凭证。 - 上传分片 (Upload Part): 把文件切成5MB或10MB的小块(这个大小可以调),按顺序(1,2,3...)把每一块连同
uploadId一起传给Minio。Minio会为每个分片返回一个ETag(可以理解为分片的指纹)。 - 完成上传 (Complete): 所有分片都传完后,告诉Minio:“我的分片都齐了,这是所有分片序号和对应的ETag,请你把它们合并成完整的
video.mp4”。Minio校验无误后执行合并。 - 取消上传 (Abort): 如果用户不想传了,或者出了什么错误,可以用
uploadId取消整个上传任务,Minio会清理掉所有临时分片。
3.2 增强版Minio工具类
直接裸用SDK的API比较繁琐,我习惯封装一个工具类。除了基本功能,我还加了两个很实用的东西:上传ID缓存和进度追踪。
@Component
public class MinioUploadService {
@Autowired
private MinioClient minioClient;
// 缓存:避免重复初始化,也方便前端断点续传时获取uploadId
private final Map<String, String> uploadIdCache = new ConcurrentHashMap<>();
// 进度:记录每个文件已上传成功的分片信息
private final Map<String, List<Part>> partProgressMap = new ConcurrentHashMap<>();
/**
* 1. 初始化分片上传任务
*/
public String initMultiPartUpload(String bucketName, String objectName) throws Exception {
String uploadId = minioClient.initiateMultipartUpload(
InitiateMultipartUploadArgs.builder()
.bucket(bucketName)
.object(objectName)
.build()
).uploadId();
// 缓存起来,key可以用 bucketName + ":" + objectName 组合
String cacheKey = bucketName + ":" + objectName;
uploadIdCache.put(cacheKey, uploadId);
// 初始化一个空列表来记录这个文件的分片进度


4333

被折叠的 条评论
为什么被折叠?



