最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot+RustFS 實現(xiàn)文件切片極速上傳的實例代碼

 更新時間:2025年09月10日 09:52:43   作者:分布式存儲與RustFS  
本文介紹利用SpringBoot和RustFS構(gòu)建高性能文件切片上傳系統(tǒng),實現(xiàn)大文件秒傳、斷點續(xù)傳和分片上傳等功能,具有一定的參考價值,感興趣的可以了解一下

本文將手把手教你如何通過 SpringBoot 和 RustFS 構(gòu)建高性能文件切片上傳系統(tǒng),解決大文件傳輸?shù)耐袋c,實現(xiàn)秒傳、斷點續(xù)傳和分片上傳等高級功能。

一、為什么選擇 RustFS + SpringBoot?

在傳統(tǒng)文件上傳方案中,大文件傳輸面臨諸多挑戰(zhàn):網(wǎng)絡(luò)傳輸不穩(wěn)定、服務(wù)器內(nèi)存溢出、上傳失敗需重新傳輸等。而 ?RustFS? 作為一款基于 Rust 語言開發(fā)的高性能分布式對象存儲系統(tǒng),具有以下突出優(yōu)勢:

  • ?高性能?:充分利用 Rust 的內(nèi)存安全和高并發(fā)特性,響應(yīng)速度極快
  • ?分布式架構(gòu)?:可擴(kuò)展且具備容錯能力,適用于海量數(shù)據(jù)存儲
  • ?AWS S3 兼容性?:支持標(biāo)準(zhǔn) S3 API,可與現(xiàn)有生態(tài)無縫集成
  • ?可視化管理?:內(nèi)置功能豐富的 Web 控制臺,管理更方便
  • ?開源友好?:采用 Apache 2.0 協(xié)議,鼓勵社區(qū)貢獻(xiàn)

結(jié)合 SpringBoot 的快速開發(fā)特性,我們可以輕松構(gòu)建企業(yè)級文件上傳解決方案。

二、環(huán)境準(zhǔn)備與部署

2.1 安裝 RustFS

使用 Docker 快速部署 RustFS:

# docker-compose.yml
version: '3.8'
services:
  rustfs:
    image: registry.cn-shanghai.aliyuncs.com/study-03/rustfs:latest
    container_name: rustfs
    ports:
      - "9000:9000"  # 管理控制臺
      - "9090:9090"  # API服務(wù)端口
    volumes:
      - ./data:/data
    environment:
      - RUSTFS_ROOT_USER=admin
      - RUSTFS_ROOT_PASSWORD=admin123
    restart: unless-stopped

運行 docker-compose up -d即可啟動服務(wù)。訪問 http://localhost:9000使用 admin/admin123 登錄管理控制臺。

2.2 SpringBoot 項目配置

pom.xml中添加必要依賴:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>io.minio</groupId>
        <artifactId>minio</artifactId>
        <version>8.5.2</version>
    </dependency>
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.11.0</version>
    </dependency>
</dependencies>

配置 application.yml:

rustfs:
  endpoint: http://localhost:9090
  access-key: admin
  secret-key: admin123
  bucket-name: mybucket

三、核心代碼實現(xiàn)

3.1 配置 RustFS 客戶端

@Configuration
@ConfigurationProperties(prefix = "rustfs")
public class RustFSConfig {
    private String endpoint;
    private String accessKey;
    private String secretKey;
    private String bucketName;

    @Bean
    public MinioClient rustFSClient() {
        return MinioClient.builder()
                .endpoint(endpoint)
                .credentials(accessKey, secretKey)
                .build();
    }
}

3.2 文件切片上傳服務(wù)

@Service
@Slf4j
public class FileUploadService {
    @Autowired
    private MinioClient rustFSClient;
    
    @Value("${rustfs.bucket-name}")
    private String bucketName;
    
    /**
     * 初始化分片上傳
     */
    public String initUpload(String fileName, String fileMd5) {
        String uploadId = UUID.randomUUID().toString();
        // 檢查文件是否已存在(秒傳實現(xiàn))
        if (checkFileExists(fileMd5)) {
            throw new RuntimeException("文件已存在,可直接秒傳");
        }
        // 存儲上傳記錄到Redis或數(shù)據(jù)庫
        redisTemplate.opsForValue().set("upload:" + fileMd5, uploadId);
        return uploadId;
    }
    
    /**
     * 上傳文件分片
     */
    public void uploadChunk(MultipartFile chunk, String fileMd5, 
                           int chunkIndex, int totalChunks) {
        try {
            // 生成分片唯一名稱
            String chunkName = fileMd5 + "_chunk_" + chunkIndex;
            
            // 上傳分片到RustFS
            rustFSClient.putObject(
                PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(chunkName)
                    .stream(chunk.getInputStream(), chunk.getSize(), -1)
                    .build()
            );
            
            // 記錄已上傳分片
            redisTemplate.opsForSet().add("chunks:" + fileMd5, chunkIndex);
            
        } catch (Exception e) {
            log.error("分片上傳失敗", e);
            throw new RuntimeException("分片上傳失敗");
        }
    }
    
    /**
     * 合并文件分片
     */
    public void mergeChunks(String fileMd5, String fileName, int totalChunks) {
        try {
            // 創(chuàng)建臨時文件
            Path tempFile = Files.createTempFile("merge_", ".tmp");
            
            try (FileOutputStream fos = new FileOutputStream(tempFile.toFile())) {
                // 按順序下載并合并所有分片
                for (int i = 0; i < totalChunks; i++) {
                    String chunkName = fileMd5 + "_chunk_" + i;
                    
                    try (InputStream is = rustFSClient.getObject(
                        GetObjectArgs.builder()
                            .bucket(bucketName)
                            .object(chunkName)
                            .build()
                    )) {
                        IOUtils.copy(is, fos);
                    }
                    
                    // 刪除已合并的分片
                    rustFSClient.removeObject(
                        RemoveObjectArgs.builder()
                            .bucket(bucketName)
                            .object(chunkName)
                            .build()
                    );
                }
            }
            
            // 上傳最終文件
            rustFSClient.uploadObject(
                UploadObjectArgs.builder()
                    .bucket(bucketName)
                    .object(fileName)
                    .filename(tempFile.toString())
                    .build()
            );
            
            // 清理臨時文件
            Files.deleteIfExists(tempFile);
            
            // 更新文件記錄
            saveFileRecord(fileMd5, fileName);
            
        } catch (Exception e) {
            log.error("分片合并失敗", e);
            throw new RuntimeException("分片合并失敗");
        }
    }
    
    /**
     * 檢查文件是否存在(秒傳功能)
     */
    private boolean checkFileExists(String fileMd5) {
        // 查詢數(shù)據(jù)庫或Redis檢查文件是否已存在
        return redisTemplate.hasKey("file:" + fileMd5);
    }
}

3.3 控制器實現(xiàn)

@RestController
@RequestMapping("/api/upload")
@Slf4j
public class FileUploadController {
    @Autowired
    private FileUploadService fileUploadService;
    
    /**
     * 初始化上傳
     */
    @PostMapping("/init")
    public ResponseEntity<?> initUpload(@RequestParam String fileName,
                                      @RequestParam String fileMd5) {
        try {
            String uploadId = fileUploadService.initUpload(fileName, fileMd5);
            return ResponseEntity.ok(Map.of("uploadId", uploadId));
        } catch (RuntimeException e) {
            return ResponseEntity.ok(Map.of("exists", true)); // 文件已存在
        }
    }
    
    /**
     * 上傳分片
     */
    @PostMapping("/chunk")
    public ResponseEntity<String> uploadChunk(@RequestParam MultipartFile chunk,
                                             @RequestParam String fileMd5,
                                             @RequestParam int chunkIndex,
                                             @RequestParam int totalChunks) {
        fileUploadService.uploadChunk(chunk, fileMd5, chunkIndex, totalChunks);
        return ResponseEntity.ok("分片上傳成功");
    }
    
    /**
     * 合并分片
     */
    @PostMapping("/merge")
    public ResponseEntity<String> mergeChunks(@RequestParam String fileMd5,
                                             @RequestParam String fileName,
                                             @RequestParam int totalChunks) {
        fileUploadService.mergeChunks(fileMd5, fileName, totalChunks);
        return ResponseEntity.ok("文件合并成功");
    }
    
    /**
     * 獲取已上傳分片列表(斷點續(xù)傳)
     */
    @GetMapping("/chunks/{fileMd5}")
    public ResponseEntity<List<Integer>> getUploadedChunks(@PathVariable String fileMd5) {
        Set<Object> uploaded = redisTemplate.opsForSet().members("chunks:" + fileMd5);
        List<Integer> chunks = uploaded.stream()
                .map(obj -> Integer.parseInt(obj.toString()))
                .collect(Collectors.toList());
        return ResponseEntity.ok(chunks);
    }
}

四、前端實現(xiàn)關(guān)鍵代碼

4.1 文件切片處理

class FileUploader {
    constructor() {
        this.chunkSize = 5 * 1024 * 1024; // 5MB分片大小
        this.concurrentLimit = 3; // 并發(fā)上傳數(shù)
    }
    
    // 計算文件MD5(秒傳功能)
    async calculateFileMD5(file) {
        return new Promise((resolve) => {
            const reader = new FileReader();
            const spark = new SparkMD5.ArrayBuffer();
            
            reader.onload = e => {
                spark.append(e.target.result);
                resolve(spark.end());
            };
            
            reader.readAsArrayBuffer(file);
        });
    }
    
    // 切片上傳
    async uploadFile(file) {
        // 計算文件MD5
        const fileMd5 = await this.calculateFileMD5(file);
        
        // 初始化上傳
        const initResponse = await fetch('/api/upload/init', {
            method: 'POST',
            body: JSON.stringify({
                fileName: file.name,
                fileMd5: fileMd5
            }),
            headers: {
                'Content-Type': 'application/json'
            }
        });
        
        const initResult = await initResponse.json();
        
        // 如果文件已存在,直接返回
        if (initResult.exists) {
            alert('文件已存在,秒傳成功!');
            return;
        }
        
        // 獲取已上傳分片(斷點續(xù)傳)
        const uploadedChunks = await this.getUploadedChunks(fileMd5);
        
        // 計算分片信息
        const totalChunks = Math.ceil(file.size / this.chunkSize);
        const uploadPromises = [];
        
        for (let i = 0; i < totalChunks; i++) {
            // 跳過已上傳的分片
            if (uploadedChunks.includes(i)) {
                continue;
            }
            
            const start = i * this.chunkSize;
            const end = Math.min(file.size, start + this.chunkSize);
            const chunk = file.slice(start, end);
            
            // 控制并發(fā)數(shù)
            if (uploadPromises.length >= this.concurrentLimit) {
                await Promise.race(uploadPromises);
            }
            
            const uploadPromise = this.uploadChunk(chunk, fileMd5, i, totalChunks)
                .finally(() => {
                    const index = uploadPromises.indexOf(uploadPromise);
                    if (index > -1) {
                        uploadPromises.splice(index, 1);
                    }
                });
            
            uploadPromises.push(uploadPromise);
        }
        
        // 等待所有分片上傳完成
        await Promise.all(uploadPromises);
        
        // 合并分片
        await this.mergeChunks(fileMd5, file.name, totalChunks);
    }
    
    // 上傳單個分片
    async uploadChunk(chunk, fileMd5, chunkIndex, totalChunks) {
        const formData = new FormData();
        formData.append('chunk', chunk);
        formData.append('fileMd5', fileMd5);
        formData.append('chunkIndex', chunkIndex);
        formData.append('totalChunks', totalChunks);
        
        const response = await fetch('/api/upload/chunk', {
            method: 'POST',
            body: formData
        });
        
        if (!response.ok) {
            throw new Error(`分片上傳失敗: ${response.statusText}`);
        }
    }
}

五、高級功能與優(yōu)化

5.1 斷點續(xù)傳實現(xiàn)

通過記錄已上傳的分片信息,實現(xiàn)上傳中斷后從中斷處繼續(xù)上傳:

@Service
public class UploadProgressService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 獲取已上傳分片列表
     */
    public List<Integer> getUploadedChunks(String fileMd5) {
        Set<Object> uploaded = redisTemplate.opsForSet().members("chunks:" + fileMd5);
        return uploaded.stream()
                .map(obj -> Integer.parseInt(obj.toString()))
                .collect(Collectors.toList());
    }
    
    /**
     * 清理上傳記錄
     */
    public void clearUploadRecord(String fileMd5) {
        redisTemplate.delete("chunks:" + fileMd5);
        redisTemplate.delete("upload:" + fileMd5);
    }
}

5.2 分片驗證與安全

確保分片傳輸?shù)耐暾院桶踩裕?/p>

/**
 * 分片驗證服務(wù)
 */
@Service
public class ChunkValidationService {
    
    /**
     * 驗證分片哈希
     */
    public boolean validateChunkHash(MultipartFile chunk, String expectedHash) {
        try {
            String actualHash = HmacUtils.hmacSha256Hex("secret-key", 
                chunk.getBytes());
            return actualHash.equals(expectedHash);
        } catch (IOException e) {
            return false;
        }
    }
    
    /**
     * 驗證分片順序
     */
    public boolean validateChunkOrder(String fileMd5, int chunkIndex) {
        // 獲取已上傳分片
        Set<Object> uploaded = redisTemplate.opsForSet().members("chunks:" + fileMd5);
        List<Integer> chunks = uploaded.stream()
                .map(obj -> Integer.parseInt(obj.toString()))
                .sorted()
                .collect(Collectors.toList());
        
        // 檢查分片是否按順序上傳
        return chunks.isEmpty() || chunkIndex == chunks.size();
    }
}

六、部署與性能優(yōu)化

6.1 系統(tǒng)級優(yōu)化建議

?分片大小選擇?

  • 內(nèi)網(wǎng)環(huán)境:10MB-20MB
  • 移動網(wǎng)絡(luò):1MB-5MB
  • 廣域網(wǎng):500KB-1MB

?并發(fā)控制?

# 應(yīng)用配置
spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 100MB

?定時清理策略?

@Scheduled(fixedRate = 24 * 60 * 60 * 1000) // 每日清理
public void cleanTempFiles() {
    // 刪除超過24小時的臨時分片
    redisTemplate.keys("chunks:*").forEach(key -> {
        if (redisTemplate.getExpire(key) < 0) {
            redisTemplate.delete(key);
        }
    });
}

6.2 監(jiān)控與告警

集成 Prometheus 監(jiān)控上傳性能:

# 監(jiān)控指標(biāo)配置
management:
  endpoints:
    web:
      exposure:
        include: health,metrics,prometheus
  metrics:
    tags:
      application: ${spring.application.name}

七、總結(jié)

通過 SpringBoot 和 RustFS 的組合,我們實現(xiàn)了一個高性能的文件切片上傳系統(tǒng),具備以下優(yōu)勢:

  • ?高性能?:利用 RustFS 的高并發(fā)特性和分片并行上傳,大幅提升傳輸速度
  • ?可靠性?:斷點續(xù)傳機(jī)制確保上傳中斷后從中斷處繼續(xù),避免重復(fù)勞動
  • ?智能優(yōu)化?:秒傳功能避免重復(fù)文件上傳,節(jié)省帶寬和存儲空間
  • ?易于擴(kuò)展?:分布式架構(gòu)支持水平擴(kuò)展,適應(yīng)不同規(guī)模的應(yīng)用場景

這種方案特別適用于:

  • 視頻平臺的大文件上傳
  • 企業(yè)級文檔管理系統(tǒng)
  • 云存儲和備份服務(wù)
  • AI 訓(xùn)練數(shù)據(jù)集上傳

到此這篇關(guān)于SpringBoot+RustFS 實現(xiàn)文件切片極速上傳的實例代碼的文章就介紹到這了,更多相關(guān)SpringBoot RustFS 文件切片上傳內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論

长葛市| 连城县| 正定县| 玉门市| 峨眉山市| 昌图县| 淮安市| 桐梓县| 平乐县| 汕尾市| 青阳县| 乐陵市| 德惠市| 临洮县| 乌拉特中旗| 唐山市| 吉木萨尔县| 桐乡市| 合肥市| 鄂尔多斯市| 漳平市| 崇义县| 集安市| 图木舒克市| 巴南区| 德兴市| 张北县| 赫章县| 平定县| 清镇市| 南涧| 平武县| 辽中县| 横峰县| 富民县| 庆城县| 交城县| 宜兴市| 柘荣县| 惠安县| 织金县|