文章目录
- 前言
- 一、minio 为例
- 二、涉及所有代码
- 总结
前言
目前文件存储一般采用obs存储,也就是对象存储
比较流行的有: minio 阿里云 华为云 阿里云 腾讯云 七牛云 百度云 ,对于贫穷的我来说,当然选择免费开源的minio了,但是他们有一个统一的标准也就是S3协议,相当于jdbc标准协议一样,你只要熟悉了S3协议,那么几乎所有的对象存储几乎就是一致了.
这里讨论分片上传
- 创建一个分片上传的任务,会返回一个唯一任务id,这个任务id很重要
- 根据任务id,和要分片的数量,获取上传分片的地址,每个分片对应一个上传地址
- 根据返回的上传地址,分别上传各自的分片文件流
- 全部上传后,调用文件合并,将之前上传的多个分片合并成一个最终文件
这也就是为何分片上传会快的原因,因为它是同时上传,每个上传地址上传文件的一部分,所以非常快~
一、minio 为例
-
我们需要安装好minio
-
引入S3协议的依赖
com.amazonaws aws-java-sdk-s3 1.12.263 -
配置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…,如有侵权,请联系删除。