目录
简介
分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(称之为Part)来进行分别上传,上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件。
分片上传不仅可以避免因网络环境不好导致的一直需要从文件起始位置上传的问题,还能使用多线程对不同分块数据进行并发发送,提高发送效率。
分片上传思路图
![](https://img-blog.csdnimg.cn/3a96792e99504cf99e852344b2079823.png)
- 设置两个系统参数"是否分片"和"分片大小", 前者用于判断是否开启分片, 后者用于判断分片大小(不过此参数如果要修改,需要删除数据库未上传完的文件分片数据)
- 串行可以文件追加(第一个文件分片上传之后,后面的文件分片都往此文件上追加)或者文件合并, 并行只能文件合并.
- 某个节点上传失败后,客户端点击继续上传,客户调用查询上传进度接口.继续上传未上传完的文件分片
oss分片上传代码示例
初始化分片事件
向oss服务器获取全局唯一的uploadId, 如果之前上传到一半断了, 在数据库存储之前已上传的数据, 初始化时获取上传位置, 从指定位置开始上传
@Data
public class InitSliceDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "存储空间名称")
private String bucketName;
@ApiModelProperty(value = "对象名称")
private String objectName;
public InitSliceDTO() {
}
public InitSliceDTO(String bucketName, String objectName) {
this.bucketName = bucketName;
this.objectName = objectName;
}
}
public class InitSliceResultDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "oss的uploadId")
private String uploadId;
@ApiModelProperty(value = "存储空间名称")
private String bucketName;
@ApiModelProperty(value = "对象名称")
private String objectName;
public InitSliceResultDTO() {
}
public InitSliceResultDTO(String uploadId, String bucketName, String objectName) {
this.uploadId = uploadId;
this.bucketName = bucketName;
this.objectName = objectName;
}
}
// 创建InitiateMultipartUploadRequest对象。
OSSClient ossClient = FileUploadUtil.getInstance();
InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest
(initSliceDTO.getBucketName(), initSliceDTO.getObjectName());
InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
return new InitSliceResultDTO(upresult.getUploadId(), initSliceDTO.getBucketName(), initSliceDTO.getObjectName());
开始切片上传
- 分片一般由前端完成,通过后端将分片上传到oss服务器,上传时可无需按照顺序上传。
- 如果由前端直接上传到oss服务器, 后端仅需要向前端颁发凭证即可。
- 需要注意的是,在分片上传成功后需保存响应数据,合并时需要。
public class ChunkDTO implements Serializable { private static final long serialVersionUID = 1L; @NotNull(message = "文件块序号不能为空") @Min(message = "文件块序号必须大于等于1", value = 1) @Max(message = "文件块序号必须小于等于10000", value = 10000) @ApiModelProperty(value = "当前文件块,从1开始") private Integer chunkNumber; @ApiModelProperty(value = "分块大小") @NotNull(message = "分块大小不能为空") private Long chunkSize; @ApiModelProperty(value = "当前分块大小") @NotNull(message = "当前分块大小不能为空") @Max(message = "当前分块大小不打大于5G", value = 5 * 1024 * 1024 * 1024) private Long currentChunkSize; @ApiModelProperty(value = "总大小") @NotNull(message = "总大小不能为空") private Long totalSize; @ApiModelProperty(value = "文件标识") @NotBlank(message = "文件标识不能为空") private String identifier; @ApiModelProperty(value = "文件名") @NotBlank(message = "文件名不能为空") private String filename; @ApiModelProperty(value = "相对路径") private String relativePath; @ApiModelProperty(value = "总块数") @NotNull(message = "总块数不能为空") private Integer totalChunks; @ApiModelProperty(value = "oss的uploadId") private String uploadId; @ApiModelProperty(value = "对象名称,也是path") private String objectName; @ApiModelProperty(value = "二进制文件") @NotNull(message = "二进制文件不能为空") private MultipartFile file; } public class ChunkUploadDTO extends ChunkDTO { @ApiModelProperty(value = "存储空间名称") private String bucketName; @ApiModelProperty(value = "用于完成时合并所需参数") private List<PartETagDTO> partETags; } @ApiModel(description = "对应PartETag") public class PartETagDTO implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "分片序号") private int partNumber; @ApiModelProperty(value = "eTag") private String eTag; @ApiModelProperty(value = "分片大小") private long partSize; @ApiModelProperty(value = "主键") private Long partCRC; } public SliceUploadResultDTO sliceUpload(ChunkDTO chunkDTO) throws Exception { ChunkUploadDTO chunkUploadDTO = (ChunkUploadDTO) chunkDTO; SliceUploadResultDTO sliceUploadResultDTO = new SliceUploadResultDTO(); try { OSSClient ossClient = FileUploadUtil.getInstance(); String uploadId = chunkUploadDTO.getUploadId(); UploadPartRequest uploadPartRequest = new UploadPartRequest(); uploadPartRequest.setBucketName(chunkUploadDTO.getBucketName()); uploadPartRequest.setKey(chunkUploadDTO.getObjectName()); uploadPartRequest.setUploadId(uploadId); uploadPartRequest.setInputStream(chunkDTO.getFile().getInputStream()); // 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。上传时不会做校验,合并时会做校验 uploadPartRequest.setPartSize(chunkDTO.getCurrentChunkSize()); // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,OSS将返回InvalidArgument错误码。 uploadPartRequest.setPartNumber(chunkDTO.getChunkNumber()); // 每个分片不需要按顺序上传,甚至可以在不同客户端上传,OSS会按照分片号排序组成完整的文件。 UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest); PartETag partETag = uploadPartResult.getPartETag(); sliceUploadResultDTO.setResult(true); sliceUploadResultDTO.setPartCRC(partETag.getPartCRC()); sliceUploadResultDTO.setPartETag(partETag.getETag()); sliceUploadResultDTO.setPartSize(partETag.getPartSize()); sliceUploadResultDTO.setPartNumber(partETag.getPartNumber()); sliceUploadResultDTO.setObjectName(chunkUploadDTO.getObjectName()); sliceUploadResultDTO.setUploadId(uploadId); } catch (OSSException oe) { sliceUploadResultDTO.setResult(false); logger.error("Caught an OSSException, which means your request made it to OSS, " + "but was rejected with an error response for some reason."); logger.error("Error Message:" + oe.getErrorMessage()); logger.error("Error Code:" + oe.getErrorCode()); logger.error("Request ID:" + oe.getRequestId()); logger.error("Host ID:" + oe.getHostId()); } catch (ClientException ce) { sliceUploadResultDTO.setResult(false); logger.error("Caught an ClientException, which means the client encountered " + "a serious internal problem while trying to communicate with OSS, " + "such as not being able to access the network."); logger.error("ClientException Error Message:" + ce.getMessage()); } catch (Exception e) { logger.error("Exception Error Message:" + e.getMessage()); logger.error("Exception:" + JSONObject.toJSONString(e.getStackTrace())); sliceUploadResultDTO.setResult(false); } return sliceUploadResultDTO; } }
分片上传完成,合并上传
- 分片上传完成后,需主动通知oss服务器合并文件。
@ApiModel(description = "分片上传结果")
public class SliceUploadResultDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "是否上传成功结果")
private Boolean result;
@ApiModelProperty(value = "是否合并完成成功结果")
private Boolean completeResult;
@ApiModelProperty(value = "uploadId")
private String uploadId;
@ApiModelProperty(value = "分片序号")
private Integer partNumber;
@ApiModelProperty(value = "分片大小")
private Long partSize;
@ApiModelProperty(value = "分片etag")
private String partETag;
@ApiModelProperty(value = "分片crc,oss有此值")
private Long partCRC;
@ApiModelProperty(value = "合并后存储空间")
private String bucketName;
@ApiModelProperty(value = "合并后objectName对应key,也是路径")
private String objectName;
@ApiModelProperty(value = "location")
private String location;
@ApiModelProperty(value = "合并后etag")
private String eTag;
}
public SliceUploadResultDTO completeMultipartUpload(ChunkDTO chunkDTO) {
ChunkUploadDTO chunkUploadDTO = (ChunkUploadDTO) chunkDTO;
SliceUploadResultDTO sliceUploadResultDTO = new SliceUploadResultDTO();
try {
OSSClient ossClient = FileUploadUtil.getInstance();
List<PartETag> partETags = new ArrayList<>();
chunkUploadDTO.getPartETags().forEach(e -> {
partETags.add(new PartETag(e.getPartNumber(), e.getETag()));
});
// 分片上传结束后,调用complete完成分片上传
CompleteMultipartUploadRequest completeMultipartUploadRequest =
new CompleteMultipartUploadRequest(chunkUploadDTO.getBucketName(), chunkUploadDTO.getObjectName(),
chunkUploadDTO.getUploadId(), partETags);
CompleteMultipartUploadResult completeResult =
ossClient.completeMultipartUpload(completeMultipartUploadRequest);
// build返回参数
sliceUploadResultDTO.setCompleteResult(true);
sliceUploadResultDTO.setBucketName(completeResult.getBucketName());
sliceUploadResultDTO.setObjectName(completeResult.getKey());
sliceUploadResultDTO.seteTag(completeResult.getETag());
sliceUploadResultDTO.setLocation(completeResult.getLocation());
} catch (Exception e) {
logger.error("Exception Error Message:" + e.getMessage());
logger.error("oss completeMultipartUpload Exception" + JSONObject.toJSONString(e.getStackTrace()));
sliceUploadResultDTO.setCompleteResult(false);
}
return sliceUploadResultDTO;
}
}
关于前端的分片上传可以使用vue-simple-uploader。
以上就是分片上传的内容,欢迎大家来讨论指正。