Spring Boot實(shí)現(xiàn)分片上傳、斷點(diǎn)續(xù)傳與進(jìn)度條功能
Spring Boot 實(shí)現(xiàn)分片上傳、斷點(diǎn)續(xù)傳與進(jìn)度條
# Spring Boot 實(shí)現(xiàn)分片上傳、斷點(diǎn)續(xù)傳與進(jìn)度條 ## —— 支持 MinIO / RustFS / SeaweedFS 可配置切換 在大文件上傳場景下,傳統(tǒng)單次上傳存在明顯問題: - 文件過大,失敗后需要整體重傳 - 網(wǎng)絡(luò)不穩(wěn)定,用戶體驗(yàn)差 - 無法展示上傳進(jìn)度 本文基于 **Spring Boot**,實(shí)現(xiàn)一套**生產(chǎn)可用**的大文件上傳方案,支持: - 分片上傳 - 斷點(diǎn)續(xù)傳 - 上傳進(jìn)度查詢 - MinIO / RustFS / SeaweedFS / 本地存儲 - 通過 yml 配置文件切換存儲類型 --- ## 一、整體架構(gòu)設(shè)計(jì) 系統(tǒng)整體采用「接口隔離 + 策略模式」設(shè)計(jì):
Controller ↓ UploadService ↓ StorageService(統(tǒng)一抽象) ↓ MinIO / RustFS / SeaweedFS / Local
**核心思想:上傳邏輯與底層存儲解耦。**
---
## 二、統(tǒng)一配置設(shè)計(jì)
### 1. 存儲類型切換
```yaml
file:
path: file/
prefix: pre
domain: domain/
storage:
type: minio # minio / rustfs / seaweedfs / local只需修改 storage.type,即可切換存儲實(shí)現(xiàn)。
2. MinIO 配置
minio: url: http://localhost:9000 accessKey: minioadmin secretKey: minioadmin123 bucketName: xxx
3. RustFS 配置(S3 協(xié)議)
rustfs: url: http://localhost:9000 accessKey: rustfsadmin secretKey: rustfsadmin bucketName: xxx
RustFS 兼容 S3 協(xié)議,可直接復(fù)用 MinIO SDK。
4. SeaweedFS 配置
seaweedfs: url: http://127.0.0.1:8333 accessKey: weed secretKey: weed bucketName: xxx
三、分片上傳核心流程
1. 前端切片思路
前端將大文件切割為多個(gè)分片(如 5MB):
file ├── chunk_0 ├── chunk_1 ├── chunk_2 └── chunk_n
每個(gè)分片上傳時(shí)攜帶:
- 文件唯一標(biāo)識(guid / md5)
- 分片索引(chunkIndex)
- 總分片數(shù)(totalChunk)
2. 分片上傳接口
POST /file/chunk/upload
四、斷點(diǎn)續(xù)傳實(shí)現(xiàn)
1. 查詢已上傳分片
GET /file/chunk/uploaded?guid=xxx
返回示例:
[0,1,3,5]
前端只上傳缺失分片即可。
2. 實(shí)現(xiàn)原則
- 是否已上傳以「存儲層」為準(zhǔn)
- 不依賴內(nèi)存或 Redis
- 服務(wù)重啟不影響續(xù)傳
五、上傳進(jìn)度計(jì)算
進(jìn)度 = 已上傳分片數(shù) / 總分片數(shù) × 100%
示例返回:
{
"uploaded": 6,
"total": 10,
"percent": 60
}六、存儲層抽象設(shè)計(jì)
1. 統(tǒng)一接口定義
public interface StorageService {
void uploadChunk(String path, InputStream inputStream);
boolean exists(String path);
List<Integer> listChunks(String prefix);
void mergeChunks(String chunkPrefix, String targetPath, int totalChunk);
void deleteChunks(String chunkPrefix);
}2. MinIO / RustFS 實(shí)現(xiàn)(S3 通用)
@Slf4j
public class MinioStorageService implements StorageService {
private final MinioClient minioClient;
private final String bucket;
public MinioStorageService(MinioClient client, String bucket) {
this.minioClient = client;
this.bucket = bucket;
}
@Override
public void uploadChunk(String path, InputStream inputStream) {
try {
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucket)
.object(path)
.stream(inputStream, -1, 5 * 1024 * 1024)
.build()
);
} catch (Exception e) {
throw new RuntimeException("分片上傳失敗", e);
}
}
@Override
public boolean exists(String path) {
try {
minioClient.statObject(
StatObjectArgs.builder()
.bucket(bucket)
.object(path)
.build()
);
return true;
} catch (Exception e) {
return false;
}
}
@Override
public List<Integer> listChunks(String prefix) {
List<Integer> chunks = new ArrayList<>();
Iterable<Result<Item>> results =
minioClient.listObjects(
ListObjectsArgs.builder()
.bucket(bucket)
.prefix(prefix)
.build()
);
for (Result<Item> r : results) {
String name = r.get().objectName();
chunks.add(Integer.parseInt(
name.substring(name.lastIndexOf("_") + 1)));
}
return chunks;
}
@Override
public void mergeChunks(String chunkPrefix,
String targetPath,
int totalChunk) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (int i = 0; i < totalChunk; i++) {
InputStream in = minioClient.getObject(
GetObjectArgs.builder()
.bucket(bucket)
.object(chunkPrefix + "/chunk_" + i)
.build()
);
IOUtils.copy(in, out);
}
uploadChunk(
targetPath,
new ByteArrayInputStream(out.toByteArray()));
} catch (Exception e) {
throw new RuntimeException("分片合并失敗", e);
}
}
@Override
public void deleteChunks(String chunkPrefix) {
// 可按需實(shí)現(xiàn)批量刪除
}
}七、業(yè)務(wù) Service 實(shí)現(xiàn)
@Service
public class FileUploadServiceImpl implements FileUploadService {
@Autowired
private StorageService storageService;
@Override
public void uploadChunk(ChunkUploadDTO dto) throws IOException {
String path = dto.getGuid() + "/chunk_" + dto.getChunkIndex();
if (storageService.exists(path)) {
return;
}
storageService.uploadChunk(
path, dto.getFile().getInputStream());
}
@Override
public List<Integer> uploadedChunks(String guid) {
return storageService.listChunks(guid + "/");
}
@Override
public void merge(String guid, int totalChunk) {
storageService.mergeChunks(
guid, guid + ".final", totalChunk);
storageService.deleteChunks(guid + "/");
}
}八、Controller 接口
@RestController
@RequestMapping("/file/chunk")
public class FileUploadController {
@Autowired
private FileUploadService fileUploadService;
@PostMapping("/upload")
public void upload(ChunkUploadDTO dto) throws IOException {
fileUploadService.uploadChunk(dto);
}
@GetMapping("/uploaded")
public List<Integer> uploaded(@RequestParam String guid) {
return fileUploadService.uploadedChunks(guid);
}
@PostMapping("/merge")
public void merge(@RequestParam String guid,
@RequestParam Integer totalChunk) {
fileUploadService.merge(guid, totalChunk);
}
}九、總結(jié)
本文實(shí)現(xiàn)了一套 Spring Boot 大文件上傳方案,具備:
- 分片上傳
- 斷點(diǎn)續(xù)傳
- 上傳進(jìn)度計(jì)算
- 多存儲后端解耦
- yml 配置快速切換
適用于文件中心、數(shù)據(jù)平臺、企業(yè)網(wǎng)盤等場景,可直接用于生產(chǎn)環(huán)境。
到此這篇關(guān)于Spring Boot 實(shí)現(xiàn)分片上傳、斷點(diǎn)續(xù)傳與進(jìn)度條的文章就介紹到這了,更多相關(guān)springboot分片上傳斷點(diǎn)續(xù)傳內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot基于Minio實(shí)現(xiàn)分片上傳、斷點(diǎn)續(xù)傳的實(shí)現(xiàn)
- springboot整合vue2-uploader實(shí)現(xiàn)文件分片上傳、秒傳、斷點(diǎn)續(xù)傳功能
- springboot大文件上傳、分片上傳、斷點(diǎn)續(xù)傳、秒傳的實(shí)現(xiàn)
- SpringBoot 中大文件(分片上傳)斷點(diǎn)續(xù)傳與極速秒傳功能的實(shí)現(xiàn)
- SpringBoot上傳文件如何返回前端進(jìn)度條
- SpringBoot實(shí)現(xiàn)文件上傳下載實(shí)時(shí)進(jìn)度條功能(附源碼)
相關(guān)文章
SpringBoot內(nèi)嵌tomcat處理有特殊字符轉(zhuǎn)義的問題
這篇文章主要介紹了SpringBoot內(nèi)嵌tomcat處理有特殊字符轉(zhuǎn)義的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06
java 實(shí)現(xiàn)微信服務(wù)器下載圖片到自己服務(wù)器
這篇文章主要介紹了 java 實(shí)現(xiàn)微信服務(wù)器下載圖片到自己服務(wù)器的相關(guān)資料,需要的朋友可以參考下2017-05-05
Java使用list集合remove需要注意的事項(xiàng)(使用示例)
List集合的一個(gè)特點(diǎn)是它其中的元素是有序的,也就是說元素的下標(biāo)是根據(jù)插入的順序來的,在刪除頭部或者中間的一個(gè)元素后,后面的元素下標(biāo)會往前移動,本文給大家介紹Java使用list集合remove需要注意的事項(xiàng),感興趣的朋友一起看看吧2022-01-01
Java實(shí)現(xiàn)游戲飛機(jī)大戰(zhàn)-III的示例代碼
這篇文章主要為大家介紹了如何利用Java實(shí)現(xiàn)經(jīng)典的游戲之飛機(jī)大戰(zhàn),文中采用了swing技術(shù)進(jìn)行了界面化處理,感興趣的小伙伴可以動手試一試2022-02-02
SpringBoot?自定義starter?yaml提示失效問題及解決方法
在自定義starter后,必不可少會有properties配置參數(shù)需要指定,而在有時(shí)又不知道為什么出現(xiàn)這個(gè)問題,這篇文章主要介紹了SpringBoot?自定義starter?yaml提示失效問題,需要的朋友可以參考下2022-12-12
Spring Boot部署到Tomcat過程中遇到的問題匯總
這篇文章主要給大家分享了關(guān)于Spring Boot部署到Tomcat過程中遇到的一些問題,文中將解決的方法介紹非常詳細(xì),對同樣遇到這個(gè)問題的朋友具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03
SpringBoot項(xiàng)目整合jasypt實(shí)現(xiàn)過程詳解
這篇文章主要介紹了SpringBoot項(xiàng)目整合jasypt實(shí)現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
java實(shí)現(xiàn)翻轉(zhuǎn)單詞順序列
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)翻轉(zhuǎn)單詞順序列,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-03-03
基于GeoTools和SpringBoot的省域駕車最快路線規(guī)劃方法
本文探討基于GeoTools與SpringBoot的省域駕車最快路線規(guī)劃方法,解決復(fù)雜路況與地理因素影響,通過天地圖API計(jì)算并存儲距離數(shù)據(jù),展示湖南、新疆、黑龍江等地的路線成果,為交通與經(jīng)濟(jì)影響評估提供參考,感興趣的朋友快來一起學(xué)習(xí)吧2025-07-07

