S3协议分片上传(minio)

262 阅读3分钟

文章目录

  • 前言
  • 一、minio 为例
  • 二、涉及所有代码
  • 总结

前言

目前文件存储一般采用obs存储,也就是对象存储

比较流行的有: minio 阿里云 华为云 阿里云 腾讯云 七牛云 百度云 ,对于贫穷的我来说,当然选择免费开源的minio了,但是他们有一个统一的标准也就是S3协议,相当于jdbc标准协议一样,你只要熟悉了S3协议,那么几乎所有的对象存储几乎就是一致了.

这里讨论分片上传

  • 创建一个分片上传的任务,会返回一个唯一任务id,这个任务id很重要
  • 根据任务id,和要分片的数量,获取上传分片的地址,每个分片对应一个上传地址
  • 根据返回的上传地址,分别上传各自的分片文件流
  • 全部上传后,调用文件合并,将之前上传的多个分片合并成一个最终文件

这也就是为何分片上传会快的原因,因为它是同时上传,每个上传地址上传文件的一部分,所以非常快~


一、minio 为例

  1. 我们需要安装好minio

  2. 引入S3协议的依赖

    com.amazonaws aws-java-sdk-s3 1.12.263
  3. 配置S3协议client

// 读取配置
@Data
@Component
@ConfigurationProperties(prefix = "minio")
public class MinioProperties {

    @Value("endpoint")
    private String endpoint;

    @Value("access-key")
    private String accessKey;

    @Value("access-secret")
    private String accessSecret;

    @Value("bucket")
    private String bucket;
}
// 配置
@Configuration
public class AmazonS3Config {

    @Resource
    private MinioProperties minioProperties;

    @Bean(name = "amazonS3Client")
    public AmazonS3 amazonS3Client () {
        //设置连接时的参数
        ClientConfiguration config = new ClientConfiguration();
        //设置连接方式为HTTP,可选参数为HTTP和HTTPS
        config.setProtocol(Protocol.HTTP);
        //设置网络访问超时时间
        config.setConnectionTimeout(5000);
        config.setUseExpectContinue(true);
        AWSCredentials credentials = new BasicAWSCredentials(minioProperties.getAccessKey(), minioProperties.getAccessSecret());
        //设置Endpoint
        AwsClientBuilder.EndpointConfiguration end_point = new AwsClientBuilder.EndpointConfiguration(minioProperties.getEndpoint(), Regions.US_EAST_1.name());
        AmazonS3 amazonS3 = AmazonS3ClientBuilder.standard()
                .withClientConfiguration(config)
                .withCredentials(new AWSStaticCredentialsProvider(credentials))
                .withEndpointConfiguration(end_point)
                .withPathStyleAccessEnabled(true).build();
        return amazonS3;
    }
}

4. 通过S3协议实现文件分片上传

/**
 * 分片上传
 * <p>
 * 1. 创建一个上传任务: uploadId, 上传的每个分片对应的预上传url
 * 2. 根据预上传url,前端自己调用上传,此时与后端无关
 * 3. 合并分片,根据uploadId,合并分片
 * </p>
 *
 * @since 2022-08-22 17:47:31
 */
@RestController
@RequestMapping("/minio")
@Slf4j
public class MinioController {

    @Autowired
    private AmazonS3 amazonS3;

    @Autowired
    private MinioProperties minioProperties;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 创建一个上传任务
     *
     * @return
     */
    @PostMapping
    public Result<InitTaskVO> initTask(@RequestBody InitTask initTask) {
        // 构建上传任务参数
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentType(FileUtil.extName(initTask.getFileName()));
        String finalFileName = IdUtil.fastSimpleUUID() + StrUtil.DOT + objectMetadata.getContentType();
        InitiateMultipartUploadResult initiateMultipartUploadResult = amazonS3
                .initiateMultipartUpload(new InitiateMultipartUploadRequest(minioProperties.getBucket(), finalFileName)
                        .withObjectMetadata(objectMetadata));
        // 生成上传任务id
        String uploadId = initiateMultipartUploadResult.getUploadId();
        // 缓存文件信息
        FileCacheIno fileCacheIno = new FileCacheIno();
        fileCacheIno.setUploadId(uploadId);
        fileCacheIno.setPartSize(initTask.getPartSize());
        redisTemplate.opsForValue().setIfAbsent(initTask.getMd5Str(), fileCacheIno, 10, TimeUnit.MINUTES);
        Date currentDate = new Date();
        Date expireDate = DateUtil.offsetMillisecond(currentDate, 60 * 10 * 1000);
        GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(minioProperties.getBucket(), finalFileName)
                .withExpiration(expireDate).withMethod(HttpMethod.PUT);
        List<String> putUploadUrls = CollUtil.newArrayList();

        // 必须从1 开始
        for (Integer i = 1; i <= initTask.getPartSize(); i++) {
            request.addRequestParameter("partNumber", i.toString());
            request.addRequestParameter("uploadId", uploadId);
            // 获取上传分片的地址
            URL preSignedUrl = amazonS3.generatePresignedUrl(request);
            putUploadUrls.add(preSignedUrl.toString());
            log.info("preSignedUrl-{}:{}", i, preSignedUrl);
        }
        return Result.ok(InitTaskVO.builder()
                .finalFileName(finalFileName).fileName(initTask.getFileName())
                .putUploadUrls(putUploadUrls).build());
    }

    /**
     * 合并分片
     *
     * @param md5Str        文件md5
     * @param finalFileName 最终文件名称
     * @return 文件全路径
     */
    @GetMapping("/merge")
    public Result merge(@RequestParam("md5Str") String md5Str, @RequestParam("finalFileName") String finalFileName) {
        if (Boolean.FALSE.equals(redisTemplate.hasKey(md5Str))) {
            return Result.error("任务不存在");
        }
        FileCacheIno fileCacheIno = (FileCacheIno) redisTemplate.opsForValue().get(md5Str);
        Assert.isTrue(ObjectUtil.isNotNull(fileCacheIno), "上传任务不存在");
        String uploadId = fileCacheIno.getUploadId();
        int partSize = fileCacheIno.getPartSize();
        ListPartsRequest listPartsRequest = new ListPartsRequest(minioProperties.getBucket(), finalFileName, uploadId);
        PartListing partListing = amazonS3.listParts(listPartsRequest);
        List<PartSummary> parts = partListing.getParts();
        Assert.isTrue(parts.size() == partSize, "分片数量不匹配");
        List<PartETag> partETags = parts.stream()
                .map(partSummary -> new PartETag(partSummary.getPartNumber(), partSummary.getETag()))
                .collect(Collectors.toList());
        CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest()
                .withUploadId(uploadId)
                .withKey(finalFileName)
                .withBucketName(minioProperties.getBucket())
                .withPartETags(partETags);
        CompleteMultipartUploadResult result = amazonS3.completeMultipartUpload(completeMultipartUploadRequest);
        return Result.ok(result.getLocation());
    }

}

二、涉及所有代码

都放在了gitee上, minio-分片demo


总结

之前总是听说分片上传,但 纸上得来终觉浅,绝知此事要躬行 ,只有亲自做过,才真正理解分片上传的快速的原因是由于,将大文件切分成了多个部分,然后每个部分可以同时执行上传,这也变相相当于变成了多个线程同时处理一个文件的上传了~

本文转自 jimolvxing.blog.csdn.net/article/det…,如有侵权,请联系删除。