阿里雲OSS單檔案斷點續傳+前端 簡單展示(springmvc架構)
阿新 • • 發佈:2018-12-30
業務沒有需要多檔案一起上傳,所以這裡只是單檔案,多檔案的話也是在獲得File的地方變成List即可,多個迴圈,多一些執行緒,網上有程式碼
一、pom.xml
<dependencys>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>3.0.8</version >
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>2.2.0</version>
</dependency>
</dependencys>
<!-- 阿里雲sdk -->
<repositories>
<repository>
<id>sonatype-nexus-staging</id>
<name>Sonatype Nexus Staging</name>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
<releases>
<enabled >true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
二、前端(簡單寫,不是很嚴謹)
“`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script type="text/javascript" src="js/jquery-1.11.0.js"></script>
<script type="application/javascript">
//標識,如果為true則每3秒訪問一次進度
var timeout = false;
function progress(){
if(!timeout) return;
$.post("u_test.do",function(f){
if(null == f){
$("#jindu").val("上傳錯誤");
}else{
$("#jindu").val(f+"%");
}
})
setTimeout(progress,3000);
}
$(function(){
$("#upload").click(function(){
var uploadUrl = "upload.do";
callback = function(res) {
timeout = false;
$("#d").append("上傳結束" + res);
$("#jindu").val("100%");
};
uploading = function(res) {
$("#d").append(res+",");
timeout = true;
$("#jindu").val("1%");
setTimeout(progress,3000);
};
beforeSend = function() {
$("#d").append("開始");
};
var file = document.getElementById("myUpload-input").files[0];
if(beforeSend instanceof Function){
if(beforeSend(file) === false){
return false;
}
}
var fd = new FormData();
fd.append("file", file);
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
if(callback instanceof Function){
callback(xhr.responseText);
}
}
}
//偵查當前附件上傳到TOMCAT的快取情況,並不是真正傳到了OSS中。如果是個大檔案(2G+),這個過程也比較慢,有個初始化的進度條也可以
xhr.upload.onprogress = function(evt) {
var loaded = evt.loaded;
var tot = evt.total;
var per = Math.floor(100 * loaded / tot); //已經上傳的百分比
if(uploading instanceof Function){
uploading(per);
}
};
xhr.open("post", uploadUrl);
xhr.send(fd);
})
});
</script>
</head>
<body>
//如果能得到檔案的url,後端直接就使用該url即可,也方便,file不用轉來轉去
<!--請輸入url或檔案路徑:<input name="url" value="" id="url" width="200px"/><br>-->
<input type="file" id="myUpload-input" name="file"/><br>
<input type="button" value="點選上傳" id="upload"/>
<div id="d">存入快取的進度:</div>
上傳的進度:<input id="jindu" value="0" />
</body>
</html>
前端效果
因為網路很慢,所以一片的大小盡管只有5M,上傳還是挺慢
後端程式碼,注意我配置的springmvc是攔截.do路徑,繼承了一個建立OSSClient的工具類,在後面貼出
package xxxx
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.*;
import com.market.web.controller.NewsControllerApi;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import java.io.*;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* @author
* @create 2016-10-31 19:13
* @others 阿里雲OSS測試
*/
@Controller
public class MultipartUpload extends OSSConfig {
//日誌記錄
private static final Logger LOG = LoggerFactory.getLogger(MultipartUpload.class);
//獲取配置檔案
ResourceBundle resourceBundle = ResourceBundle.getBundle("config");
//預設5個執行緒
private static ExecutorService executorService = Executors.newFixedThreadPool(5);
//儲存OSS返回來的partETags,是一個執行緒同步的arraylist,靠這個去查詢
private static List<PartETag> partETags = Collections.synchronizedList(new ArrayList<PartETag>());
//如果上傳完畢,uploadId則失效,這裡將client設為null,則進度查詢會顯示完畢(不是必須)
private static OSSClient client = null;
// 建立一個要存放的Object的名稱,使用檔名怕重複,直接改名+存入資料庫
private static String key;
//預設一個靜態的欄位貯備,查詢進度需要
private static String uploadId;
//預設一個靜態的欄位儲備檔案分割的數量,進度查詢需要
private static Integer partCount;
//識別符號,false情況下說名還沒有開始,true則前端可以開始查詢進度(不是必須)
private boolean progress = false;
// 每片最少5MB,這裡寫死了,最好用配置檔案隨時修改
final long partSize = 5 * 1024 * 1024L;
@ResponseBody
@RequestMapping("upload")
public void upload(@RequestParam(value = "file", required = false) MultipartFile mFile)
throws IOException {
//隨便寫一個檔名,這個是要和資料庫關聯的
key = "time-1121";
// MultipartFile轉File,springmvc得到MultipartFile非常簡單
CommonsMultipartFile cf= (CommonsMultipartFile)mFile;
DiskFileItem dFile = (DiskFileItem)cf.getFileItem();
File file = dFile.getStoreLocation();
// 構造OSSClient
client = new OSSClient(endpoint, accessKeyId, accessKeySecret);
try {
// 1-獲得uploadId
uploadId = getUploadId(client, bucketName, key);
// 2-斷點續傳分片
partCount = getPartCount(file);
// 3-開始配置子執行緒,開始上傳多片檔案到自己的bucket(oss的資料夾)中
for (int i = 0; i < partCount; i++) {
long startPos = i * partSize;
long curPartSize = (i + 1 == partCount) ? (file.length() - startPos) : partSize;
executorService.execute(new PartUploader(file, startPos, curPartSize, i + 1, uploadId));
}
// 4-方法執行中(只是步驟展示,這裡沒有程式碼)
// 5-將識別符號設為true,前端開始可以訪問到上傳進度
progress = true;
// 6-等待所有的執行緒上傳完畢後關閉執行緒
executorService.shutdown();
while (!executorService.isTerminated()) {
try {
//如果有執行緒沒有完畢,等待5秒
executorService.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 7-判斷是否所有的分片都上傳完畢(不是必須)
// judgePartCount();
// 8-必須:在將所有資料Part都上傳完成後對整個檔案的分片驗證。
verification();
// 9-成功,恢復初始值(恢復初始值是為了不讓這次上傳影響其他的檔案上傳)
restoreDefaultValues();
// 10-下載上傳的檔案(不是必須)
//client.getObject(new GetObjectRequest(bucketName, key), new File(localFilePath));
} catch (OSSException oe) {
System.out.println("OSS錯誤原因:");
System.out.println("Error Message: " + oe.getErrorCode());
System.out.println("Error Code: " + oe.getErrorCode());
System.out.println("Request ID: " + oe.getRequestId());
System.out.println("Host ID: " + oe.getHostId());
} catch (ClientException ce) {
System.out.println("OSSClient錯誤原因:");
System.out.println("Error Message: " + ce.getMessage());
} finally {
//注意關閉資源
if (client != null) {
client.shutdown();
}
}
}
/**
* 獲得uploadId
*/
public String getUploadId(OSSClient client, String bucketName, String key){
InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, key);
InitiateMultipartUploadResult result = client.initiateMultipartUpload(request);
return result.getUploadId();
}
/**
* 斷點續傳分片數量判斷
*/
public int getPartCount(File file){
long fileLength = file.length();
int partCount = (int) (fileLength / partSize); // 分片個數,不能超過10000
if (fileLength % partSize != 0) {
partCount++;
}
if (partCount > 10000) {
throw new RuntimeException("分片不能超過10000,請修改每片大小");
} else {
return partCount;
}
}
/**
* 子執行緒上傳(多執行緒的上傳效果提升明顯,如果單執行緒則在for迴圈中一個個上傳即可)
*/
private static class PartUploader implements Runnable {
private File localFile;
private long startPos;
private long partSize;
private int partNumber;
private String uploadId;
public PartUploader(File localFile, long startPos, long partSize, int partNumber, String uploadId) {
this.localFile = localFile;
this.startPos = startPos;
this.partSize = partSize;
this.partNumber = partNumber;
this.uploadId = uploadId;
}
@Override
public void run() {
InputStream instream = null;
try {
instream = new FileInputStream(this.localFile);
instream.skip(this.startPos);
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 = client.uploadPart(uploadPartRequest);
synchronized (partETags) {
partETags.add(uploadPartResult.getPartETag());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (instream != null) {
try {
instream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 判斷是否上傳完畢,這裡起始需要得到哪個錯了,從哪個開始重新上傳,獲得part#,然後計算5M的大小上傳
* 這裡只是單純的記錄一下,以後再完善
* @return
*/
public void judgePartCount(){
if (partETags.size() != partCount) {
System.out.println("一部分沒有上傳成功,需要從ListMultipartUploads裡面檢視上傳數量");
throw new IllegalStateException("Upload multiparts fail due to some parts are not finished yet");
} else {
System.out.println("上傳成功,檔名稱:" + key + "\n");
}
}
/**
* 驗證檔案是否完全,OSS必須走這個方法
* 最後返回CompleteMultipartUploadResult,這裡不繼續深化了
* @return
*/
public void verification(){
// 修改順序
Collections.sort(partETags, new Comparator<PartETag>() {
@Override
public int compare(PartETag p1, PartETag p2) {
return p1.getPartNumber() - p2.getPartNumber();
}
});
System.out.println("開始驗證\n");
CompleteMultipartUploadRequest completeMultipartUploadRequest =
new CompleteMultipartUploadRequest(bucketName, key, uploadId, partETags);
client.completeMultipartUpload(completeMultipartUploadRequest);
}
/**
* 恢復初始值
* @return
*/
public void restoreDefaultValues(){
executorService = Executors.newFixedThreadPool(5);
partETags = Collections.synchronizedList(new ArrayList<PartETag>());
client = null;
uploadId = null;
partCount = 0;
}
/**
* 頁面上進度查詢走這個方法
* @return
*/
@ResponseBody
@RequestMapping("u_test")
public Object u_test(){
//獲得OSS上面的已經好了的分片和總分片相除獲得進度
ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, key, uploadId);
PartListing partListing;
try {
partListing = client.listParts(listPartsRequest);
Integer nowPartCount = partListing.getParts().size();
return (int)((double)nowPartCount/partCount * 100);
}catch (Exception e){
//如果識別符號為true說明已經開始上傳,設為100%;否則說明剛進入方法,還沒有上傳,設為1%
if(progress)
return "100";
else
return "1";
}
}
}
繼承的父類
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.Bucket;
/**
* @author
* @create 2016-10-31 15:05
* @others 存放儲存上傳的資訊
*/
public class OSSConfig {
// endpoint為訪問域名
// 可以使用OSS域名、自定義域名(CNAME)、IP、STS作為endpoint
// STS(介紹:https://help.aliyun.com/document_detail/31931.html?spm=5176.doc32010.2.6.u4i4ck)
// 訪問域名對照中心可參看;https://help.aliyun.com/document_detail/31837.html?spm=5176.doc32010.2.1.EwrB1F
// 此處endpoint以杭州為例,其它region請按實際情況填寫:http://oss-cn-hangzhou.aliyuncs.com
protected static String endpoint = "oss-cn-hangzhou.aliyuncs.com";
// accessKey請登入https://ak-console.aliyun.com/#/檢視
// accessKeyId和accessKeySecret為申請的祕鑰
protected static String accessKeyId = "";
protected static String accessKeySecret = "";
// 隨機生成一個15位的BucketName,相當於名稱空間(容器)的名稱 "mybucket"+RandomUtils.getNumRandom(15)
protected static String bucketName = "";
protected static String localFilePath = "檔案下載位置";
// 建立OSSClient例項
protected static OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
/**
* 建立一個Bucket 儲存空間
* @param buckets 建立Bucket的名稱
* @return 建立的Bucket物件
*/
public Bucket createBucket(String buckets){
Bucket bucket = ossClient.createBucket(buckets);
// 關閉client
ossClient.shutdown();
return bucket;
}
}