欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Springboot2.7+Minio8 實現(xiàn)大文件分片上傳

 更新時間:2024年01月02日 15:07:43   作者:xiaoyi學(xué)習(xí)  
本文主要介紹了Springboot2.7+Minio8 實現(xiàn)大文件分片上傳,通過文件切片上傳,我們能夠提高文件上傳的速度,優(yōu)化用戶體驗,具有一定的參考價值,感興趣的可以了解一下

 1. 介紹:

分片上傳: 將一個文件按照指定大小分割成多份數(shù)據(jù)塊(Part)分開上傳, 上傳之后再由服務(wù)端整合為原本的文件

分片上傳場景:

  • 網(wǎng)絡(luò)環(huán)境差: 當(dāng)出現(xiàn)上傳失敗的時候,只需要對失敗的Part進(jìn)行重新上傳
  • 斷點續(xù)傳: 中途暫停之后,可以從上次上傳完成的Part的位置繼續(xù)上傳
  • 加速上傳: 要上傳到OSS的本地文件很大的時候,可以并行上傳多個Part以加快上傳速度
  • 流式上傳: 可以在需要上傳的文件大小還不確定的情況下開始上傳,這種場景在視頻監(jiān)控等行業(yè)應(yīng)用中比較常見
  • 文件較大: 一般文件比較大時,默認(rèn)情況下一般都會采用分片上傳

分片上傳流程:

  • 將需要上傳的文件按照一定大小進(jìn)行分割(推薦1MB或者5MB),分割成相同大小的數(shù)據(jù)塊
  • 初始化一個分片上傳任務(wù),返回本次分片上傳唯一標(biāo)識(md5)
  • 按照一定的策略(串行或并行)發(fā)送各個分片數(shù)據(jù)塊
  • 發(fā)送完成后,服務(wù)端根據(jù)判斷數(shù)據(jù)上傳是否完整,如果完整,則進(jìn)行數(shù)據(jù)塊合成得到原始文件。

 2. 代碼部分:

application.yml

minio:
  minioUrl: http://ip地址:9000        # MinIO 服務(wù)地址-需要修改
  minioName: 賬號                     # MinIO 訪問密鑰-需要修改
  minioPass: 密碼                     # MinIO 秘鑰密碼-需要修改
  bucketName: 桶名                    # MinIO 桶名稱-需要修改
  region: ap-southeast-1              # MinIO 存儲區(qū)域,可以指定為 "ap-southeast-1"

spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 10MB

 pom.xml

<!--minio-->
<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.0.3</version>
</dependency>

MinioTemplate

/**
 * @author xiaoyi
 */
@Slf4j
@AllArgsConstructor
public class MinioTemplate {
    /**
     * MinIO 客戶端
     */
    private final MinioClient minioClient;
    /**
     * MinIO 配置類
     */
    private final MinioConfig minioConfig;

    /**
     * 查詢所有存儲桶
     *
     * @return Bucket 集合
     */
    @SneakyThrows
    public List<Bucket> listBuckets() {
        return minioClient.listBuckets();
    }

    /**
     * 查詢文件大小
     *
     * @return Bucket 集合
     */
    @SneakyThrows
    public Long getObjectSize(String bucketName, String objectName) {
        return minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build()).size();
    }

    /**
     * 桶是否存在
     *
     * @param bucketName 桶名
     * @return 是否存在
     */
    @SneakyThrows
    public boolean bucketExists(String bucketName) {
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * 創(chuàng)建存儲桶
     *
     * @param bucketName 桶名
     */
    @SneakyThrows
    public void makeBucket(String bucketName) {
        if (!bucketExists(bucketName)) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }

    /**
     * 刪除一個空桶 如果存儲桶存在對象不為空時,刪除會報錯。
     *
     * @param bucketName 桶名
     */
    @SneakyThrows
    public void removeBucket(String bucketName) {
        removeBucket(bucketName, false);
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

    /**
     * 刪除一個桶 根據(jù)桶是否存在數(shù)據(jù)進(jìn)行不同的刪除
     * 桶為空時直接刪除
     * 桶不為空時先刪除桶中的數(shù)據(jù),然后再刪除桶
     *
     * @param bucketName 桶名
     */
    @SneakyThrows
    public void removeBucket(String bucketName, boolean bucketNotNull) {
        if (bucketNotNull) {
            deleteBucketAllObject(bucketName);
        }
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

    /**
     * 上傳文件
     *
     * @param inputStream      流
     * @param originalFileName 原始文件名
     * @param bucketName       桶名
     * @return ObjectWriteResponse
     */
    @SneakyThrows
    public OssFile putObject(InputStream inputStream, String bucketName, String originalFileName) {
        String uuidFileName = generateFileInMinioName(originalFileName);
        try {
            if (ObjectUtils.isEmpty(bucketName)) {
                bucketName = minioConfig.getBucketName();
            }
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(uuidFileName)
                            .stream(inputStream, inputStream.available(), -1)
                            .build());


            return new OssFile(uuidFileName, originalFileName);
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }


    /**
     * 刪除桶中所有的對象
     *
     * @param bucketName 桶對象
     */
    @SneakyThrows
    public void deleteBucketAllObject(String bucketName) {
        List<String> list = listObjectNames(bucketName);
        if (!list.isEmpty()) {
            for (String objectName : list) {
                deleteObject(bucketName, objectName);
            }
        }
    }

    /**
     * 查詢桶中所有的對象名
     *
     * @param bucketName 桶名
     * @return objectNames
     */
    @SneakyThrows
    public List<String> listObjectNames(String bucketName) {
        List<String> objectNameList = new ArrayList<>();
        if (bucketExists(bucketName)) {
            Iterable<Result<Item>> results = listObjects(bucketName, true);
            for (Result<Item> result : results) {
                String objectName = result.get().objectName();
                objectNameList.add(objectName);
            }
        }
        return objectNameList;
    }


    /**
     * 刪除一個對象
     *
     * @param bucketName 桶名
     * @param objectName 對象名
     */
    @SneakyThrows
    public void deleteObject(String bucketName, String objectName) {
        minioClient.removeObject(RemoveObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .build());
    }

    /**
     * 上傳分片文件
     *
     * @param inputStream 流
     * @param objectName  存入桶中的對象名
     * @param bucketName  桶名
     * @return ObjectWriteResponse
     */
    @SneakyThrows
    public OssFile putChunkObject(InputStream inputStream, String bucketName, String objectName) {
        try {
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .stream(inputStream, inputStream.available(), -1)
                            .build());
            return new OssFile(objectName, objectName);
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }

    /**
     * 返回臨時帶簽名、Get請求方式的訪問URL
     *
     * @param bucketName 桶名
     * @param filePath   Oss文件路徑
     * @return 臨時帶簽名、Get請求方式的訪問URL
     */
    @SneakyThrows
    public String getPresignedObjectUrl(String bucketName, String filePath) {
        return minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.GET)
                        .bucket(bucketName)
                        .object(filePath)
                        .build());
    }

    /**
     * 返回臨時帶簽名、過期時間為1天的PUT請求方式的訪問URL
     *
     * @param bucketName  桶名
     * @param filePath    Oss文件路徑
     * @param queryParams 查詢參數(shù)
     * @return 臨時帶簽名、過期時間為1天的PUT請求方式的訪問URL
     */
    @SneakyThrows
    public String getPresignedObjectUrl(String bucketName, String filePath, Map<String, String> queryParams) {
        return minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.PUT)
                        .bucket(bucketName)
                        .object(filePath)
                        .expiry(1, TimeUnit.DAYS)
                        .extraQueryParams(queryParams)
                        .build());
    }


    /**
     * GetObject接口用于獲取某個文件(Object)。此操作需要對此Object具有讀權(quán)限。
     *
     * @param bucketName 桶名
     * @param objectName 文件路徑
     */
    @SneakyThrows
    public InputStream getObject(String bucketName, String objectName) {
        return minioClient.getObject(
                GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    /**
     * 查詢桶的對象信息
     *
     * @param bucketName 桶名
     * @param recursive  是否遞歸查詢
     * @return 桶的對象信息
     */
    @SneakyThrows
    public Iterable<Result<Item>> listObjects(String bucketName, boolean recursive) {
        return minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).recursive(recursive).build());
    }

    /**
     * 獲取帶簽名的臨時上傳元數(shù)據(jù)對象,前端可獲取后,直接上傳到Minio
     *
     * @param bucketName 桶名稱
     * @param fileName   文件名
     * @return Map<String, String>
     */
    @SneakyThrows
    public Map<String, String> getPresignedPostFormData(String bucketName, String fileName) {
        // 為存儲桶創(chuàng)建一個上傳策略,過期時間為7天
        PostPolicy policy = new PostPolicy(bucketName, ZonedDateTime.now().plusDays(1));
        // 設(shè)置一個參數(shù)key,值為上傳對象的名稱
        policy.addEqualsCondition("key", fileName);
        // 添加Content-Type,例如以"image/"開頭,表示只能上傳照片,這里吃吃所有
        policy.addStartsWithCondition("Content-Type", MediaType.ALL_VALUE);
        // 設(shè)置上傳文件的大小 64kiB to 10MiB.
        //policy.addContentLengthRangeCondition(64 * 1024, 10 * 1024 * 1024);
        return minioClient.getPresignedPostFormData(policy);
    }


    public String generateFileInMinioName(String originalFilename) {
        return "files" + StrUtil.SLASH + DateUtil.format(new Date(), "yyyy-MM-dd") + StrUtil.SLASH + UUID.randomUUID() + StrUtil.UNDERLINE + originalFilename;
    }

    /**
     * 初始化默認(rèn)存儲桶
     */
    @PostConstruct
    public void initDefaultBucket() {
        String defaultBucketName = minioConfig.getBucketName();
        if (bucketExists(defaultBucketName)) {
            log.info("默認(rèn)存儲桶:defaultBucketName已存在");
        } else {
            log.info("創(chuàng)建默認(rèn)存儲桶:defaultBucketName");
            makeBucket(minioConfig.getBucketName());
        }
    }

    /**
     * 文件合并,將分塊文件組成一個新的文件
     *
     * @param bucketName       合并文件生成文件所在的桶
     * @param objectName       原始文件名
     * @param sourceObjectList 分塊文件集合
     * @return OssFile
     */
    @SneakyThrows
    public OssFile composeObject(List<ComposeSource> sourceObjectList, String bucketName, String objectName) {
        minioClient.composeObject(ComposeObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .sources(sourceObjectList)
                .build());
        String presignedObjectUrl = getPresignedObjectUrl(bucketName, objectName);
        return new OssFile(presignedObjectUrl, objectName);
    }

    /**
     * 文件合并,將分塊文件組成一個新的文件
     *
     * @param originBucketName 分塊文件所在的桶
     * @param targetBucketName 合并文件生成文件所在的桶
     * @param objectName       存儲于桶中的對象名
     * @return OssFile
     */
    @SneakyThrows
    public OssFile composeObject(String originBucketName, String targetBucketName, String objectName) {
        Iterable<Result<Item>> results = listObjects(originBucketName, true);
        List<String> objectNameList = new ArrayList<>();
        for (Result<Item> result : results) {
            Item item = result.get();
            objectNameList.add(item.objectName());
        }
        if (ObjectUtils.isEmpty(objectNameList)) {
            throw new IllegalArgumentException(originBucketName + "桶中沒有文件,請檢查");
        }

        List<ComposeSource> composeSourceList = new ArrayList<>(objectNameList.size());
        // 對文件名集合進(jìn)行升序排序
        objectNameList.sort((o1, o2) -> Integer.parseInt(o2) > Integer.parseInt(o1) ? -1 : 1);
        for (String object : objectNameList) {
            composeSourceList.add(ComposeSource.builder()
                    .bucket(originBucketName)
                    .object(object)
                    .build());
        }

        return composeObject(composeSourceList, targetBucketName, objectName);
    }
}

 MinioConfig

import ai.gantong.common.constant.CommonConstant;
import ai.gantong.common.constant.SymbolConstant;
import ai.gantong.common.util.MinioUtil;
import io.minio.MinioClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Minio文件上傳配置文件
 *
 * @author xiaoyi
 */
@Slf4j
@Configuration
public class MinioConfig {
    @Value(value = "${minio.minioUrl}")
    private String minioUrl;
    @Value(value = "${minio.minioName}")
    private String minioName;
    @Value(value = "${minio.minioPass}")
    private String minioPass;
    @Value(value = "${minio.bucketName}")
    private String bucketName;

    public String getBucketName() {
        return bucketName;
    }

    @Bean
    public void initMinio() {
        MinioUtil.setMinioUrl(minioUrl);
        MinioUtil.setMinioName(minioName);
        MinioUtil.setMinioPass(minioPass);
        MinioUtil.setBucketName(bucketName);
    }

    //  將 MinIOClient 注入到 Spring 上下文中
    @Bean("minioClient")
    public MinioClient minioClient() {
        return MinioClient.builder().endpoint(minioUrl).credentials(minioName, minioPass).region(region).build();
    }

    //  初始化MinioTemplate,封裝了一些MinIOClient的基本操作
    @Bean(name = "minioTemplate")
    public MinioTemplate minioTemplate() {
        return new MinioTemplate(minioClient(), this);
    }

}

Controller

    /**
     * 根據(jù)文件大小和文件的md5校驗文件是否存在, 實現(xiàn)秒傳接口
     *
     * @param md5 文件的md5
     * @return 操作是否成功
     */
    @ApiOperation(value = "極速秒傳接口")
    @GetMapping(value = "/fastUpload")
    public Result<String> checkFileExists(@ApiParam(value = "文件的md5") String md5) {
        return fileService.checkFileExists(md5);
    }

    /**
     * 大文件分片上傳
     *
     * @param md5      文件的md5
     * @param file     文件
     * @param fileName 文件名
     * @param index    分片索引
     * @return 分片執(zhí)行結(jié)果
     */
    @ApiOperation(value = "上傳分片的接口")
    @PostMapping(value = "/upload")
    public Result<String> upload(@ApiParam(value = "文件的md5") String md5, @ApiParam(value = "文件") MultipartFile file,
                                 @ApiParam(value = "文件名") String fileName, @ApiParam(value = "分片索引") Integer index) {
        return fileService.upload(md5, file, fileName, index);
    }

    /**
     * 大文件合并
     *
     * @param mergeInfo 合并信息
     * @return 分片合并的狀態(tài)
     */
    @ApiOperation(value = "合并分片的接口")
    @PostMapping(value = "/merge")
    public Result<String> merge(@RequestBody MergeInfo mergeInfo) {
        return fileService.merge(mergeInfo);
    }

 ServiceImpl

@Slf4j
@Service
public class FileServiceImpl implements IFileService {

    private static final String MD5_KEY = "自定義前綴:minio:file:md5List";

    @Resource
    private MinioClient minioClient;
    @Resource
    private MinioConfig minioConfig;
    @Resource
    private MinioTemplate minioTemplate;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public Result<String> checkFileExists(String md5) {
        Result<String> result = new Result<>();
        // 先從Redis中查詢
        String url = (String) redisTemplate.boundHashOps(MD5_KEY).get(md5);
        // 文件不存在
        if (StrUtil.isEmpty(url)) {
            result.setSuccess(false);
            result.setMessage("資源不存在");
        } else {
            // 文件已經(jīng)存在了
            result.setSuccess(true);
            result.setResult(url);
            result.setMessage("極速秒傳成功");
        }
        return result;
    }

    @Override
    public Result<String> upload(String md5, MultipartFile file, String fileName, Integer index) {
        // 上傳過程中出現(xiàn)異常
        Assert.notNull(file, "文件上傳異常=>文件不能為空!");
        // 創(chuàng)建文件桶
        minioTemplate.makeBucket(md5);
        String objectName = String.valueOf(index);
        try {
            // 上傳文件
            minioTemplate.putChunkObject(file.getInputStream(), md5, objectName);
            // 設(shè)置上傳分片的狀態(tài)
            return Result.ok("文件上傳成功!");
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error("文件上傳失敗!");
        }
    }

    @Override
    public Result<String> merge(MergeInfo mergeInfo) {
        Assert.notNull(mergeInfo, "mergeInfo不能為空!");
        String md5 = mergeInfo.getMd5();
        String fileType = mergeInfo.getFileType();
        try {
            // 開始合并請求
            String targetBucketName = minioConfig.getBucketName();
            String fileNameWithoutExtension = UUID.randomUUID().toString();
            String objectName = fileNameWithoutExtension + "." + fileType;
            // 合并文件
            minioTemplate.composeObject(md5, targetBucketName, objectName);
            log.info("桶:{} 中的分片文件,已經(jīng)在桶:{},文件 {} 合并成功", md5, targetBucketName, objectName);

            // 合并成功之后刪除對應(yīng)的臨時桶
            minioTemplate.removeBucket(md5, true);
            log.info("刪除桶 {} 成功", md5);

            // 表示是同一個文件, 且文件后綴名沒有被修改過
            String url = minioTemplate.getPresignedObjectUrl(targetBucketName, objectName);

            // 存入redis中
            redisTemplate.boundHashOps(MD5_KEY).put(md5, url);

            return Result.ok("文件合并成功");// 成功
        } catch (Exception e) {
            log.error("文件合并執(zhí)行異常 => ", e);
            return Result.error("文件合并異常");// 失敗
        }
    }
}

MergeInfo

@Data
@ApiModel(description = "大文件合并信息")
public class MergeInfo implements Serializable {
    @ApiModelProperty(value = "文件的md5")
    public String md5;
    @ApiModelProperty(value = "文件名")
    public String fileName;
    @ApiModelProperty(value = "文件類型")
    public String fileType;
}

OssFile

@Data
@NoArgsConstructor
@AllArgsConstructor
public class OssFile {
    /**
     * OSS 存儲時文件路徑
     */
    private String ossFilePath;
    /**
     * 原始文件名
     */
    private String originalFileName;
}

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

相關(guān)文章

  • Java線程安全中的單例模式

    Java線程安全中的單例模式

    這篇文章主要介紹了Java線程安全中的單例模式,需要的朋友可以參考下
    2015-02-02
  • SpringBoot如何使用內(nèi)嵌Tomcat問題

    SpringBoot如何使用內(nèi)嵌Tomcat問題

    這篇文章主要介紹了SpringBoot如何使用內(nèi)嵌Tomcat問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • 基于Spring Boot應(yīng)用ApplicationEvent案例場景

    基于Spring Boot應(yīng)用ApplicationEvent案例場景

    這篇文章主要介紹了基于Spring Boot應(yīng)用ApplicationEvent,利用Spring的機(jī)制發(fā)布ApplicationEvent和監(jiān)聽ApplicationEvent,需要的朋友可以參考下
    2023-03-03
  • SpringBoot集成pf4j實現(xiàn)插件開發(fā)功能的代碼示例

    SpringBoot集成pf4j實現(xiàn)插件開發(fā)功能的代碼示例

    pf4j是一個插件框架,用于實現(xiàn)插件的動態(tài)加載,支持的插件格式(zip、jar),本文給大家介紹了SpringBoot集成pf4j實現(xiàn)插件開發(fā)功能的示例,文中通過代碼示例給大家講解的非常詳細(xì),需要的朋友可以參考下
    2024-07-07
  • JAVA 8 ''::'' 關(guān)鍵字詳解

    JAVA 8 ''::'' 關(guān)鍵字詳解

    這篇文章主要介紹了JAVA 8 '::' 關(guān)鍵字,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-09-09
  • SpringSecurity在分布式環(huán)境下的使用流程分析

    SpringSecurity在分布式環(huán)境下的使用流程分析

    文章介紹了Spring?Security在分布式環(huán)境下的使用,包括單點登錄(SSO)的概念、流程圖以及JWT(JSON?Web?Token)的生成和校驗,通過使用JWT和RSA非對稱加密,可以實現(xiàn)安全的分布式認(rèn)證,感興趣的朋友一起看看吧
    2025-02-02
  • 如何給yml配置文件的密碼加密(SpringBoot)

    如何給yml配置文件的密碼加密(SpringBoot)

    這篇文章主要介紹了如何給yml配置文件的密碼加密(SpringBoot),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • java 動態(tài)加載的實現(xiàn)代碼

    java 動態(tài)加載的實現(xiàn)代碼

    這篇文章主要介紹了java 動態(tài)加載的實現(xiàn)代碼的相關(guān)資料,Java動態(tài)加載類主要是為了不改變主程序代碼,通過修改配置文件就可以操作不同的對象執(zhí)行不同的功能,需要的朋友可以參考下
    2017-07-07
  • 淺談collection標(biāo)簽的oftype屬性能否為java.util.Map

    淺談collection標(biāo)簽的oftype屬性能否為java.util.Map

    這篇文章主要介紹了collection標(biāo)簽的oftype屬性能否為java.util.Map,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • springboot單獨(dú)使用feign簡化接口調(diào)用方式

    springboot單獨(dú)使用feign簡化接口調(diào)用方式

    這篇文章主要介紹了springboot單獨(dú)使用feign簡化接口調(diào)用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03

最新評論