天天看點

七牛雲檔案上傳

           最近做的項目需要上傳音視訊以及圖檔,剛開始時候是用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,至少能夠減少一些存儲空間了。

          好了,這篇部落格先暫時這個樣子了。如有錯誤,歡迎指正。多謝

七牛雲檔案上傳

。祝大家周末愉快。

繼續閱讀