第2-3-8章 分片上傳和分片合併的介面開發-文件存儲服務系統-nginx/fastDFS/minio/阿裡雲oss/七牛雲oss

来源:https://www.cnblogs.com/gitBook/archive/2022/11/21/16910229.html
-Advertisement-
Play Games

5.10 介面開發-分片上傳 第2-1-2章 傳統方式安裝FastDFS-附FastDFS常用命令 第2-1-3章 docker-compose安裝FastDFS,實現文件存儲服務 第2-1-5章 docker安裝MinIO實現文件存儲服務-springboot整合minio-minio全網最全的資 ...


目錄

5.10 介面開發-分片上傳

第2-1-2章 傳統方式安裝FastDFS-附FastDFS常用命令
第2-1-3章 docker-compose安裝FastDFS,實現文件存儲服務
第2-1-5章 docker安裝MinIO實現文件存儲服務-springboot整合minio-minio全網最全的資料

全套代碼及資料全部完整提供,點此處下載

5.10.1 分片上傳介紹

前面我們已經實現了普通的附件服務和網盤服務,如果上傳的文件比較小,可以直接使用這兩個服務即可。如果上傳的文件比較大,例如要上傳一個500M或者1G的視頻文件(或者更大),這就需要分片上傳了。那麼什麼是分片上傳呢?

分片上傳就是把一個大文件進行分片,一片一片的上傳到服務端,最後由服務端進行分片的合併。

要實現分片上傳需要前端和後端配合來完成。在進行分片上傳時,一般是由前端對要上傳的大文件進行分片,然後分多次將這些分片上傳到服務端,所有分片都上傳到服務端後,在服務端將分片合併為原始的大文件。採用大文件分片併發上傳,可以極大的提高文件的上傳效率。

5.10.2 前端分片上傳插件webuploader

WebUploader是由Baidu WebFE(FEX)團隊開發的一個簡單的以HTML5為主,FLASH為輔的現代文件上傳組件。在現代的瀏覽器裡面能充分發揮HTML5的優勢,同時又不摒棄主流IE瀏覽器,沿用原來的FLASH運行時,相容IE6+,iOS 6+, android 4+。

官網地址:http://fex.baidu.com/webuploader/

分片與併發結合,將一個大文件分割成多塊,併發上傳,極大地提高大文件的上傳速度。

當網路問題導致傳輸錯誤時,只需要重傳出錯分片,而不是整個文件。另外分片傳輸能夠更加實時的跟蹤上傳進度。

由於本文展示的主要為後端服務開發,所以前端部分不再開發,直接從資料中獲得使用即可。

資料位置:文件服務\資料\分片上傳\前端

直接打開index.html頁面,選擇要上傳的大文件,可以看到發送了多次請求,每次請求會上傳此大文件的一個分片:
在這裡插入圖片描述

註:由於目前後端服務還沒有開發,所以上傳會失敗。

5.10.3 後端代碼實現

5.10.3.1 介面文檔

在這裡插入圖片描述

在這裡插入圖片描述

5.10.3.2 代碼開發

第一步:創建FileChunkController並提供分片上傳方法uploadFile

package com.itheima.pinda.file.controller;

import com.itheima.pinda.base.BaseController;
import com.itheima.pinda.base.R;
import com.itheima.pinda.dozer.DozerUtils;
import com.itheima.pinda.file.domain.FileAttrDO;
import com.itheima.pinda.file.dto.chunk.FileChunksMergeDTO;
import com.itheima.pinda.file.dto.chunk.FileUploadDTO;
import com.itheima.pinda.file.entity.File;
import com.itheima.pinda.file.manager.WebUploader;
import com.itheima.pinda.file.properties.FileServerProperties;
import com.itheima.pinda.file.service.FileService;
import com.itheima.pinda.file.strategy.FileChunkStrategy;
import com.itheima.pinda.file.strategy.FileStrategy;
import com.itheima.pinda.file.utils.FileDataTypeUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
 * 分片上傳
 */
@RestController
@Slf4j
@RequestMapping("/chunk")
@CrossOrigin
@Api(value = "分片上傳", tags = "分片上傳,需要webuploder.js插件進行配合使用")
public class FileChunkController extends BaseController {
    @Autowired
    private FileServerProperties fileProperties;
    @Autowired
    private FileService fileService;
    @Autowired
    private FileStrategy fileStrategy;
    @Autowired
    private WebUploader webUploader;
    @Autowired
    private DozerUtils dozerUtils;
    /**
     * 分片上傳
     * @param fileUploadDTO
     * @param multipartFile
     * @return
     */
    @ApiOperation(value = "分片上傳", notes = "分片上傳")
    @PostMapping(value = "/upload")
    public R<FileChunksMergeDTO> uploadFile(FileUploadDTO fileUploadDTO,
                                            @RequestParam(value = "file", required = false) MultipartFile multipartFile) throws Exception {

        if (multipartFile == null || multipartFile.isEmpty()) {
            log.error("分片上傳分片為空");
            return fail("分片上傳分片為空");
        }

        //  存放分片文件的伺服器絕對路徑 ,例如 D:\\uploadfiles\\2020\\04
        String uploadFolder = FileDataTypeUtil.getUploadPathPrefix(fileProperties.getStoragePath());

        if (fileUploadDTO.getChunks() == null || fileUploadDTO.getChunks() <= 0) {
            //沒有分片,按照普通文件上傳處理
            File file = fileStrategy.upload(multipartFile);
            file.setFileMd5(fileUploadDTO.getMd5());
            
            fileService.save(file);

            return success(null);
        } else {
            //為上傳的文件準備好對應的位置
            java.io.File targetFile = webUploader.getReadySpace(fileUploadDTO, uploadFolder);

            if (targetFile == null) {
                return fail("分片上傳失敗");
            }
            //保存上傳文件
            multipartFile.transferTo(targetFile);

            //封裝信息給前端,用於分片合併
            FileChunksMergeDTO mergeDTO = new FileChunksMergeDTO();
            mergeDTO.setSubmittedFileName(multipartFile.getOriginalFilename());
            dozerUtils.map(fileUploadDTO,mergeDTO);

            return success(mergeDTO);
        }
    }
}

第二步:在配置屬性類中添加storagePath屬性和對於的get、set方法

public String getStoragePath() {
    return storagePath;
}

public void setStoragePath(String storagePath) {
    this.storagePath = storagePath;
}

//指定分片上傳時臨時存放目錄
private String storagePath ;

第三步:創建WebUploader分片上傳工具類

package com.itheima.pinda.file.manager;

import com.itheima.pinda.file.dto.chunk.FileUploadDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.IOException;
/**
 * 分片上傳工具類
 */
@Service
@Slf4j
public class WebUploader2 {
    /**
     * 為上傳的文件創建對應的保存位置,若上傳的是分片,則會創建對應的文件夾結構和tmp文件
     *
     * @param fileUploadDTO 上傳文件的相關信息
     * @param path 文件保存根路徑
     * @return
     */
    public java.io.File getReadySpace(FileUploadDTO fileUploadDTO, String path) {
        //創建上傳文件所需的文件夾
        if (!this.createFileFolder(path, false)) {
            return null;
        }

        //將上傳的分片保存在此目錄中
        String fileFolder = fileUploadDTO.getName();

        if (fileFolder == null) {
            return null;
        }

        //文件上傳路徑更新為指定文件信息簽名後的臨時文件夾,用於後期合併
        path += "/" + fileFolder;

        if (!this.createFileFolder(path, true)) {
            return null;
        }

        //分片上傳,指定當前分片文件的文件名
        String newFileName = String.valueOf(fileUploadDTO.getChunk());
        return new java.io.File(path, newFileName);
    }

    /**
     * 創建存放分片上傳的文件的文件夾
     *
     * @param file   文件夾路徑
     * @param hasTmp 是否有臨時文件
     * @return
     */
    private boolean createFileFolder(String file, boolean hasTmp) {
        //創建存放分片文件的臨時文件夾
        java.io.File tmpFile = new java.io.File(file);
        if (!tmpFile.exists()) {
            try {
                tmpFile.mkdirs();
            } catch (SecurityException ex) {
                log.error("無法創建文件夾", ex);
                return false;
            }
        }

        if (hasTmp) {
            //創建臨時文件,用來記錄上傳分片文件的修改時間,用於清理長期未完成的垃圾分片
            tmpFile = new java.io.File(file + ".tmp");
            if (tmpFile.exists()) {
                return tmpFile.setLastModified(System.currentTimeMillis());
            } else {
                try {
                    tmpFile.createNewFile();
                } catch (IOException ex) {
                    log.error("無法創建tmp文件", ex);
                    return false;
                }
            }
        }
        return true;
    }
}

第四步:修改Nacos配置中心的pd-file-server.yml文件,加入storagePath配置項

5.10.3.3 介面測試

第一步:啟動Nacos配置中心

第二步:啟動Nginx服務

第三步:啟動文件服務

第四步:訪問分片上傳頁面,進行大文件上傳

可以看到,上傳完成後,對應的分片上傳所需目錄、臨時文件、分片文件都已經創建成功了:

在這裡插入圖片描述
在這裡插入圖片描述

5.11 介面開發-分片合併

前面我們已經完成了分片上傳的介面,本小節需要完成的是將這些分片文件合併為原始文件並按照配置文件配置的存儲策略保存到相應位置。由於不同的存儲方式對應的分片合併方式也不同,所以我們需要提供不同的分片合併處理策略。具體介面設計如下:

在這裡插入圖片描述

5.11.1 FileChunkStrategy

FileChunkStrategy是分片文件處理策略頂層介面,是對分片文件處理的頂層抽象,具體代碼如下:

package com.itheima.pinda.file.strategy;

import com.itheima.pinda.base.R;
import com.itheima.pinda.file.dto.chunk.FileChunksMergeDTO;
import com.itheima.pinda.file.entity.File;
/**
 * 文件分片處理策略介面
 */
public interface FileChunkStrategy {
    /**
     * 分片合併
     *
     * @param merge
     * @return
     */
    R<File> chunksMerge(FileChunksMergeDTO merge);
}

5.11.2 AbstractFileChunkStrategy

AbstractFileChunkStrategy是抽象分片策略處理類,實現了FileChunkStrategy介面。AbstractFileChunkStrategy實現主要的分片合併處理流程,例如:分片臨時存儲路徑獲取、分片數量的檢查、合併後臨時分片文件清理、合併後將文件信息保存到資料庫等,但是真正分片合併的處理過程需要其子類來完成,因為不同的存儲方案處理方式是不同的。

由於在進行分片合併處理過程中需要鎖,在資料中(文件服務\資料\分片上傳\後端)已經提供了工具類,直接導入項目使用即可。

AbstractFileChunkStrategy代碼如下:

package com.itheima.pinda.file.strategy.impl;

import com.itheima.pinda.base.R;
import com.itheima.pinda.file.dto.chunk.FileChunksMergeDTO;
import com.itheima.pinda.file.entity.File;
import com.itheima.pinda.file.enumeration.IconType;
import com.itheima.pinda.file.properties.FileServerProperties;
import com.itheima.pinda.file.service.FileService;
import com.itheima.pinda.file.strategy.FileChunkStrategy;
import com.itheima.pinda.file.utils.FileLock;
import com.itheima.pinda.file.utils.FileDataTypeUtil;
import com.itheima.pinda.utils.DateUtils;
import com.itheima.pinda.utils.NumberHelper;
import com.itheima.pinda.utils.StrPool;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.locks.Lock;
/**
 * 文件分片處理 抽象策略類
 */
@Slf4j
public abstract class AbstractFileChunkStrategy implements FileChunkStrategy {
    @Autowired
    protected FileService fileService;
    @Autowired
    protected FileServerProperties fileProperties;

    protected FileServerProperties.Properties properties;

    /**
     * 分片合併
     * @param info
     * @return
     */
    @Override
    public R<File> chunksMerge(FileChunksMergeDTO info) {
        //   570de89d476e6a5ba371f5fdd8d7920b.avi
        String filename = new StringBuilder(info.getName()).append(StrPool.DOT).append(info.getExt()).toString();
        //分片合併
        R<File> result = chunksMerge(info, filename);

        if (result.getIsSuccess() && result.getData() != null) {
            //文件名
            File filePo = result.getData();

            LocalDateTime now = LocalDateTime.now();
            filePo.setDataType(FileDataTypeUtil.getDataType(info.getContextType()))
                    .setCreateMonth(DateUtils.formatAsYearMonthEn(now))
                    .setCreateWeek(DateUtils.formatAsYearWeekEn(now))
                    .setCreateDay(DateUtils.formatAsDateEn(now))
                    .setSubmittedFileName(info.getSubmittedFileName())
                    .setIsDelete(false)
                    .setSize(info.getSize())
                    .setFileMd5(info.getMd5())
                    .setContextType(info.getContextType())
                    .setFilename(filename)
                    .setExt(info.getExt())
                    .setIcon(IconType.getIcon(info.getExt()).getIcon());

            //將上傳的文件信息保存到資料庫
            fileService.save(filePo);
            return R.success(filePo);
        }
        return result;
    }

    /**
     * 分片合併
     * @param info
     * @param fileName
     * @return
     */
    private R<File> chunksMerge(FileChunksMergeDTO info, String fileName) {
        //獲得分片文件存儲的路徑 D:\\chunks\\2020\\05
        String path = FileDataTypeUtil.getUploadPathPrefix(fileProperties.getStoragePath());
        int chunks = info.getChunks();
        String folder = info.getName();
        String md5 = info.getMd5();
        int chunksNum = this.getChunksNum(Paths.get(path, folder).toString());

        //檢查是否滿足合併條件:分片數量是否足夠
        if (chunks == chunksNum) {
            //同步指定合併的對象
            Lock lock = FileLock.getLock(folder);
            try {
                lock.lock();
                //檢查是否滿足合併條件:分片數量是否足夠
                List<java.io.File> files = new ArrayList<>(Arrays.asList(this.getChunks(Paths.get(path, folder).toString())));
                if (chunks == files.size()) {
                    //按照名稱排序文件,這裡分片都是按照數字命名的

                    //這裡存放的文件名一定是數字
                    files.sort((f1, f2) -> NumberHelper.intValueOf0(f1.getName()) - NumberHelper.intValueOf0(f2.getName()));

                    R<File> result = merge(files, fileName, info);

                    //清理:文件夾,tmp文件
                    this.cleanSpace(folder, path);
                    return result;
                }
            } catch (Exception ex) {
                log.error("數據分片合併失敗", ex);
                return R.fail("數據分片合併失敗");
            } finally {
                //解鎖
                lock.unlock();
                //清理鎖對象
                FileLock.removeLock(folder);
            }
        }

        log.error("文件[簽名:" + md5 + "]數據不完整,可能該文件正在合併中");
        return R.fail("數據不完整,可能該文件正在合併中, 也有可能是上傳過程中某些分片丟失");
    }

    /**
     * 子類實現具體的合併操作
     *
     * @param files    分片文件
     * @param fileName 唯一名 含尾碼
     * @param info     分片信息
     * @return
     * @throws IOException
     */
    protected abstract R<File> merge(List<java.io.File> files,  String fileName, FileChunksMergeDTO info) throws IOException;

    /**
     * 清理分片上傳的相關數據
     * 文件夾,tmp文件
     *
     * @param folder 文件夾名稱
     * @param path   上傳文件根路徑
     * @return
     */
    protected boolean cleanSpace(String folder, String path) {
        //刪除分片文件夾
        java.io.File garbage = new java.io.File(Paths.get(path, folder).toString());
        if (!FileUtils.deleteQuietly(garbage)) {
            return false;
        }
        //刪除tmp文件
        garbage = new java.io.File(Paths.get(path, folder + ".tmp").toString());
        if (!FileUtils.deleteQuietly(garbage)) {
            return false;
        }
        return true;
    }

    /**
     * 獲取指定文件的分片數量
     *
     * @param folder 文件夾路徑
     * @return
     */
    private int getChunksNum(String folder) {
        java.io.File[] filesList = this.getChunks(folder);
        return filesList.length;
    }

    /**
     * 獲取指定文件的所有分片
     *
     * @param folder 文件夾路徑
     * @return
     */
    private java.io.File[] getChunks(String folder) {
        java.io.File targetFolder = new java.io.File(folder);
        return targetFolder.listFiles((file) -> {
            if (file.isDirectory()) {
                return false;
            }
            return true;
        });
    }
}

5.11.3 LocalChunkServiceImpl

LocalChunkServiceImpl是AbstractFileChunkStrategy的子類,負責處理存儲策略為本地時的分片文件合併操作。為了使程式能夠動態選擇具體的策略處理類,故將LocalChunkServiceImpl定義在LocalAutoConfigure配置類中,具體代碼如下:

/**
* 本地分片文件處理策略類
*/
@Service
public class LocalChunkServiceImpl extends AbstractFileChunkStrategy {
    /**
         *分片合併
         * @param files    分片文件
         * @param fileName 唯一名 含尾碼
         * @param info     分片信息
         * @return
         * @throws IOException
     */
    @Override
    protected R<File> merge(List<java.io.File> files, String fileName, FileChunksMergeDTO info) throws IOException {
        properties = fileProperties.getLocal();

        //日期目錄
        String relativePath = Paths.get(LocalDate.now().format(DateTimeFormatter.ofPattern(DateUtils.DEFAULT_MONTH_FORMAT_SLASH))).toString();

        //合併後文件的存儲路徑 例如:D:\\uploadFiles\\oss-file-service\\2020\\05
        String path = Paths.get(properties.getEndpoint(), properties.getBucketName(), relativePath).toString();

        //上傳文件存放目錄,如果不存在則創建
        java.io.File uploadFolder = new java.io.File(path);
        if(!uploadFolder.exists()){
            uploadFolder.mkdirs();
        }

        //創建合併後的文件
        java.io.File outputFile = new java.io.File(Paths.get(path, fileName).toString());
        if (!outputFile.exists()) {
            boolean newFile = outputFile.createNewFile();
            if (!newFile) {
                return R.fail("創建文件失敗");
            }
            try (FileChannel outChannel = new FileOutputStream(outputFile).getChannel()) {
                //同步nio 方式對分片進行合併, 有效的避免文件過大導致記憶體溢出
                for (java.io.File file : files) {
                    try (FileChannel inChannel = new FileInputStream(file).getChannel()) {
                        inChannel.transferTo(0, inChannel.size(), outChannel);
                    } catch (FileNotFoundException ex) {
                        log.error("文件轉換失敗", ex);
                        return R.fail("文件轉換失敗");
                    }
                    //刪除分片
                    if (!file.delete()) {
                        log.error("分片[" + info.getName() + "=>" + file.getName() + "]刪除失敗");
                    }
                }
            } catch (FileNotFoundException e) {
                log.error("文件輸出失敗", e);
                return R.fail("文件輸出失敗");
            }

        } else {
            log.warn("文件[{}], fileName={}已經存在", info.getName(), fileName);
        }

        String url = new StringBuilder(properties.getUriPrefix()).
                    append(bucketName).append(StrPool.SLASH).
                    append(relativePath).append(StrPool.SLASH).
                    append(fileName).toString();
        File filePo = File.builder()
            .relativePath(relativePath)
            .url(StringUtils.replace(url, "\\", StrPool.SLASH))
            .build();
        return R.success(filePo);
    }
}

5.11.4 FastDfsChunkServiceImpl

FastDfsChunkServiceImpl是AbstractFileChunkStrategy的子類,負責處理存儲策略為FastDFS時的分片文件合併操作。為了使程式能夠動態選擇具體的策略處理類,故將FastDfsChunkServiceImpl定義在FastDfsAutoConfigure配置類中,具體代碼如下:

/**
* FastDfs分片文件處理策略類
*/
@Service
public class FastDfsChunkServiceImpl extends AbstractFileChunkStrategy {
    @Autowired
    protected AppendFileStorageClient storageClient;

    /**
         * 分片合併
         * @param files    分片文件
         * @param fileName 唯一名 含尾碼
         * @param info     分片信息
         * @return
         * @throws IOException
    */
    @Override
    protected R<File> merge(List<java.io.File> files, String fileName, FileChunksMergeDTO info) throws IOException {
        StorePath storePath = null;

        for (int i = 0; i < files.size(); i++) {
            java.io.File file = files.get(i);

            FileInputStream in = FileUtils.openInputStream(file);
            if (i == 0) {
                storePath = storageClient.uploadAppenderFile(null, in,
                                                             file.length(), info.getExt());
            } else {
                storageClient.appendFile(storePath.getGroup(), storePath.getPath(),
                                         in, file.length());
            }
        }
        if (storePath == null) {
            return R.fail("上傳失敗");
        }

        String url = new StringBuilder(fileProperties.getUriPrefix())
            .append(storePath.getFullPath())
            .toString();
        File filePo = File.builder()
            .url(url)
            .group(storePath.getGroup())
            .path(storePath.getPath())
            .build();
        return R.success(filePo);
    }
}

5.11.5 AliChunkServiceImpl

AliChunkServiceImpl是AbstractFileChunkStrategy的子類,負責處理存儲策略為阿裡雲OSS時的分片文件合併操作。為了使程式能夠動態選擇具體的策略處理類,故將AliChunkServiceImpl定義在AliOssAutoConfigure配置類中,具體代碼如下:

/**
* 阿裡雲OSS分片文件處理策略類
*/
@Service
public class AliChunkServiceImpl extends AbstractFileChunkStrategy {
    private OSS buildClient() {
        properties = fileProperties.getAli();
        return new OSSClientBuilder().build(properties.getEndpoint(), properties.getAccessKeyId(),
                                            properties.getAccessKeySecret());
    }

    /**
         * 分片合併
         * @param files    分片文件
         * @param fileName 唯一名 含尾碼
         * @param info     分片信息
         * @return
         * @throws IOException
    */
    @Override
    protected R<File> merge(List<java.io.File> files, String fileName, FileChunksMergeDTO info) throws IOException {
        OSS client = buildClient();
        String bucketName = properties.getBucketName();

        //日期文件夾
        String relativePath = LocalDate.now().format(DateTimeFormatter.ofPattern(DEFAULT_MONTH_FORMAT_SLASH));
        // web伺服器存放的相對路徑
        String relativeFileName = relativePath + StrPool.SLASH + fileName;

        ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentDisposition("attachment;fileName=" + info.getSubmittedFileName());
        metadata.setContentType(info.getContextType());
        //步驟1:初始化一個分片上傳事件。
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, relativeFileName, metadata);
        InitiateMultipartUploadResult result = client.initiateMultipartUpload(request);
        // 返回uploadId,它是分片上傳事件的唯一標識,您可以根據這個ID來發起相關的操作,如取消分片上傳、查詢分片上傳等。
        String uploadId = result.getUploadId();

        // partETags是PartETag的集合。PartETag由分片的ETag和分片號組成。
        List<PartETag> partETags = new ArrayList<PartETag>();
        for (int i = 0; i < files.size(); i++) {
            java.io.File file = files.get(i);
            FileInputStream in = FileUtils.openInputStream(file);

            UploadPartRequest uploadPartRequest = new UploadPartRequest();
            uploadPartRequest.setBucketName(bucketName);
            uploadPartRequest.setKey(relativeFileName);
            uploadPartRequest.setUploadId(uploadId);
            uploadPartRequest.setInputStream(in);
            // 設置分片大小。除了最後一個分片沒有大小限制,其他的分片最小為100KB。
            uploadPartRequest.setPartSize(file.length());
            // 設置分片號。每一個上傳的分片都有一個分片號,取值範圍是1~10000,如果超出這個範圍,OSS將返回InvalidArgument的錯誤碼。
            uploadPartRequest.setPartNumber(i + 1);

            // 每個分片不需要按順序上傳,甚至可以在不同客戶端上傳,OSS會按照分片號排序組成完整的文件。
            UploadPartResult uploadPartResult = client.uploadPart(uploadPartRequest);

            // 每次上傳分片之後,OSS的返回結果會包含一個PartETag。PartETag將被保存到partETags中。
            partETags.add(uploadPartResult.getPartETag());
        }

        /* 步驟3:完成分片上傳。 */
        // 排序。partETags必須按分片號升序排列。
        partETags.sort(Comparator.comparingInt(PartETag::getPartNumber));

        // 在執行該操作時,需要提供所有有效的partETags。OSS收到提交的partETags後,會逐一驗證每個分片的有效性。當所有的數據分片驗證通過後,OSS將把這些分片組合成一個完整的文件。
        CompleteMultipartUploadRequest completeMultipartUploadRequest =
            new CompleteMultipartUploadRequest(bucketName, relativeFileName, uploadId, partETags);

        CompleteMultipartUploadResult uploadResult = client.completeMultipartUpload(completeMultipartUploadRequest);

        String url = new StringBuilder(properties.getUriPrefix())
            .append(relativePath)
            .append(StrPool.SLASH)
            .append(fileName)
            .toString();
        File filePo = File.builder()
            .relativePath(relativePath)
            .group(uploadResult.getETag())
            .path(uploadResult.getRequestId())
            .url(StringUtils.replace(url, "\\", StrPool.SLASH))
            .build();

        // 關閉OSSClient。
        client.shutdown();
        return R.success(filePo);
    }
}

5.11.6 MinioChunkServiceImpl

MinioChunkServiceImpl是AbstractFileChunkStrategy的子類,負責處理存儲策略為MINIO時的分片文件合併操作。為了使程式能夠動態選擇具體的策略處理類,故將MinioChunkServiceImpll定義在MinioAutoConfigure配置類中,具體代碼如下:

    /**
     * 分片文件策略處理類
     */
    @Service
    public class MinioChunkServiceImpl extends AbstractFileChunkStrategy {

        /**
         * 分片合併抽象方法,需要子類實現
         *
         * @param files
         * @param fileName
         * @param fileChunksMergeDTO
         * @return
         */
        @Override
        protected R<File> merge(List<java.io.File> files, String fileName, FileChunksMergeDTO fileChunksMergeDTO) throws Exception {
            MinioAutoConfigure.this.buildClient(fileServerProperties);
            Vector<InputStream> streams = new Vector<>();
            //分片合併成功,需要封裝File對象相關屬性
            File fileResult = new File();

            for (java.io.File file : files) {//file對應的就是分片文件
                streams.add(new FileInputStream(file));
                new FileInputStream(file).available();
                //刪除當前分片
                file.delete();
            }

            //生成滿足要求的objectName和url
            String objectName = doReName(fileName, fileResult);
            //sequenceInputStream直接使用只能獲取第一個分片的數據,故先全部轉成輸出流再轉成輸入流
            //存在問題:
            //1.本身這個實現就不優雅
            //2.OutOfMemoryError: Java heap space,測試同時傳三個幾百M的文件會發生記憶體溢出
            //目前是分片文件上傳到伺服器,再程式里合併後再上傳到minio,下麵提供了很多minio的工具類,可以改成分片文件上傳到minio,利用minioClient合併文件,目前未實現
            try (SequenceInputStream sequenceInputStream = new SequenceInputStream(streams.elements());
                 ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
                byte[] bytes = new byte[sequenceInputStream.available()];
                int len;
                while ((len = sequenceInputStream.read(bytes)) != -1) {
                    outputStream.write(bytes, 0, len);
                }

                byte[] outBytes = outputStream.toByteArray();
                ByteBuffer buffer = ByteBuffer.wrap(outBytes);
                try (ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer.array())) {
                    // 使用putObject上傳一個文件到存儲桶中
                    PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .contentType(fileChunksMergeDTO.getContextType())
                            .stream(inputStream, inputStream.available(), ObjectWriteArgs.MIN_MULTIPART_SIZE).build();
                    minioClient.putObject(putObjectArgs);
                } catch (Exception ex) {
                    log.error("分片文件合併失敗");
                    return R.fail("分片文件合併失敗");
                }
            } catch (Exception ex) {
                log.error("分片文件合併失敗");
                return R.fail("分片文件合併失敗");
            }
            return R.success(fileResult);
        }
    }

5.11.7 分片合併介面

介面文檔:

在這裡插入圖片描述
在這裡插入圖片描述

在FileChunkController中提供分片合併方法,直接調用分片處理策略類完成分片合併操作:

@Autowired
private FileChunkStrategy fileChunkStrategy;//分片文件處理策略

/**
* 分片合併
* @param info
* @return
*/
@ApiOperation(value = "分片合併", notes = "所有分片上傳成功後,調用該介面對分片進行合併")
@PostMapping(value = "/merge")
public R<File> saveChunksMerge(FileChunksMergeDTO info) {
    log.info("info={}", info);

    return fileChunkStrategy.chunksMerge(info);
}

第2-1-2章 傳統方式安裝FastDFS-附FastDFS常用命令
第2-1-3章 docker-compose安裝FastDFS,實現文件存儲服務
第2-1-5章 docker安裝MinIO實現文件存儲服務-springboot整合minio-minio全網最全的資料

全套代碼及資料全部完整提供,點此處下載


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Linux的文件系統分層結構: Lunux文件系統常用的目錄如下: 根目錄(/):根目錄是整個系統最重要的一個目錄,因為不但所有的目錄都是由根目錄衍生出來的,同時根目錄也與開機、還原、系統修複等操作有關。 執行文件目錄(/bin):所有用戶使用的基本命令:不能關聯至獨立分區,OS啟動即會用到的程式 ...
  • 本文章沒有任何宣傳產品等盈利性質,所有的陳述均為博主真實的日常使用。 關於電腦 現在很多學電腦的同學總是抱怨自己的電腦卡卡卡,打開一看360,金山等安全軟體在瘋狂打架,桌面各種文件堆得也是稀碎,再打開文件資源管理器一看,好家伙,C盤都紅了,各種文件堆在盤裡也是亂七八糟。清理垃圾本身沒多煩,21世紀 ...
  • 一、安裝VMware Workstation虛擬機 下載VMware Workstation 16 PRO虛擬機 https://www.vmware.com/cn/products/workstation-pro/workstation-pro-evaluation.html,下載後安裝即可,安裝 ...
  • 1.虛擬機下載 官網下載地址:https://www.kali.org/get-kali/#kali-virtual-machines 選擇VMware版本下載,並解壓 2.打開虛擬機 選擇打開虛擬機,瀏覽到剛纔壓縮包解壓路徑,選擇.vmx文件打開 開啟此虛擬機 用戶名密碼都是kali 3.設置ro ...
  • 世界上的開源許可證(Open Source License)大概有上百種,而我們常用的開源軟體協議大致有GPL、BSD、MIT、Mozilla、Apache和LGPL。 從下圖中可以看出幾種開源軟體協議的區別。 以下是上述協議的簡單介紹: GPL GNU是GNU General Public Lic ...
  • GreatSQL社區原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。 GreatSQL是MySQL的國產分支版本,使用上與MySQL一致。 作者: 如常 Debezium Incremental snapshotting Introduction CDC(Change-Data-Captur ...
  • 資料庫面試測試題(一) 簡述當前主流RDBMS軟體有哪些?開源且跨平臺的資料庫軟體有哪些? 參考答案 當前主流的資料庫伺服器軟體有: Oracle 、 DB2 、 SQL SERVER 、MySQL 等 ,其中只有MySQL是既開源又跨平臺的資料庫服務軟體。 簡述MySQL資料庫的服務進程名、預設端 ...
  • 1、文本類指令 {{}}、v-text 都是用於綁定節點的文本; 二者區別:{{}}這種綁定值的方式在頁面會出現“{{}}”一閃而過的效果 解決{{}}在頁面出現一閃而過的辦法: // css: [v-cloak] { display: 'none' }// html <h1 v-cloak>{{m ...
一周排行
    -Advertisement-
    Play Games
  • 不廢話,直接代碼 private Stack<Action> actionStack = new Stack<Action>(); private void SetCellValues() { var worksheet = Globals.ThisAddIn.Application.ActiveS ...
  • OpenAPI 規範是用於描述 HTTP API 的標準。該標準允許開發人員定義 API 的形狀,這些 API 可以插入到客戶端生成器、伺服器生成器、測試工具、文檔等中。儘管該標準具有普遍性和普遍性,但 ASP.NET Core 在框架內預設不提供對 OpenAPI 的支持。 當前 ASP.NET ...
  • @DateTimeFormat 和 @JsonFormat 是 Spring 和 Jackson 中用於處理日期時間格式的註解,它們有不同的作用: @DateTimeFormat @DateTimeFormat 是 Spring 框架提供的註解,用於指定字元串如何轉換為日期時間類型,以及如何格式化日 ...
  • 一、背景說明 1.1 效果演示 用python開發的爬蟲採集軟體,可自動抓取抖音評論數據,並且含二級評論! 為什麼有了源碼還開發界面軟體呢?方便不懂編程代碼的小白用戶使用,無需安裝python、無需懂代碼,雙擊打開即用! 軟體界面截圖: 爬取結果截圖: 以上。 1.2 演示視頻 軟體運行演示視頻:見 ...
  • SpringBoot筆記 SpringBoot文檔 官網: https://spring.io/projects/spring-boot 學習文檔: https://docs.spring.io/spring-boot/docs/current/reference/html/ 線上API: http ...
  • 作為後端工程師,多數情況都是給別人提供介面,寫的好不好使你得重視起來。 最近我手頭一些活,需要和外部公司對接,我們需要提供一個介面文檔,這樣可以節省雙方時間、也可以防止後續扯皮。這是就要考驗我的介面是否規範化。 1. 介面名稱清晰、明確 顧名思義,介面是做什麼的,是否準確、清晰?讓使用這一眼就能知道 ...
  • 本文介紹基於Python語言,遍歷文件夾並從中找到文件名稱符合我們需求的多個.txt格式文本文件,並從上述每一個文本文件中,找到我們需要的指定數據,最後得到所有文本文件中我們需要的數據的合集的方法~ ...
  • Java JUC&多線程 基礎完整版 目錄Java JUC&多線程 基礎完整版1、 多線程的第一種啟動方式之繼承Thread類2、多線程的第二種啟動方式之實現Runnable介面3、多線程的第三種實現方式之實現Callable介面4、多線的常用成員方法5、線程的優先順序6、守護線程7、線程的讓出8、線 ...
  • 實時識別關鍵詞是一種能夠將搜索結果提升至新的高度的API介面。它可以幫助我們更有效地分析文本,並提取出關鍵詞,以便進行進一步的處理和分析。 該介面是挖數據平臺提供的,有三種模式:精確模式、全模式和搜索引擎模式。不同的模式在分詞的方式上有所不同,適用於不同的場景。 首先是精確模式。這種模式會儘量將句子 ...
  • 1 為啥要折騰搭建一個專屬圖床? 技術大佬寫博客都用 md 格式,要在多平臺發佈,圖片就得有外鏈 後續如博客遷移,國內博客網站如掘金,簡書,語雀等都做了防盜鏈,圖片無法遷移 2 為啥選擇CloudFlare R2 跳轉:https://dash.cloudflare.com/ 有白嫖額度 免費 CD ...