当上传大文件时,经常出现因网络不稳定或程序崩溃导致上传失败的情况。失败后再次重新上传不仅浪费资源,而且当网络不稳定时仍然有上传失败的风险。断点续传上传接口uploadFile能有效地解决此类问题引起的上传效率低下的问题。其原理是将待上传的文件分成若干个分片分别上传,如果出现网络异常或程序崩溃导致文件上传失败时,会将中断对象的断点处记录下来,从而能在失败重传时继续上传未上传完成的部分,节省资源提高效率,还因其能够对分片进行并发上传的机制能加快上传速度。
本文主要介绍通过Java SDK实现断点续传。SDK下载地址:SDK概览。
注意事项
- 断点续传上传,您必须必须是桶拥有者或拥有上传对象的权限,才能上传对象。 
- 断点续传上传接口传入的文件大小至少要5MB以上,因为最小的分片大小就是5MB。 
- 使用SDK的断点续传接口时,必须开启断点续传选项 - setEnableCheckpoint为- true才能在再次上传同一对象时读取到之前的上传进度。
- 您可实现 - ProgressListener接口来实现对上传进度的监控。
示例代码
断点续传
以下为断点续传示例代码。
public class ResumeUploadDemo {
	private static String endpoint = "https://gdoss.xstore.ctyun.cn";//资源池endpoint,示例以断广东资源池1区为例,其他资源池请根据实际情况填写
	private static String accessKeyId = "ak";// 主账号或子账号ak
	private static String accessKeySecret = "sk";// 主账号或子账号sk
	private static String bucketName = "bucket";// 上传对象的目标bucket
	private static String key = "xx.xx";// 对象名
	private static String uploadFile = "xx.xx";// 本地待上传文件路径
	public static void main(String[] args) {
		ClientConfiguration clientConfiguration = new ClientConfiguration();
		BasicAWSCredentials credentials = new BasicAWSCredentials(accessKeyId, accessKeySecret);
		AWSStaticCredentialsProvider credProvider = new AWSStaticCredentialsProvider(credentials);
		AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
				endpoint, Regions.DEFAULT_REGION.getName());
		AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
				.withCredentials(credProvider)
				.withClientConfiguration(clientConfiguration)
				.withEndpointConfiguration(endpointConfiguration)
				.withPathStyleAccessEnabled(false)
				.build();
		try {
			// 通过UploadFileRequest设置多个参数。
			// 依次填写Bucket名称以及对象名称。
			UploadFileRequest uploadFileRequest = new UploadFileRequest(bucketName, key);
			// 指定监听器,当您实现以上接口后,可在这设置您的进度监控实例,从而完成对于上传进度的监控。
			uploadFileRequest.setProgressListener(progressListener);
			// 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
			uploadFileRequest.setUploadFile(uploadFile);
			// 指定上传并发线程数,默认值为1。
			uploadFileRequest.setTaskNum(5);
			// 指定上传的分片大小,单位为字节。默认值为5 MB。
			uploadFileRequest.setPartSize(1024 * 1024 * 5);
			// 开启断点续传,默认关闭。开启后,上传过程中的进度信息会保存在文件中,默认与待上传的本地文件同路径,名称为${uploadFile}.ucp,如果某一分片上传失败,再次上传时会根据文件中记录的点继续上传。上传完成后,该文件会被删除。
			uploadFileRequest.setEnableCheckpoint(true);
			try {
				UploadFileResult uploadFileResult = s3Client.uploadFile(uploadFileRequest);
				CompleteMultipartUploadResult multipartUploadResult = uploadFileResult.getMultipartUploadResult();
				System.out.println(multipartUploadResult);
			} catch (Throwable e) {
				throw new RuntimeException(e);
			}
		} catch (Exception e) {
			System.err.println("Upload failed: " + e.getMessage());
			e.printStackTrace();
		}
	}
}进度监控接口
通过实现上传过程监控接口,从而可以在上传过程中掌握您的大文件上传进度。常用的监听事件有:
- REQUEST_CONTENT_LENGTH_EVENT(要在请求中发送的对象内容长度的事件) 
- TRANSFER_STARTED_EVENT(上传开始事件) 
- TRANSFER_PART_STARTED_EVENT(开始上传分片事件) 
- TRANSFER_PART_COMPLETED_EVENT(分片上传完毕事件) 
- TRANSFER_COMPLETED_EVENT(上传完毕事件) 
- TRANSFER_FAILED_EVENT(上传失败事件) 
- TRANSFER_PART_FAILED_EVENT(上传分片失败事件) 
当有相应场景发生时progressChanged,则会产生相应的事件回调,即可调用您的监控代码从而掌握对象的上传进度。
以下为进度监控代码。
ProgressListener progressListener = new ProgressListener() {
	private long totalBytes = -1;
	private long transferredBytes = 0;
	private long startTime = System.currentTimeMillis();
	private final Object lock = new Object(); // 用于线程安全更新进度
	
	@Override
	public void progressChanged(ProgressEvent event) {
		switch (event.getEventType()) {
			case REQUEST_CONTENT_LENGTH_EVENT:
				totalBytes = event.getBytes();
				System.out.printf("总大小: %,d bytes%n", totalBytes);
				break;
			case TRANSFER_STARTED_EVENT:
				System.out.println("[开始] 文件传输启动");
				startTime = System.currentTimeMillis();
				break;
			case CLIENT_REQUEST_SUCCESS_EVENT:
				System.out.printf("[秒传] 文件已存在服务端 (大小: %.2fMB)%n", event.getBytes() / 1024.0 / 1024);
				break;
			case TRANSFER_PART_STARTED_EVENT:
				long encodedStart = event.getBytes();
				int partNumber = (int) (encodedStart >> 32);
				long partSize = encodedStart & 0xFFFFFFFFL;
				System.out.printf("[分片] #%d 开始上传 (大小: %.2fMB)%n",
				partNumber, partSize / 1024.0 / 1024);
				break;
			case TRANSFER_PART_COMPLETED_EVENT:
				long encodedComplete = event.getBytes();
				long actualBytes = encodedComplete & 0xFFFFFFFFL;
				synchronized (lock) {
					transferredBytes += actualBytes;
					}
				printProgress();
				break;
			case TRANSFER_COMPLETED_EVENT:
				System.out.println("\n[完成] 所有分片上传成功");
				printFinalStats();
				break;
				case TRANSFER_FAILED_EVENT:
				System.err.println("\n[失败] 文件传输异常终止");
				printFinalStats();
				break;
			case TRANSFER_PART_FAILED_EVENT:
				long encodedFail = event.getBytes();
				int failedPart = (int) (encodedFail >> 32);
				System.err.printf("[异常] 分片 #%d 上传失败%n", failedPart);
				break;
				}
			}
			private void printProgress() {
				synchronized (lock) {
					if (totalBytes <= 0)
						return;
					double percent = transferredBytes * 100.0 / totalBytes;
					System.out.printf("\r[进度] %.2f%% - %.2fMB/%.2fMB",
							percent,
							transferredBytes / 1024.0 / 1024,
							totalBytes / 1024.0 / 1024);
				}
			}
			private void printFinalStats() {
				long endTime = System.currentTimeMillis();
				double elapsed = (endTime - startTime) / 1000.0;
				double speed = (transferredBytes / 1024.0 / 1024) / elapsed;
				System.out.printf("耗时: %.1fs | 平均速度: %.1fMB/s%n", elapsed, speed);
			}
		};