栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JAVA+VUE3.0+MINIO 大文件上传(极速上传,分片上传)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JAVA+VUE3.0+MINIO 大文件上传(极速上传,分片上传)

文章目录
    • 定义分片大小
    • 急速上传
      • 文件MD5
    • 分片上传
      • 创建分片上传任务
        • 前端计算文件分片数量
        • 自定义MINIO CLIENT
        • 调用后台接口创建上传任务
        • 创建单文件上传任务
        • 创建分块上传任务
        • 校验桶是否存在(不存在则创建)
      • 分片文件合并
        • 获取指定 uploadId 下已上传的分块信息
        • MINIO 文件合并

记录一下自己在实现大文件上传时的简单思路和核心代码。

大体思路如下:
1、数据库中存放文件路径,所有文件保存在 MINIO 中,文件名即是文件的 MD5。
2、当用户上传文件时,首先判断该文件信息是否存在在数据库中,如果存在则直接显示上传成功(急速上传),若不存在则执行上传操作。
3、文件在真正上传之前先判断文件大小,太小的不需要创建分片上传任务,一次性上传即可。
4、后台调用 MINIO 的 API 创建分片上传任务(得到一个任务 ID ),并为该任务生成分片上传链接(上传地址列表)后返回给前端,前端将对应分片按照到对应的连接传递到 MINIO 中。
5、分片上传成功后更新进度信息。
6、所有分片上传结束后,调用 MINIO 的 API 将当前任务的分片全部合并形成整个文件。

定义分片大小
const chunkSize = 5 * 1024 * 1024; // 切片大小为5M
急速上传 文件MD5

前端使用 SparkMD5 获取文件的 MD5 信息,当该 MD5 信息已经存在在数据库中时,即上传完成(急速上传)
下面是获取文件 MD5 的方法

import SparkMD5 from 'spark-md5';
 
//获取文件的MD5信息 分片获取
const ReadFileMD5 = (param) => {
  return new Promise((resolve, reject) => {
    const file = param.file;
    const fileReader = new FileReader();
    const md5 = new SparkMD5();
    let index = 0;
    const loadFile = () => {
      const slice = file.slice(index, index + chunkSize);
      fileReader.readAsBinaryString(slice);
    };
    loadFile();
    fileReader.onload = (e) => {
      md5.appendBinary(e.target.result);
      if (index < file.size) {
        index += chunkSize;
        loadFile();
      } else {
        // md5.end() 就是文件md5码
        var md5Str = md5.end();
        return resolve(md5Str);
      }
    };
    fileReader.onerror = () => {
      reject('文件MD5获取失败');
    };
  });
};

当确认该文件的 MD5 在数据库中不存在时,开始触发我们的上传操作

分片上传 创建分片上传任务 前端计算文件分片数量
let chunks = Math.ceil(file.file.size / chunkSize);
自定义MINIO CLIENT

我们需要重新写一个 MINIO 客户端来实现我们的分片上传。

public class CustomMinioClient extends MinioClient {
 
    protected CustomMinioClient(MinioClient client) {
        super(client);
    }
 	//创建分块上传任务
    public String initMultiPartUpload(String bucket, String region, String object, Multimap headers, Multimap extraQueryParams) throws IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, ServerException, InternalException, XmlParserException, InvalidResponseException, ErrorResponseException {
        CreateMultipartUploadResponse response = this.createMultipartUpload(bucket, region, object, headers, extraQueryParams);
 
        return response.result().uploadId();
    }
    //合并指定上传任务的分块文件
    public ObjectWriteResponse mergeMultipartUpload(String bucketName, String region, String objectName, String uploadId, Part[] parts, Multimap extraHeaders, Multimap extraQueryParams) throws IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, ServerException, InternalException, XmlParserException, InvalidResponseException, ErrorResponseException {
 
        return this.completeMultipartUpload(bucketName, region, objectName, uploadId, parts, extraHeaders, extraQueryParams);
    }
 	//获取指定上传任务内的已上传的分块信息
    public ListPartsResponse listMultipart(String bucketName, String region, String objectName, Integer maxParts, Integer partNumberMarker, String uploadId, Multimap extraHeaders, Multimap extraQueryParams) throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, ServerException, XmlParserException, ErrorResponseException, InternalException, InvalidResponseException {
        return this.listParts(bucketName, region, objectName, maxParts, partNumberMarker, uploadId, extraHeaders, extraQueryParams);
    }
}
调用后台接口创建上传任务
...
if (partCount == 1) {
   //只有一个分片的情况下 直接返回上传地址
    String uploadObjectUrl = MinioUtils.getUploadObjectUrl(MinioUtils.FILE_WAREHOUSE,objectName);
    result.setUploadUrl(new ArrayList(){{add(uploadObjectUrl);}});
}else {
    Map initRsl = MinioUtils.initMultiPartUpload(MinioUtils.FILE_WAREHOUSE, objectName, partCount, contentType);
	result.setFinished(false);
    result.setUploadId(initRsl.get("uploadId").toString());
    result.setUploadUrl((List)initRsl.get("uploadUrls"));
}
...

其中比较重要的创建 MINIO 上传任务的方法如下

创建单文件上传任务
public static String getUploadObjectUrl(String bucketName, String objectName) {
    try {
    	//创建 MINIO 连接
        CustomMinioClient customMinioClient = new CustomMinioClient(MinioClient.builder()
                .endpoint(properties.getUrl())//MINIO 服务地址
                .credentials(properties.getAccessKey(), properties.getSecureKey())//用户名和密码
                .build());
        return customMinioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.PUT)//GET方式请求
                        .bucket(bucketName)//存储桶的名字
                        .object(objectName)//文件的名字
                        .expiry(1, TimeUnit.DAYS)//上传地址有效时长
                        .build()
        );
    } catch (Exception e) {
        return null;
    }
}
创建分块上传任务
public static Map initMultiPartUpload(String bucketName,String objectName, int partCount,String contentType) {
    Map result = new HashMap<>();
    try {
        //如果类型使用默认流会导致无法预览
        contentType = "application/octet-stream";

        HashMultimap headers = HashMultimap.create();
        headers.put("Content-Type", contentType);
        CustomMinioClient customMinioClient = new CustomMinioClient(MinioClient.builder()
                .endpoint(properties.getUrl())
                .credentials(properties.getAccessKey(), properties.getSecureKey())
                .build());
        checkBucket(customMinioClient,false,bucketName);
        //初始化分块上传任务
        String uploadId = customMinioClient.initMultiPartUpload(bucketName, null, objectName, headers, null);

        result.put("uploadId", uploadId);
        List partList = new ArrayList<>();

        Map reqParams = new HashMap<>();
        reqParams.put("uploadId", uploadId);
        for (int i = 1; i <= partCount; i++) {
            reqParams.put("partNumber", String.valueOf(i))
            //返回带签名URL
            String uploadUrl = customMinioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.PUT)//GET方式请求
	                        .bucket(bucketName)//存储桶的名字
	                        .object(objectName)//文件的名字
	                        .expiry(1, TimeUnit.DAYS)//上传地址有效时长
                            .extraQueryParams(reqParams)//指定任务ID和当前是第几个分块,生成上传链接
                            .build());
            partList.add(uploadUrl);
        }
        //返回任务ID 和 分块任务列表
        result.put("uploadUrls", partList);
    } catch (Exception e) {
        return null;
    }

    return result;
}
校验桶是否存在(不存在则创建)
private static void checkBucket(MinioClient minioClient ,boolean versioning, String bucket) throws Exception {

    boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
    if (!exists) {
        minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
        //设置Procy属性 默认所有文件都能读取 遵循 AmazonS3 规则
        String config = "{ " +
                "    "Id": "Policy1", " +
                "    "Version": "2012-10-17", " +
                "    "Statement": [ " +
                "        { " +
                "            "Sid": "Statement1", " +
                "            "Effect": "Allow", " +
                "            "Action": [ " +
                "                "s3:ListBucket", " +
                "                "s3:GetObject" " +
                "            ], " +
                "            "Resource": [ " +
                "                "arn:aws:s3:::"+bucket+"", " +
                "                "arn:aws:s3:::"+bucket+"
        customMinioClient = new CustomMinioClient(MinioClient.builder()
                .endpoint(properties.getUrl())
                .credentials(properties.getAccessKey(), properties.getSecureKey())
                .build());
        ListPartsResponse partResult = customMinioClient.listMultipart(bucketName, null, objectName, 1024, 0, uploadId, null, null);

        for (Part part : partResult.result().partList()) {
            parts.add(part.etag());
        }
        //合并分片
    } catch (Exception e) {
        //
        log.error("查询任务分片错误");
    }

    return parts;
}
MINIO 文件合并

public static boolean mergeMultipartUpload(String bucketName, String objectName, String uploadId) {
   try {
       Part[] parts = new Part[1000];
       
       CustomMinioClient customMinioClient = new CustomMinioClient(MinioClient.builder()
               .endpoint(properties.getUrl())
               .credentials(properties.getAccessKey(), properties.getSecureKey())
               .build());
       ListPartsResponse partResult = customMinioClient.listMultipart(bucketName, null, objectName, 1000, 0, uploadId, null, null);
       int partNumber = 1;
       for (Part part : partResult.result().partList()) {
           parts[partNumber - 1] = new Part(partNumber, part.etag());
           partNumber++;
       }
       //合并分片
       customMinioClient.mergeMultipartUpload(bucketName, null, objectName, uploadId, parts, null, null);
   } catch (Exception e) {
       return false;
   }
   return true;
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/996550.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号