美文网首页
springboot项目minio分片上传

springboot项目minio分片上传

作者: 为泄废举义添砖加瓦开花攻城撕 | 来源:发表于2024-07-16 10:32 被阅读0次

minio依赖

<!-- Minio -->

<dependency>

    <groupId>io.minio</groupId>

    <artifactId>minio</artifactId>

    <version>8.2.2</version>

</dependency>

前端代码demo:

<!DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8">

  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>上传文件</title>

</head>

<body>

  <div>

    <input type="file" multiple onchange="handleFileChange(event)" />

    <button onclick="uploadFiles()">Upload</button>

  </div>

  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

  <script>

    // 定义全局变量和函数

    const files = [];

    const chunkSize = 5 * 1024 * 1024; // 5MB

    const uploadIdMap = {};

    function handleFileChange(event) {

      files.length = 0; // 清空数组

      Array.from(event.target.files).forEach(file => files.push(file));

    }

    async function uploadFiles() {

      if (files.length === 0) {

        alert('Please select files to upload.');

        return;

      }

      const promises = files.map(file => uploadFile(file));

      try {

        await Promise.all(promises);

        alert('All files uploaded successfully.');

      } catch (error) {

        console.error('Error uploading files:', JSON.stringify(error, null, 2));

        alert('Failed to upload files.');

      }

    }

    async function uploadFile(file) {

      const totalChunks = Math.ceil(file.size / chunkSize);

      const fileName = file.name;

      let uploadId = null;

      try {

        for (let i = 0; i < totalChunks; i++) {

          const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);

          const formData = new FormData();

          formData.append('file', chunk);

          formData.append('shardCount', totalChunks);

          formData.append('shardIndex', i + 1);

          formData.append('fileName', fileName);

          if (!uploadId) {

            formData.append('initialize', true);

          } else {

            formData.append('uploadId', uploadId);

          }

          const response = await axios.post('http://127.0.0.1:9204/shardingUpload', formData, {

            headers: {

              'Content-Type': 'multipart/form-data',

            },

          });

          if (!uploadId) {

            uploadId = response.data.uploadId;

            uploadIdMap[fileName] = uploadId;

          }

        }

        await axios.post('http://127.0.0.1:9204/shardingComplete', {

          uploadId,

          fileName,

        });

      } catch (error) {

        console.error(`Error uploading file ${fileName}:`, error);

        throw error;

      }

    }

  </script>

</body>

</html>



后端代码:

后端controller

package com.xxxx.file.controller;

import com.construction.common.core.domain.R;

import com.construction.file.entity.ShardingCompleteRequest;

import com.construction.file.entity.ShardingUploadResponse;

import com.construction.file.service.ISysFileService;

import com.construction.file.utils.FileUploadUtils;

import com.construction.system.api.domain.SysFile;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.web.bind.annotation.PostMapping;

import org.springframework.web.bind.annotation.RequestBody;

import org.springframework.web.bind.annotation.RequestParam;

import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.multipart.MultipartFile;

import java.text.DecimalFormat;

/**

* 文件请求处理

*

* @author construction

*/

@RestController

public class SysFileController {

private static final Logger log =LoggerFactory.getLogger(SysFileController.class);

@Autowired

    @Qualifier(value ="minioService")

private ISysFileService sysFileService;

/**

    * 分片上传

    *

    * @param file      文件

    * @param fileName  文件名称

    * @param shardIndex 当前切片序号,从1开始

    * @param shardCount 切片总数量

    */

    @PostMapping("shardingUpload")

public RshardingUpload(@RequestParam("file")MultipartFile file,

@RequestParam("shardIndex")int shardIndex,

@RequestParam("shardCount")int shardCount,

@RequestParam("fileName")String fileName) {

try {

//重命名文件

            fileName =FileUploadUtils.extractFilenameForShardingUpload(fileName);

// 上传并返回访问地址

            ShardingUploadResponse result =sysFileService.shardingUpload(file,shardIndex,shardCount,fileName);

SysFile sysFile =new SysFile();

sysFile.setFileSize(this.getSize(file.getSize()));

sysFile.setSize(file.getSize());

//            sysFile.setName(FileUtils.getName(url));

            //返回原始文件名

            sysFile.setName(file.getOriginalFilename());

//            sysFile.setUrl(url);

            return R.ok(sysFile);

}catch (Exception e) {

log.error("上传文件失败",e);

return R.fail(e.getMessage());

}

}

/**

    * 完成分片上传

    *

    * @param shardingCompleteRequest

    */

    @PostMapping("shardingComplete")

public RshardingComplete(@RequestBody ShardingCompleteRequest shardingCompleteRequest) {

try {

//重命名文件

            String fileName =FileUploadUtils.extractFilenameForShardingUpload(shardingCompleteRequest.getFileName());

SysFile sysFile =sysFileService.shardingComplete(fileName,shardingCompleteRequest.getUploadId());

sysFile.setFileSize(this.getSize(sysFile.getSize()));

sysFile.setName(fileName);

return R.ok(sysFile);

}catch (Exception e) {

log.error("上传文件失败",e);

return R.fail(e.getMessage());

}

}

public String getSize(long size) {

//获取到的size为:1705230

        int GB =1024 *1024 *1024;//定义GB的计算常量

        int MB =1024 *1024;//定义MB的计算常量

        int KB =1024;//定义KB的计算常量

        DecimalFormat df =new DecimalFormat("0.00");//格式化小数

        String resultSize ="";

if (size /GB >=1) {

//如果当前Byte的值大于等于1GB

            resultSize =df.format(size / (float)GB) +"GB";

}else if (size /MB >=1) {

//如果当前Byte的值大于等于1MB

            resultSize =df.format(size / (float)MB) +"MB";

}else if (size /KB >=1) {

//如果当前Byte的值大于等于1KB

            resultSize =df.format(size / (float)KB) +"KB";

}else {

resultSize =size +"B";

}

return resultSize;

}

}


后端代码:minioconfig

package com.construction.file.config;

import com.construction.file.client.CustomMinioClient;

import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import io.minio.MinioClient;

/**

* Minio 配置信息

*

* @author construction

*/

@Configuration

@ConfigurationProperties(prefix ="minio")

public class MinioConfig

{

/**

    * 内部调用服务地址

    */

    private String url;

/**

    * 对外开放服务地址

    */

    private String httpsUrl;

/**

    * 用户名

    */

    private String accessKey;

/**

    * 密码

    */

    private String secretKey;

/**

    * 存储桶名称

    */

    private String bucketName;

public String getUrl()

{

return url;

}

public void setUrl(String url)

{

this.url =url;

}

public String getAccessKey()

{

return accessKey;

}

public void setAccessKey(String accessKey)

{

this.accessKey =accessKey;

}

public String getSecretKey()

{

return secretKey;

}

public void setSecretKey(String secretKey)

{

this.secretKey =secretKey;

}

public String getBucketName()

{

return bucketName;

}

public void setBucketName(String bucketName)

{

this.bucketName =bucketName;

}

public String getHttpsUrl() {

return httpsUrl;

}

public void setHttpsUrl(String httpsUrl) {

this.httpsUrl =httpsUrl;

}

@Bean(name ="minioClient")

public MinioClient getMinioClient()

{

return MinioClient.builder().endpoint(url).credentials(accessKey,secretKey).build();

}

/**

    * 自定义分片上传的minio客户端

    *

* @return

*/

    @Bean(name ="customMinioClient")

public CustomMinioClient getCustomMinioClient() {

MinioClient build =MinioClient.builder().endpoint(url).credentials(accessKey,secretKey).build();

return new CustomMinioClient(build);

}

}


后端代码:自定义minioclient

package com.construction.file.client;

import com.google.common.collect.Multimap;

import io.minio.*;

import io.minio.errors.*;

import io.minio.messages.Part;

import java.io.BufferedInputStream;

import java.io.IOException;

import java.io.InputStream;

import java.security.InvalidKeyException;

import java.security.NoSuchAlgorithmException;

/**

* @ProjectName: construction

* @Package: com.construction.file.client

* @ClassName: CustomMinioClient

* @Author: luanzhiwei

* @Description: 自定义minioclient

* @Date: 2024/7/16 10:24

* @Version: 1.0

*/

public class CustomMinioClient extends MinioClient {

public CustomMinioClient(MinioClient client) {

super(client);

}

/**

    * 初始化分片上传

    *

    * @param bucket

    * @param region

    * @param object

    * @param headers

    * @param extraQueryParams

    * @return

    * @throws IOException

    * @throws InvalidKeyException

    * @throws NoSuchAlgorithmException

    * @throws InsufficientDataException

    * @throws ServerException

    * @throws InternalException

    * @throws XmlParserException

    * @throws InvalidResponseException

    * @throws ErrorResponseException

    */

    public String initMultiPartUpload(String bucket,String region,String object,Multimapheaders,MultimapextraQueryParams)throws IOException,InvalidKeyException,NoSuchAlgorithmException,InsufficientDataException,ServerException,InternalException,XmlParserException,InvalidResponseException,ErrorResponseException {

CreateMultipartUploadResponse response =this.createMultipartUpload(bucket,region,object,headers,extraQueryParams);

return response.result().uploadId();

}

/**

    * 合并分片文件

    *

    * @param bucketName

    * @param region

    * @param objectName

    * @param uploadId

    * @param parts

    * @param extraHeaders

    * @param extraQueryParams

    * @return

    * @throws IOException

    * @throws InvalidKeyException

    * @throws NoSuchAlgorithmException

    * @throws InsufficientDataException

    * @throws ServerException

    * @throws InternalException

    * @throws XmlParserException

    * @throws InvalidResponseException

    * @throws ErrorResponseException

    */

    public ObjectWriteResponse mergeMultipartUpload(String bucketName,String region,String objectName,String uploadId,Part[]parts,MultimapextraHeaders,MultimapextraQueryParams)throws IOException,InvalidKeyException,NoSuchAlgorithmException,InsufficientDataException,ServerException,InternalException,XmlParserException,InvalidResponseException,ErrorResponseException {

return this.completeMultipartUpload(bucketName,region,objectName,uploadId,parts,extraHeaders,extraQueryParams);

}

/**

    * 列出详情

    *

    * @param bucketName

    * @param region

    * @param objectName

    * @param maxParts

    * @param partNumberMarker

    * @param uploadId

    * @param extraHeaders

    * @param extraQueryParams

    * @return

    * @throws NoSuchAlgorithmException

    * @throws InsufficientDataException

    * @throws IOException

    * @throws InvalidKeyException

    * @throws ServerException

    * @throws XmlParserException

    * @throws ErrorResponseException

    * @throws InternalException

    * @throws InvalidResponseException

    */

    public ListPartsResponse listMultipart(String bucketName,String region,String objectName,Integer maxParts,Integer partNumberMarker,String uploadId,MultimapextraHeaders,MultimapextraQueryParams)throws NoSuchAlgorithmException,InsufficientDataException,IOException,InvalidKeyException,ServerException,XmlParserException,ErrorResponseException,InternalException,InvalidResponseException {

return this.listParts(bucketName,region,objectName,maxParts,partNumberMarker,uploadId,extraHeaders,extraQueryParams);

}

/**

    * 切片上传

    *

    * @param bucketName

    * @param region

    * @param objectName

    * @param data

    * @param length

    * @param uploadId

    * @param partNumber

    * @param extraHeaders

    * @param extraQueryParams

    * @return

    * @throws NoSuchAlgorithmException

    * @throws InsufficientDataException

    * @throws IOException

    * @throws InvalidKeyException

    * @throws ServerException

    * @throws XmlParserException

    * @throws ErrorResponseException

    * @throws InternalException

    * @throws InvalidResponseException

    */

    public UploadPartResponse uploadParts(String bucketName,String region,String objectName,Object data,int length,String uploadId,int partNumber,MultimapextraHeaders,MultimapextraQueryParams)throws NoSuchAlgorithmException,InsufficientDataException,IOException,InvalidKeyException,ServerException,XmlParserException,ErrorResponseException,InternalException,InvalidResponseException {

if (data instanceof InputStream) {

InputStream inputStream = (InputStream)data;

if (!(inputStream instanceof BufferedInputStream)) {

inputStream =new BufferedInputStream(inputStream);

}

data =inputStream;

}

return this.uploadPart(bucketName,region,objectName,data,length,uploadId,partNumber,extraHeaders,extraQueryParams);

}

}


后端代码:service

package com.construction.file.service;

import com.construction.file.entity.ShardingUploadResponse;

import com.construction.system.api.domain.SysFile;

import org.springframework.web.multipart.MultipartFile;

/**

* 文件上传接口

*

* @author construction

*/

public interface ISysFileService {

/**

    * 文件上传接口

    *

    * @param file 上传的文件

    * @return 访问地址

    * @throws Exception

    */

    public String uploadFile(MultipartFile file)throws Exception;

/**

    * 分片文件上传接口

    *

    * @param file      上传的文件

    * @param shardIndex

    * @param shardCount

    * @param fileName

    * @return 访问地址

    * @throws Exception

    */

    public ShardingUploadResponse shardingUpload(MultipartFile file,int shardIndex,int shardCount,String fileName)throws Exception;

/**

    * 完成分片上传

    *

    * @param fileName

    * @param uploadId

    * @return

*/

    SysFile shardingComplete(String fileName,String uploadId);

}


后端代码:serviceImpl

package com.construction.file.service;

import com.alibaba.nacos.common.utils.IoUtils;

import com.construction.common.core.exception.base.BaseException;

import com.construction.file.client.CustomMinioClient;

import com.construction.file.config.MinioConfig;

import com.construction.file.entity.ShardingUploadResponse;

import com.construction.file.utils.FileUploadUtils;

import com.construction.system.api.domain.SysFile;

import io.minio.*;

import io.minio.messages.Part;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.stereotype.Service;

import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;

import java.io.InputStream;

import java.util.ArrayList;

import java.util.Arrays;

import java.util.Comparator;

import java.util.List;

import java.util.concurrent.ConcurrentHashMap;

import java.util.concurrent.locks.ReentrantLock;

/**

* Minio 文件存储

*

* @author construction

*/

@Service(value ="minioService")

@Slf4j

public class MinioSysFileServiceImpl implements ISysFileService {

@Autowired

    private MinioConfig minioConfig;

@Autowired

    @Qualifier("minioClient")

private MinioClient client;

@Resource

    @Qualifier("customMinioClient")

private CustomMinioClient customMinioClient;

/**

    * 分片上传的part

*/

    private ConcurrentHashMap>uploadPartsMap =new ConcurrentHashMap<>();

/**

    * 文件上传保存的文件名和uploadId

*/

    private ConcurrentHashMapuploadIds =new ConcurrentHashMap<>();

private final ReentrantLock lock =new ReentrantLock();

/**

    * Minio文件上传接口

    *

    * @param file 上传的文件

    * @return 访问地址

    * @throws Exception

    */

    @Override

    public String uploadFile(MultipartFile file)throws Exception {

String fileName =FileUploadUtils.extractFilename(file);

InputStream inputStream =file.getInputStream();

PutObjectArgs args =PutObjectArgs.builder()

.bucket(minioConfig.getBucketName())

.object(fileName)

.stream(inputStream,file.getSize(), -1)

.contentType(file.getContentType())

.build();

client.putObject(args);

IoUtils.closeQuietly(inputStream);

return minioConfig.getHttpsUrl() +"/" +minioConfig.getBucketName() +"/" +fileName;

}

/**

    * 分片文件上传接口

    *

    * @param file      上传的文件

    * @param shardIndex 切片序号

    * @param shardCount 切片总数

    * @param fileName

    * @return 访问地址

    * @throws Exception

    */

    @Override

    public ShardingUploadResponse shardingUpload(MultipartFile file,int shardIndex,int shardCount,String fileName)throws Exception {

log.info("分片上传,文件名称:{},总片数 {} ,当前片数:{}",fileName,shardCount,shardIndex);

ShardingUploadResponse uploadResponse =new ShardingUploadResponse();

String uploadId;

//加锁

        this.lock.lock();

try {

if (!uploadIds.containsKey(fileName)) {

uploadId =customMinioClient.initMultiPartUpload(minioConfig.getBucketName(),null,fileName,null,null);

uploadIds.put(fileName,uploadId);

uploadPartsMap.put(fileName,new ArrayList<>());

}else {

uploadId =uploadIds.get(fileName);

}

}finally {

this.lock.unlock();

}

InputStream inputStream =file.getInputStream();

UploadPartResponse response =customMinioClient.uploadParts(minioConfig.getBucketName(),null,fileName,inputStream,inputStream.available(),uploadId,shardIndex,null,null);

uploadPartsMap.get(fileName).add(new Part(shardIndex,response.etag()));

inputStream.close();

uploadResponse.setUploadId(uploadId);

log.info("uploadId: {}, 分片上传结束,文件名称:{},总片数 {} ,当前片数:{}",uploadId,fileName,shardCount,shardIndex);

return uploadResponse;

}

/**

    * 分片上传完后合并

    *

    * @param fileName 文件全路径名称

    * @param uploadId

    * @return /

*/

    public SysFile shardingComplete(String fileName,String uploadId) {

log.info("分片上传完成,文件名称:{},uploadId {}  ",fileName,uploadId);

SysFile sysFile =new SysFile();

try {

uploadId =uploadIds.get(fileName);

Listparts =uploadPartsMap.get(fileName);

// 假设 parts 是你已经获得的 Part 数组

            Part[]partsArray =parts.toArray(new Part[0]);

Arrays.sort(partsArray,Comparator.comparingInt(Part::partNumber));

customMinioClient.mergeMultipartUpload(minioConfig.getBucketName(),null,fileName,uploadId,partsArray,null,null);

// 清理上传状态

            uploadIds.remove(fileName);

uploadPartsMap.remove(fileName);

}catch (Exception e) {

log.error("minio分片上传合并失败",e);

throw new BaseException("合并分片失败!");

}

try {

StatObjectResponse statted =customMinioClient.statObject(

StatObjectArgs.builder()

.bucket(minioConfig.getBucketName())

.object(fileName)

.build()

);

sysFile.setSize(statted.size());

}catch (Exception e) {

throw new RuntimeException(e);

}

sysFile.setName(fileName);

String url =minioConfig.getHttpsUrl() +"/" +minioConfig.getBucketName() +"/" +fileName;

sysFile.setUrl(url);

log.info("分片上传完成,文件名称:{},uploadId {} ,访问地址 {}  ",fileName,uploadId,url);

return sysFile;

}

}

相关文章

网友评论

      本文标题:springboot项目minio分片上传

      本文链接:https://www.haomeiwen.com/subject/kfrkhjtx.html