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

SpringBoot基于Minio實現(xiàn)分片上傳、斷點續(xù)傳的實現(xiàn)

 更新時間:2023年08月09日 10:57:45   作者:喵只想打代碼  
本文主要介紹了SpringBoot基于Minio實現(xiàn)分片上傳、斷點續(xù)傳的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

一、準(zhǔn)備工作

安裝 Minio 服務(wù)后,在 SpringBoot 項目中使用以下代碼來獲取 MinioClient(用于操作 Minio 的服務(wù)端):

MinioClient client = MinioClient.builder()
                .endpoint("http://192.168.xx.133:9000")  // 服務(wù)端IP+端口
                .credentials(minioProperties.getAccessKey(), // 服務(wù)端用戶名
                             minioProperties.getSecretKey()) // 服務(wù)端密碼
                .build();

二、實現(xiàn)分片上傳+斷點續(xù)傳

2.1 思路

分片上傳和斷點續(xù)傳的實現(xiàn)過程中,需要在Minio內(nèi)部記錄已上傳的分片文件。

這些分片文件將以文件md5作為父目錄,分片文件的名字按照01,02,...的順序進(jìn)行命名。同時,還必須知道當(dāng)前文件的分片總數(shù),這樣就能夠根據(jù)總數(shù)來判斷文件是否上傳完畢了。

比如,一個文件被分成了10片,所以總數(shù)是10。當(dāng)前端發(fā)起上傳請求時,把一個個文件分片依次上傳,Minio 服務(wù)器中存儲的臨時文件依次是01、02、03 等等。

假設(shè)前端把05分片上傳完畢了之后斷開了連接,由于 Minio 服務(wù)器仍然存儲著01~05的分片文件,因此前端再次上傳文件時,只需從06序號開始上傳分片,而不用從頭開始傳輸。這就是所謂的斷點續(xù)傳。

2.2 代碼

① 分片上傳API

為了實現(xiàn)以上思路,考慮實現(xiàn)一個方法,用于上傳文件的某一個分片。

/**
     * 將文件進(jìn)行分片上傳
     * <p>有一個未處理的bug(雖然概率很低很低):</p>
     * 當(dāng)兩個線程同時上傳md5相同的文件時,由于兩者會定位到同一個桶的同一個臨時目錄,兩個線程會相互產(chǎn)生影響!
     * 
     * @param file 分片文件
     * @param currIndex 當(dāng)前文件的分片索引
     * @param totalPieces 切片總數(shù)(對于同一個文件,請確保切片總數(shù)始終不變)
     * @param md5 整體文件MD5
     * @return 剩余未上傳的文件索引集合
     */
    public FragResult uploadFileFragment(MultipartFile file,
                                  Integer currIndex, Integer totalPieces, String md5) throws Exception {
        checkNull(currIndex, totalPieces, md5);
        // 臨時文件存放桶
        if ( !this.bucketExists(DEFAULT_TEMP_BUCKET_NAME) ) {
            this.createBucket(DEFAULT_TEMP_BUCKET_NAME);
        }
        // 得到已上傳的文件索引
        Iterable<Result<Item>> results = this.getFilesByPrefix(DEFAULT_TEMP_BUCKET_NAME, md5.concat("/"), false);
        Set<Integer> savedIndex = Sets.newHashSet();
        boolean fileExists = false;
        for (Result<Item> item : results) {
            Integer idx = Integer.valueOf( getContentAfterSlash(item.get().objectName()) );
            if (currIndex.equals( idx )) {
                fileExists = true;
            }
            savedIndex.add( idx );
        }
        // 得到未上傳的文件索引
        Set<Integer> remainIndex = Sets.newTreeSet();
        for (int i = 0; i < totalPieces; i++) {
            if ( !savedIndex.contains(i) ) {
                remainIndex.add(i);
            }
        }
        if (fileExists) {
            return new FragResult(false, remainIndex, "index [" + currIndex + "] exists");
        }
        this.uploadFileStream(DEFAULT_TEMP_BUCKET_NAME, this.getFileTempPath(md5, currIndex, totalPieces), file.getInputStream());
        // 還剩一個索引未上傳,當(dāng)前上傳索引剛好是未上傳索引,上傳完當(dāng)前索引后就完全結(jié)束了。
        if ( remainIndex.size() == 1 && remainIndex.contains(currIndex) ) {
            return new FragResult(true, null, "completed");
        }
        return new FragResult(false, remainIndex, "index [" + currIndex + "] has been uploaded");
    }

值得注意的是,我在項目中實踐該方法時,上述參數(shù)都是由前端傳來的,因此文件分片過程發(fā)生在前端,分片的大小也由前端定義。

② 合并文件API

當(dāng)所有分片文件上傳完畢,需要手動調(diào)用 Minio 原生 API 來合并臨時文件(當(dāng)然,在上面的那個方法中,當(dāng)最后一個分片上傳完畢后直接執(zhí)行合并操作也是可以的)

臨時文件合并完畢后,將會自動刪除所有臨時文件。

/**
     * 合并分片文件,并放到指定目錄
     * 前提是之前已把所有分片上傳完畢。
     * 
     * @param bucketName 目標(biāo)文件桶名
     * @param targetName 目標(biāo)文件名(含完整路徑)
     * @param totalPieces 切片總數(shù)(對于同一個文件,請確保切片總數(shù)始終不變)
     * @param md5 文件md5
     * @return minio原生對象,記錄了文件上傳信息
     */
    public boolean composeFileFragment(String bucketName, String targetName, 
                                                   Integer totalPieces, String md5) throws Exception {
        checkNull(bucketName, targetName, totalPieces, md5);
        // 檢查文件索引是否都上傳完畢
        Iterable<Result<Item>> results = this.getFilesByPrefix(DEFAULT_TEMP_BUCKET_NAME, md5.concat("/"), false);
        Set<String> savedIndex = Sets.newTreeSet();
        for (Result<Item> item : results) {
            savedIndex.add( item.get().objectName() );
        }
        if (savedIndex.size() == totalPieces) {
            // 文件路徑 轉(zhuǎn) 文件合并對象
            List<ComposeSource> sourceObjectList = savedIndex.stream()
                    .map(filePath -> ComposeSource.builder()
                            .bucket(DEFAULT_TEMP_BUCKET_NAME)
                            .object( filePath )
                            .build())
                    .collect(Collectors.toList());
            ObjectWriteResponse objectWriteResponse = client.composeObject(
                    ComposeObjectArgs.builder()
                            .bucket(bucketName)
                            .object(targetName)
                            .sources(sourceObjectList)
                            .build());
            // 上傳成功,則刪除所有的臨時分片文件
            List<String> filePaths = Stream.iterate(0, i -> ++i)
                    .limit(totalPieces)
                    .map(i -> this.getFileTempPath(md5, i, totalPieces) )
                    .collect(Collectors.toList());
            Iterable<Result<DeleteError>> deleteResults = this.removeFiles(DEFAULT_TEMP_BUCKET_NAME, filePaths);
            // 遍歷錯誤集合(無元素則成功)
            for (Result<DeleteError> result : deleteResults) {
                DeleteError error = result.get();
                System.err.printf("[Bigfile] 分片'%s'刪除失敗! 錯誤信息: %s", error.objectName(), error.message());
            }
            return true;
        }
        throw new GlobalException("The fragment index is not complete. Please check parameters [totalPieces] or [md5]");
    }

以上方法的源碼我放到了https://github.com/sky-boom/minio-spring-boot-starter,對原生的 Minio API 進(jìn)行了封裝,抽取成了minio-spring-boot-starter組件,感興趣的朋友歡迎前去查看。

2.3 后端調(diào)用API示例

這里以單線程的分片上傳為例(即前端每次只上傳一個分片文件,調(diào)用分片上傳接口后,接口返回下一個分片文件的序號)

① Controller 層

    /**
     * 分片上傳
     * @param user 用戶對象
     * @param fileAddDto file: 分片文件, 
     *                   currIndex: 當(dāng)前分片索引, 
     *                   totalPieces: 分片總數(shù),
     *                   md5: 文件md5
     * @return 前端需上傳的下一個分片序號(-1表示上傳完成)
     */
    @PostMapping("/file/big/upload")
    public ResultData<String> uploadBigFile(User user, BigFileAddDto fileAddDto) {
        // 1.文件為空,返回失敗 (一般不是用戶的問題)
        if (fileAddDto.getFile() == null) {
            throw new GlobalException();
        }
        // 2.名字為空,或包含特殊字符,則提示錯誤
        String fileName = fileAddDto.getFile().getOriginalFilename();
        if (StringUtils.isEmpty(fileName) || fileName.matches(FileSysConstant.NAME_EXCEPT_SYMBOL)) {
            throw new GlobalException(ResultCode.INCORRECT_FILE_NAME);
        }
        // 3. 執(zhí)行分片上傳
        String result = fileSystemService.uploadBigFile(user, fileAddDto);
        return GlobalResult.success(result);
    }

② Service 層

    @Override
    public String uploadBigFile(User user, BigFileAddDto fileAddDto) {
        try {
            MultipartFile file = fileAddDto.getFile();
            Integer currIndex = fileAddDto.getCurrIndex();
            Integer totalPieces = fileAddDto.getTotalPieces();
            String md5 = fileAddDto.getMd5();
            log.info("[Bigfile] 上傳文件md5: {} ,分片索引: {}", md5, currIndex);
            FragResult fragResult = minioUtils.uploadFileFragment(file, currIndex, totalPieces, md5);
            // 分片全部上傳完畢
            if ( fragResult.isAllCompleted() ) {
                FileInfo fileInfo = getFileInfo(fileAddDto, user.getId());
                DBUtils.checkOperation( fileSystemMapper.insertFile(fileInfo) );
                String realPath = generateRealPath(generateVirtPath(fileAddDto.getParentPath(), file.getOriginalFilename()));
                // 發(fā)起文件合并請求, 無異常則成功
                minioUtils.composeFileFragment(getBucketByUsername(user.getUsername()), realPath, totalPieces, md5);
                return "-1";
            } else {
                Iterator<Integer> iterator = fragResult.getRemainIndex().iterator();
                if (iterator.hasNext()) {
                    String nextIndex = iterator.next().toString();
                    log.info("[BigFile] 下一個需上傳的文件索引是:{}", nextIndex);
                    return nextIndex;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.error("[Bigfile] 上傳文件時出現(xiàn)異常");
        throw new GlobalException(ResultCode.FILE_UPLOAD_ERROR);
    }

2.4 前端

前端主要負(fù)責(zé):

  • 規(guī)定文件分片的大?。ū热?M),然后把文件進(jìn)行拆分。
  • 計算文件分片的總數(shù),并按序號把分片文件依次傳遞給后端。
  • 前端每上傳完一個分片文件,接口都會返回下一個需要上傳的分片文件。此時前端把對應(yīng)的分片文件繼續(xù)上傳即可。
  • 當(dāng)接口返回“-1”,表示所有文件已上傳完畢。

前端代碼此處不展示,有緣后續(xù)再花時間補(bǔ)充吧………………

到此這篇關(guān)于SpringBoot基于Minio實現(xiàn)分片上傳、斷點續(xù)傳的實現(xiàn)的文章就介紹到這了,更多相關(guān)SpringBoot Minio分片上傳、斷點續(xù)傳內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java 多線程synchronized關(guān)鍵字詳解(六)

    Java 多線程synchronized關(guān)鍵字詳解(六)

    這篇文章主要介紹了Java 多線程synchronized關(guān)鍵字詳解(六)的相關(guān)資料,需要的朋友可以參考下
    2015-12-12
  • JAVA中List.addAll的基本語法與用法詳解

    JAVA中List.addAll的基本語法與用法詳解

    這篇文章主要給大家介紹了關(guān)于JAVA中List.addAll的基本語法與用法的相關(guān)資料,需要的朋友可以參考下
    2024-06-06
  • springmvc之獲取參數(shù)的方法(必看)

    springmvc之獲取參數(shù)的方法(必看)

    下面小編就為大家?guī)硪黄猻pringmvc之獲取參數(shù)的方法(必看)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08
  • 用Java實現(xiàn)24點游戲

    用Java實現(xiàn)24點游戲

    喜歡玩游戲的有福啦,文中有非常詳細(xì)的開發(fā)框架,按著框架來實現(xiàn)就好啦.而且24點游戲是經(jīng)典的紙牌益智游戲.,需要的朋友可以參考下
    2021-05-05
  • IDEA中使用Git的圖文教程

    IDEA中使用Git的圖文教程

    本文主要介紹了IDEA中使用Git的圖文教程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • MybatisPlus調(diào)用原生SQL的三種方法實例詳解

    MybatisPlus調(diào)用原生SQL的三種方法實例詳解

    這篇文章主要介紹了MybatisPlus調(diào)用原生SQL的三種方法,在有些情況下需要用到MybatisPlus查詢原生SQL,MybatisPlus其實帶有運行原生SQL的方法,我這里列舉三種,需要的朋友可以參考下
    2022-09-09
  • IDEA插件之快速刪除Java代碼中的注釋

    IDEA插件之快速刪除Java代碼中的注釋

    這篇文章主要介紹了IDEA插件之快速刪除Java代碼中的注釋,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-02-02
  • Java調(diào)用用戶芝麻信用分

    Java調(diào)用用戶芝麻信用分

    這篇文章主要為大家詳細(xì)介紹了Java調(diào)用用戶芝麻信用分,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-11-11
  • java反射技術(shù)與類使用示例

    java反射技術(shù)與類使用示例

    這篇文章主要介紹了java反射技術(shù)與類使用示例,需要的朋友可以參考下
    2014-04-04
  • Java實現(xiàn)Windows計算器界面

    Java實現(xiàn)Windows計算器界面

    這篇文章主要為大家詳細(xì)介紹了Java實現(xiàn)Windows計算器界面,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-06-06

最新評論

潮州市| 浦东新区| 高要市| 赣榆县| 上饶市| 珠海市| 从江县| 集安市| 望江县| 铁岭市| 长宁区| 沁水县| 南宫市| 黄龙县| 布拖县| 婺源县| 友谊县| 喀喇沁旗| 连南| 巴彦淖尔市| 邓州市| 芜湖市| 阜城县| 罗甸县| 龙海市| 游戏| 高州市| 南岸区| 丹巴县| 鄄城县| 曲沃县| 隆回县| 汽车| 青州市| 宽城| 印江| 永安市| 铜川市| 揭西县| 子洲县| 乌拉特中旗|