1.了解分片合并
分片上传就是为了解决大文件上传的问题。就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(我们称之为Part)来进行分别上传。
分片合并就是上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件。
2.分析我们的流程
1)谁来分片 调用方来分片
客户端负责分片 不需要我们关心
2)请求我们的服务端,服务端接收到分片后,暂存到本地目录下
服务端缓存分片文件 已完成
3)调用方判断所有分片是否全部上传成功,基于每个分片的请求的返回值是否成功,调用方自行判断
客户端自行判断分片上传状态 不需要我们关心
4)调用方判断所有分片都上传成功后,向服务端发送合并的请求 服务端提供合并接口 未完成
5)服务端执行合并逻辑,最终基于策略将文件上传到阿里云或本地 服务端基于策略执行上传逻辑 未完成
3.那么我们在服务端应该要有一个分片合并的接口
谁来触发,客户端来触发我们的合并接口
接口分析:
基于resful风格分析接口 POST请求 定义接口 /chunk/merge
传入参数:
客户端调用merge接口的时候,客户端基于自己的逻辑,判断所有的分片是否都已经上传成功,调用merge接口的时候
客户端已经确定所有的分片都已经上传成功了才会调用merge接口
那么现在要把对应文件的分片进行合并,服务端要去D:\chunks\2021\11\唯一名称\目录下找到所有的分片
那么客户端应该在调用merge的时候,将文件的唯一名称name 分片的总数chunks 原始文件的相关信息 告知我们的服务端
我们基于这一点来设计我们的分片接口参数
name 唯一文件名
submittedFileName 完整文件的原始文件名称
md5完整文件的md5值
chunks 一共有多少分片
ext 文件的后缀
folderId 父文件夹ID
size 原始文件大小
contentType 文件的类型
返回参数 与简单上传一样
成功 code:0 msg:ok data:完整的文件信息
失败 code:-1 msg:分片合并失败 data:false
接口逻辑
首先我们来整理思路:
我们的合并操作应该怎么做?
基于阿里云来说:
参考阿里云的官网
https://help.aliyun.com/document_detail/84786.htm?spm=a2c4g.11186623.0.0.3eb436dfdoOncX#concept-84786-zh
4.demo实例
public class Ali {
public static void chunkUpload1() throws Exception{
String endpoint = "ali的存储位置比如(http://oss-cn-beijing.aliyuncs.com)";
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
String accessKeyId = "自己阿里云的ak";
String accessKeySecret = "自己sk";
String bucketName = "file-demo-wgl";
// 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
// String objectName = "chunks/《Effective Java中文版 第2版》.(Joshua Bloch).[PDF]&ckook.pdf";
String objectName = "chunks/领导力.pptx";
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 创建InitiateMultipartUploadRequest对象。
InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, objectName);
// 如果需要在初始化分片时设置文件存储类型,请参考以下示例代码。
// ObjectMetadata metadata = new ObjectMetadata();
// metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
// request.setObjectMetadata(metadata);
// 初始化分片。
InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
// 返回uploadId,它是分片上传事件的唯一标识。您可以根据该uploadId发起相关的操作,例如取消分片上传、查询分片上传等。
String uploadId = upresult.getUploadId();
// partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
List<PartETag> partETags = new ArrayList<PartETag>();
// 每个分片的大小,用于计算文件有多少个分片。单位为字节。
final long partSize = 1 * 1024 * 1024L; //1 MB。
// 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
final File sampleFile = new File("填写本地文件的完整路径");
long fileLength = sampleFile.length();
int partCount = (int) (fileLength / partSize);
if (fileLength % partSize != 0) {
partCount++;
}
// 遍历分片上传。
for (int i = 0; i < partCount; i++) {
long startPos = i * partSize;
long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
InputStream instream = new FileInputStream(sampleFile);
// 跳过已经上传的分片。
instream.skip(startPos);
UploadPartRequest uploadPartRequest = new UploadPartRequest();
uploadPartRequest.setBucketName(bucketName);
uploadPartRequest.setKey(objectName);
uploadPartRequest.setUploadId(uploadId);
uploadPartRequest.setInputStream(instream);
// 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。
uploadPartRequest.setPartSize(curPartSize);
// 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,OSS将返回InvalidArgument错误码。
uploadPartRequest.setPartNumber( i + 1);
// 每个分片不需要按顺序上传,甚至可以在不同客户端上传,OSS会按照分片号排序组成完整的文件。
UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
// 每次上传分片之后,OSS的返回结果包含PartETag。PartETag将被保存在partETags中。
partETags.add(uploadPartResult.getPartETag());
}
// 创建CompleteMultipartUploadRequest对象。
// 在执行完成分片上传操作时,需要提供所有有效的partETags。OSS收到提交的partETags后,会逐一验证每个分片的有效性。当所有的数据分片验证通过后,OSS将把这些分片组合成一个完整的文件。
CompleteMultipartUploadRequest completeMultipartUploadRequest =
new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);
// 如果需要在完成文件上传的同时设置文件访问权限,请参考以下示例代码。
// completeMultipartUploadRequest.setObjectACL(CannedAccessControlList.PublicRead);
// 完成上传。
CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);
System.out.println(completeMultipartUploadResult.getETag());
// 关闭OSSClient。
ossClient.shutdown();
}
public static void chunkUpload2() throws Exception{
String endpoint = "http://oss-cn-beijing.aliyuncs.com";
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
String accessKeyId = "ak";
String accessKeySecret = "sk";
String bucketName = "file-demo-wgl";
// 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
// 《Effective Java中文版 第2版》.(Joshua Bloch).[PDF]&ckook.pdf
String objectName = "bucketName 2021/11/name/《Effective Java中文版 第2版》.(Joshua Bloch).[PDF]&ckook.pdf";
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 创建InitiateMultipartUploadRequest对象。
InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, objectName);
// 如果需要在初始化分片时设置文件存储类型,请参考以下示例代码。
// ObjectMetadata metadata = new ObjectMetadata();
// metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
// request.setObjectMetadata(metadata);
// 初始化分片。
InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
// 返回uploadId,它是分片上传事件的唯一标识。您可以根据该uploadId发起相关的操作,例如取消分片上传、查询分片上传等。
String uploadId = upresult.getUploadId();
// partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
List<PartETag> partETags = new ArrayList<PartETag>();
// 每个分片的大小,用于计算文件有多少个分片。单位为字节。
final long partSize = 10 * 1024 * 1024L; //10 MB。
// 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
File folder = new File("填写本地文件的完整路径");
List<File> files = Arrays.asList(folder.listFiles((file) -> {
if (file.isDirectory()) {
return false;
}
return true;
}));
//根据文件名称进行排序
files.sort((f1,f2) -> Integer.valueOf(f1.getName()) - Integer.valueOf(f2.getName()));
// 遍历分片上传。
for (int i = 0; i < files.size(); i++) {
File file = files.get(i);
InputStream instream = new FileInputStream(files.get(i));
UploadPartRequest uploadPartRequest = new UploadPartRequest();
uploadPartRequest.setBucketName(bucketName);
uploadPartRequest.setKey(objectName);
uploadPartRequest.setUploadId(uploadId);
uploadPartRequest.setInputStream(instream);
// 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100KB。
uploadPartRequest.setPartSize(file.length());
// 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出这个范围,OSS将返回InvalidArgument的错误码。
uploadPartRequest.setPartNumber(i + 1);
// 每个分片不需要按顺序上传,甚至可以在不同客户端上传,OSS会按照分片号排序组成完整的文件。
UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
// 每次上传分片之后,OSS的返回结果会包含一个PartETag。PartETag将被保存到partETags中。
partETags.add(uploadPartResult.getPartETag());
}
// 创建CompleteMultipartUploadRequest对象。
// 在执行完成分片上传操作时,需要提供所有有效的partETags。OSS收到提交的partETags后,会逐一验证每个分片的有效性。当所有的数据分片验证通过后,OSS将把这些分片组合成一个完整的文件。
CompleteMultipartUploadRequest completeMultipartUploadRequest =
new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);
// 如果需要在完成文件上传的同时设置文件访问权限,请参考以下示例代码。
// completeMultipartUploadRequest.setObjectACL(CannedAccessControlList.PublicRead);
// 完成上传。
CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);
System.out.println(completeMultipartUploadResult.getETag());
// 关闭OSSClient。
ossClient.shutdown();
}
public static void main(String[] args) throws Exception{
// chunkUpload1();
chunkUpload2();
}
}