1. 程式人生 > >阿里雲OSS單檔案斷點續傳+前端 簡單展示(springmvc架構)

阿里雲OSS單檔案斷點續傳+前端 簡單展示(springmvc架構)

業務沒有需要多檔案一起上傳,所以這裡只是單檔案,多檔案的話也是在獲得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;
    }

}

然後跑起來試試看吧