第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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...