1. 程式人生 > >阿里雲OSS 分塊上傳的程式碼整理

阿里雲OSS 分塊上傳的程式碼整理

廢話不多說,把程式碼貼出來。

package com.changba.erp.utils;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.CompleteMultipartUploadRequest;
import com.aliyun.oss.model.InitiateMultipartUploadRequest;
import com.aliyun.oss.model.InitiateMultipartUploadResult;
import com.aliyun.oss.model.ListPartsRequest;
import com.aliyun.oss.model.PartETag;
import com.aliyun.oss.model.PartListing;
import com.aliyun.oss.model.PartSummary;
import com.aliyun.oss.model.UploadPartRequest;
import com.aliyun.oss.model.UploadPartResult;

public class AliyunOSSUpload implements Runnable {
	private MultipartFile localFile;
	private long startPos;

	private long partSize;
	private int partNumber;
	private String uploadId;
	private static String key;
	private static String bucketName;
	
	// 新建一個List儲存每個分塊上傳後的ETag和PartNumber
	protected static List<PartETag> partETags = Collections.synchronizedList(new ArrayList<PartETag>());
	
	private static Logger logger = LoggerFactory.getLogger(FileUploader.class);
	
	protected static OSSClient client = null;

	/**
	 * 建立構造方法
	 * 
	 * @param localFile
	 *            要上傳的檔案
	 * @param startPos
	 *            每個檔案塊的開始
	 * @param partSize
	 * @param partNumber
	 * @param uploadId
	 *            作為塊的標識
	 * @param key
	 *            上傳到OSS後的檔名
	 */
	public AliyunOSSUpload(MultipartFile localFile, long startPos, long partSize, int partNumber, String uploadId, String key , String bucketName) {
		this.localFile = localFile;
		this.startPos = startPos;
		this.partSize = partSize;
		this.partNumber = partNumber;
		this.uploadId = uploadId;
		AliyunOSSUpload.key = key;
		AliyunOSSUpload.bucketName = bucketName;
	}

	/**
	 * 分塊上傳核心方法(將檔案分成按照每個5M分成N個塊,並加入到一個list集合中)
	 */
	@Override
	public void run() {
		InputStream instream = null;
		try {
			// 獲取檔案流
			instream = localFile.getInputStream();
			// 跳到每個分塊的開頭
			instream.skip(this.startPos);

			// 建立UploadPartRequest,上傳分塊
			UploadPartRequest uploadPartRequest = new UploadPartRequest();
			uploadPartRequest.setBucketName(bucketName);
			uploadPartRequest.setKey(key);
			uploadPartRequest.setUploadId(this.uploadId);
			uploadPartRequest.setInputStream(instream);
			uploadPartRequest.setPartSize(this.partSize);
			uploadPartRequest.setPartNumber(this.partNumber);

			UploadPartResult uploadPartResult = FileUploader.client.uploadPart(uploadPartRequest);
			logger.info("Part#" + this.partNumber + " done\n");
			synchronized (partETags) {
				// 將返回的PartETag儲存到List中。
				partETags.add(uploadPartResult.getPartETag());
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (instream != null) {
				try {
					// 關閉檔案流
					instream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 初始化分塊上傳事件並生成uploadID,用來作為區分分塊上傳事件的唯一標識
	 * 
	 * @return
	 */
	protected static String claimUploadId(String bucketName, String key) {
		InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, key);
		InitiateMultipartUploadResult result = FileUploader.client.initiateMultipartUpload(request);
		logger.info(result.getUploadId());
		return result.getUploadId();
	}

	/**
	 * 將檔案分塊進行升序排序並執行檔案上傳。
	 * 
	 * @param uploadId
	 */
	protected static void completeMultipartUpload(String uploadId) {
		// 將檔案分塊按照升序排序
		Collections.sort(partETags, new Comparator<PartETag>() {
			@Override
			public int compare(PartETag p1, PartETag p2) {
				return p1.getPartNumber() - p2.getPartNumber();
			}
		});

		CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucketName,
				key, uploadId, partETags);
		// 完成分塊上傳
		FileUploader.client.completeMultipartUpload(completeMultipartUploadRequest);
	}

	/**
	 * 列出檔案所有分塊的清單
	 * 
	 * @param uploadId
	 */
	protected static void listAllParts(String uploadId) {
		ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, key, uploadId);
		// 獲取上傳的所有分塊資訊
		PartListing partListing = FileUploader.client.listParts(listPartsRequest);

		// 獲取分塊的大小
		int partCount = partListing.getParts().size();
		// 遍歷所有分塊
		for (int i = 0; i < partCount; i++) {
			PartSummary partSummary = partListing.getParts().get(i);
			logger.info("分塊編號 " + partSummary.getPartNumber() + ", ETag=" + partSummary.getETag());
		}
	}
}
檔案上傳程式碼:
package com.changba.erp.utils;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import com.aliyun.oss.OSSClient;

public class FileUploader {

	private static String endpoint = "http://oss-cn-hangzhou.aliyuncs.com";
	private static String accessKeyId = "FAsc5DqUOpCMZqv8";
	private static String accessKeySecret = "ewbsDctAbvNQbBl4d66mcoYgNC9N9T";
	private static String bucketName = "mysong";

	protected static OSSClient client = null;

	private static Logger logger = LoggerFactory.getLogger(FileUploader.class);

	public static String fileUpload(MultipartFile file) {
		
		// 建立一個可重用固定執行緒數的執行緒池。若同一時間執行緒數大於10,則多餘執行緒會放入佇列中依次執行
		ExecutorService executorService = Executors.newFixedThreadPool(10);
		
		String key = file.getOriginalFilename(); // 獲取上傳檔案的名稱,作為在OSS上的檔名
		// 建立OSSClient例項
		client = new OSSClient(endpoint, accessKeyId, accessKeySecret);
		try {
			String uploadId = AliyunOSSUpload.claimUploadId(bucketName, key);
			// 設定每塊為 5M(除最後一個分塊以外,其他的分塊大小都要大於5MB)
			final long partSize = 5 * 1024 * 1024L;
			// 計算分塊數目
			long fileLength = file.getSize();
			int partCount = (int) (fileLength / partSize);
			if (fileLength % partSize != 0) {
				partCount++;
			}

			// 分塊 號碼的範圍是1~10000。如果超出這個範圍,OSS將返回InvalidArgument的錯誤碼。
			if (partCount > 10000) {
				throw new RuntimeException("檔案過大(分塊大小不能超過10000)");
			} else {
				logger.info("一共分了 " + partCount + " 塊");
			}

			/**
			 * 將分好的檔案塊加入到list集合中
			 */
			for (int i = 0; i < partCount; i++) {
				long startPos = i * partSize;
				long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
				
				// 執行緒執行。將分好的檔案塊加入到list集合中
				executorService.execute(new AliyunOSSUpload(file, startPos, curPartSize, i + 1, uploadId, key, bucketName));
			}

			/**
			 * 等待所有分片完畢
			 */
			// 關閉執行緒池(執行緒池不馬上關閉),執行以前提交的任務,但不接受新任務。
			executorService.shutdown();
			// 如果關閉後所有任務都已完成,則返回 true。
			while (!executorService.isTerminated()) {
				try {
					// 用於等待子執行緒結束,再繼續執行下面的程式碼
					executorService.awaitTermination(5, TimeUnit.SECONDS);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}

			/**
			 * partETags(上傳塊的ETag與塊編號(PartNumber)的組合) 如果校驗與之前計算的分塊大小不同,則丟擲異常
			 */
			System.out.println(AliyunOSSUpload.partETags.size()  +" -----   "+partCount);
			if (AliyunOSSUpload.partETags.size() != partCount) {
				throw new IllegalStateException("OSS分塊大小與檔案所計算的分塊大小不一致");
			} else {
				logger.info("將要上傳的檔名  " + key + "\n");
			}

			/*
			 * 列出檔案所有的分塊清單並列印到日誌中,該方法僅僅作為輸出使用
			 */
			AliyunOSSUpload.listAllParts(uploadId);

			/*
			 * 完成分塊上傳
			 */
			AliyunOSSUpload.completeMultipartUpload(uploadId);
			
			// 返回上傳檔案的URL地址
			return endpoint + "/" + bucketName + "/" + client.getObject(bucketName, key).getKey();

		} catch (Exception e) {
			logger.error("上傳失敗!", e);
			return "上傳失敗!";
		} finally {
			AliyunOSSUpload.partETags.clear();
			if (client != null) {
				client.shutdown();
			}
		}
	}
}