1. 程式人生 > >七牛雲檔案上傳

七牛雲檔案上傳

           最近做的專案需要上傳音視訊以及圖片,剛開始時候是用retrofit上傳,說實話,那叫一個簡單和爽啊,必須得贊一個。只需要將File往裡面一放就ok了。哦哦,貌似有點跑題了偷笑。我應該說七牛雲上傳檔案的。哈哈哈(這也足以說明retrofit上傳是多麼的便利了)。既然retrofit這麼的方便簡單,為啥還是要用七牛雲呢?因為服務端害怕將來使用者量大了,讀寫檔案多了,伺服器壓力太大,萬一搞癱了腫麼辦?所以就選擇了七牛雲。

           其實,這個整合也比較簡單。因為客戶端不需要申請任何的key,所以AndroidManifest裡面也不需要任何配置了;只需要在module app的build.gradle裡面新增    compile 'com.qiniu:qiniu-android-sdk:7.1.3'(當然這個版本可能更新,以七牛官網為準嘍)就好了。客戶端的上傳流程取決於服務端的上傳策略,剛開始我們採用的流程是 app申請上傳憑證---上傳檔案至七牛----七牛回撥到業務伺服器---業務伺服器處理完畢之後回撥至七牛-------七牛再次返回給app結果。哇,是不是好麻煩,流程也是非常繁瑣,萬一裡面某個環節出現了問題,都會導致上傳失敗。後來經過討論採用了第二種流程方式:app申請上傳憑證-----上傳檔案至七牛-----七牛檔案儲存完畢後返回給app----app再將結果上傳給業務伺服器;這樣最大的就是簡化了業務伺服器和七牛的互動,並且我們的很多業務引數也不必通過七牛傳遞到我們自己的伺服器了。可能有朋友會問,上面的上傳憑證是什麼,這個是服務端通過七牛生成的一個安全憑證,只有客戶端拿著這個上傳憑證上傳檔案才是有效的,否則七牛伺服器是不接受的。也算是為了安全吧。

        接下來看自己寫的一個類

/**
 * 向七牛上傳檔案,然後回撥業務伺服器
 * Created by hexiappang_android on 16/11/28.
 */
public class UploadFileQiniuUtils1 {
    private static int SUCCESSCOUNT = 0;//上傳成功個數
    private static int FAILCOUNT = 0;//失敗個數
    private static String stringKeys;//上傳檔案後的的key的拼接
    private static UploadFileQiniuUtils1 uploadFileUtils;
    private UploadManager uploadManager;//上傳管理
    private static String fileKey = null;//檔案上傳key;
    //單例模式獲取
    public static UploadFileQiniuUtils1 getInstance(){
        if(uploadFileUtils == null){
            uploadFileUtils = new UploadFileQiniuUtils1();
        }
        //每次將原有變數清空
        SUCCESSCOUNT = 0;
        FAILCOUNT = 0;
        stringKeys= "";
        fileKey = null;
        return uploadFileUtils;
    }

    /**
     * 上傳圖片(可能會有多張。現在一次上傳的的操作,只取一次上傳憑證;然後會併發上傳;)
     */
    public void uploadMultiFile(final List<String> stringList,final OnResponse onResponse){
        //獲取上傳憑證(這個是用retrofit從伺服器獲取的)
        RetrofitNetHelper.getInstance().getUploadToken(new NetResponseSubscriber<String>(null) {
            @Override
            protected void onError(ApiException ex) {
                onResponse.onFailure(ex.getDisplayMessage());
            }

            @Override
            protected void onResponse(final String stringToken, String successMsg) {
                //一個臨時集合
                final List<String> listTemp = new ArrayList<>();
                //因為壓縮圖片也是一個耗時的過程,所以將其放入非ui執行緒中,壓縮完成後,回撥至ui執行緒處理(採用的是RxJava的執行緒切換)
                Observable.create(new Observable.OnSubscribe<List<String>>() {
                    @Override
                    public void call(Subscriber<? super List<String>> subscriber) {
                        File fileTemp;
                        //迴圈傳入檔案路徑list
                        for(int i = 0;i < stringList.size();i++){
                            fileTemp = new File(stringList.get(i));
                            //如果傳入的檔案存在
                            if(fileTemp.exists()){
                                //進行壓縮(注意下面的檔名是用時間戳+當前的檔案在集合中所在位置的拼接,而不是用了檔案原有的名字,應的是服務端的要求)
                                fileTemp = ImageUtils.compressBitmap(stringList.get(i),800,480, System.currentTimeMillis()+i+".jpg");
                                //如果壓縮過後的圖片存在,那麼新增入新的list
                                if(fileTemp != null && fileTemp.exists()){
                                    listTemp.add(fileTemp.getAbsolutePath());
                                }
                            }
                        }
                        //將壓縮結果傳送至ui執行緒
                        subscriber.onNext(listTemp);
                    }
                }).subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe(new Subscriber<List<String>>() {
                            @Override
                            public void onCompleted() {

                            }

                            @Override
                            public void onError(Throwable e) {
                                //如果有錯誤,那麼會回撥給呼叫方
                                onResponse.onFailure(ResourcesUtil.getString(R.string.str_uploadfail));
                            }

                            @Override
                            public void onNext(final List<String> list) {
                                //如果上傳憑證非空,那麼執行上傳操作
                                if(!TextUtils.isEmpty(stringToken)){
                                    onResponse.onProgress(FormatUtils.resolveBuild("圖片上傳中","(1/",listTemp.size(),")"));
                                    //因為網路請求是非同步的,所以圖片請求會是同時進行的。不能保證順序操作
                                    for(int j=0;j<list.size();j++){
                                        //七牛雲上傳檔案管理類
                                        if (uploadManager == null) {
                                            uploadManager = new UploadManager();
                                        }
                                        File uploadFile = new File(list.get(j));
                                        //執行真正的上傳操作
                                        uploadManager.put(uploadFile, uploadFile.getName(), stringToken,
                                                new UpCompletionHandler() {
                                                    @Override
                                                    public void complete(String key, ResponseInfo respInfo,
                                                                         JSONObject jsonData) {
                                                        //各個檔案之間的key用逗號分隔(這個是業務的需要,大家不一定這麼寫)
                                                        stringKeys = FormatUtils.resolveBuild(stringKeys,key,",");
                                                        //如果某個檔案上傳成功
                                                        if(respInfo.isOK()){
                                                            SUCCESSCOUNT++;//成功數量+1
                                                        }else{
                                                            FAILCOUNT++;//否則失敗數量+1
                                                        }
                                                        //這個是將“上傳進度”回撥給呼叫方,其實也算是是假的,因為網路請求是幾乎同時發出的,也是非同步的,這裡只是將上傳完畢的記結果給回去了
                                                        onResponse.onProgress(FormatUtils.resolveBuild("圖片上傳中","(",SUCCESSCOUNT,"/",list.size(),")"));
                                                        //如果所有的圖片都已經上傳完畢了
                                                        if(SUCCESSCOUNT + FAILCOUNT == list.size()){
                                                            if(FAILCOUNT == 0){//如果失敗的為0,則代表全部都成功,然後上傳key
                                                                stringKeys = stringKeys.substring(0,stringKeys.length()-1);

                                                            }else{//只要有其中一張圖片上傳失敗則認為上傳失敗
                                                                onResponse.onFailure(ResourcesUtil.getString(R.string.str_uploadfail));
                                                            }
                                                        }
                                                    }
                                                }, null);
                                    }
                                }else{
                                    onResponse.onFailure(ResourcesUtil.getString(R.string.str_uploadfail));
                                }
                            }
                        });
            }
        });
    }

    /**
     * 上傳單一檔案(主要是音視訊)
     */
    public void upLoadSingleFile(final String localPath, final OnResponse onResponse){
        //在這裡構造 上傳檔案的key,規則是字首+時間戳+檔案字尾名
        if(type == Const.UPLOAD_AUDIO){
            fileKey = System.currentTimeMillis()+".mp3";
        }else if(type == Const.UPLOAD_VIDEO){
            fileKey = System.currentTimeMillis()+".mp4";
        }
        final File file = new File(localPath);
        if(file.exists()){
            //這裡有個檔案最大位元組的限制
            if(file.length() > Const.MAX_FILE_BYTES){
                onResponse.onFailure("上傳檔案太大,不能上傳");
                return;
            }
            //獲取上傳憑證
            RetrofitNetHelper.getInstance().getUploadToken(new NetResponseSubscriber<String>(null) {
                @Override
                protected void onError(ApiException ex) {
                    onResponse.onFailure(ex.getDisplayMessage());
                }

                @Override
                protected void onResponse(String s, String successMsg) {
                    if(!TextUtils.isEmpty(s)){
                        if (uploadManager == null) {
                            uploadManager = new UploadManager();
                        }
                        UploadOptions uploadOptions = new UploadOptions(null, null, false,
                                new UpProgressHandler() {
                                    @Override
                                    public void progress(String key, double percent) {
                                        onResponse.onProgress(FormatUtils.resolveBuild("上傳中(",FormatUtils.formatNumb(percent*100),"%)"));
                                    }
                                }, null);
                        uploadManager.put(file, fileKey, s, new UpCompletionHandler() {
                            @Override
                            public void complete(String key, ResponseInfo respInfo,
                                                 JSONObject jsonData) {
                                if(respInfo.isOK()){

                                }else{
                                    onResponse.onFailure(ResourcesUtil.getString(R.string.str_uploadfail));
                                }
                            }
                        }, uploadOptions);
                    }else{
                        onResponse.onFailure(ResourcesUtil.getString(R.string.str_uploadfail));
                    }
                }
            });
        }else{
            onResponse.onFailure("上傳的視訊不存在");
        }
    }

    //對於檔案上傳結果的回撥介面
    public interface OnResponse{
        void onSuccess();
        void onFailure(String message);

        /**
         * 對於檔案上傳進度的提示(如果是圖片則會有處理中。。。以及上傳position/size的提示。對於音視訊則會有x%的提示了)
         */
        void onProgress(String message);
    }

}

        這個類主要是有兩個方法了,一個是上傳單一檔案的,另外一個就是上傳多張圖片的,另外最後提供了將上傳結果以及進度回撥給呼叫方的介面(註釋寫的也比較詳細了大笑)。先說那個多張圖片上傳的,其實一次上傳操作無論是單檔案的還是多檔案的,我們都是隻獲取一次上傳憑證,因為伺服器設定有失效時間,當然可以確定的是一次上傳過程中,這個失效時間一定是不會到的啦。大家都知道網路請求是非同步的,所以貌似很難保證順序上傳了,就是一張完了接著另外一張,暫時還沒想到怎麼來做。另外,即使可以做到一張一張傳,那樣豈不是很耗時呀,使用者體驗可能也不是很好了。所以最終就寫成了上面的樣子,多張圖片請求幾乎是同步的了。其實用了兩個變數來判斷是否上傳完畢了,因為網路請求幾乎同時發出。所以誰先回來都不知道。所以每次返回時候判斷成功個數+失敗個數是否等於要上傳的檔案數。如果相等了代表所有圖片的上傳都有一個返回結果了。另外如果失敗個數不為0,則會認為上傳失敗,則會提示使用者進行重新上傳了。然後單一檔案的上傳其實和多檔案上傳幾乎一樣,只是我們為了上傳的進度,多用了一個UploadOptions了,它能夠將上傳的進度以double值回調回來了。

       最後還要說下上面的圖片的命名,也就是我上傳給七牛的key,就是uploadMagager的第二個引數了。由於當時服務端說他們指定的上傳策略的緣故,我們客戶端不能上傳兩個key一樣的檔案,要不然七牛會報錯的,當時覺得不合理,但是服務端既然這麼說了,說的那麼真誠(哈哈,我好壞,我們服務端還是很給力的)然後我們就這樣子做了。當時也沒有驗證,但是剛剛在寫這篇文章的時候,我想我得試試,要不然看誤導了看這篇文章的人就不好了。所以我就上傳了好幾張相同的圖片,如下圖

,然後我在七牛後臺看到的是

同一個key(我用的是檔名)的檔案就只有一個。所以得出的結論是,同一個檔案上傳多次,七牛隻會儲存一次了。我上面的程式碼暫時還沒改。其實圖片的名字被改的那麼複雜一是滿足業務需要,二是為了不重複,”不報錯“。當然事實證明是沒報錯的。但是最終我的這個程式碼是否要改,還得週一去跟服務端商量。其實,這樣用檔名作為key,至少能夠減少一些儲存空間了。

          好了,這篇部落格先暫時這個樣子了。如有錯誤,歡迎指正。多謝大笑。祝大家週末愉快。