1. 程式人生 > >**關於阿里雲oss圖片批量上傳問題解析**

**關於阿里雲oss圖片批量上傳問題解析**

關於阿里雲oss圖片批量上傳問題解析
背景
最近在專案開發過程中遇到的問題,消耗了比較久的時間,過程曲折,雖然最終達到目的,但是鑑於各種常用的雲資料在物件儲存方面大同小異,所以記錄一下。
理一下思路:
我們在呼叫第三方物件儲存API的時候,通常需要解決以下問題:

1.考慮通用性
2.在呼叫API過程中需要的引數.
3.如何處理資料(使用方式、成功與失敗)
4.呼叫過程中的異常處理

通用性

在整個App中好幾個介面都涉及到圖片上傳,根據每個介面上傳的特點,將其整合成了一個工具類。

引數
閱讀阿里雲的API以及SDK文件,找關鍵字,事實證明這樣真不好,發現幾個重要的欄位:Endpoint、Bucket、STS、OssClient、KeyObject、Meta、data、OSSAsyncTask,我理解就是拿一個地址一層層找檔案,完整的url長這樣:
“http://” +BucketName+ “.” +END_POINT + “/” + ObjectKey

1.EndPoint:Oss區域地址 Bucket:裡面的容器,放檔案(Object)的地方,這裡提一下bucketname
2.KeyObject:上傳檔案在oss中的名稱
3.Meta:上傳檔案的自我描述,算是標記,為name-value型別 data:這個就是字面上的意思
4.data:字面上的意思
5.STS:鑑權方式,關於這個方式的安全性以及涉及到的引數及其含義詳見OSS文件。
6.OssClient:看到Client其實你就能聯想到網路請求 
7.OSSAsyncTask:非同步任務,最終我就是差點當在這裡,
看漏一句話:該任務屬於安全上傳,適用於高併發執行...我居然寫到末尾把它硬生生寫成同步,捂臉...

資料處理

圖片上傳過程中,每張圖片的上傳進度,成功與失敗的資料返回處理,
上傳過程中可能會出現的問題:比如說正在上傳的圖片被使用者刪了之類的等等。

準備工作

1.下載jar包:aliyun-oss-sdk-android-2.3.0.jar、okhttp-3.x.x.jar 和 okio-1.x.x.jar,加入libs中
2.設定許可權:在manifests.xml中加入: 
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission
android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
<uses-permission
android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>

程式碼片段:

public class PictureUpload {
    private static PictureUpload pictureUpload;
    /**
     * 上傳client
     */
    OSS oss;
    /**
     * 上傳次數
     */
    int number;
    /**
     * 成功上傳(本地檔名作為key,阿里雲地址為value)
     */
    Map<String, Object> success = new HashMap<>();
    /**
     * 失敗上傳(返回失敗檔案的本地地址)
     */
    List<String> failure = new ArrayList<>();
    /**
     * 上傳回調
     */
    UploadListener uploadListener;
    /**
     * 上傳任務列表
     */
    List<OSSAsyncTask> ossAsyncTasks = new ArrayList<>();
    /**
     * 自動更新Token
     */
    OSSCredentialProvider credetialProvider = new OSSFederationCredentialProvider() {
        @Override
        public OSSFederationToken getFederationToken() {
            try {
                //阿里鑑權請求
                AliAuthRes res = new JsonDefaultRequest(AliAuthRes.class).POST("", null, SharePerefrence.getTokenKey());
                return new OSSFederationToken(res.getAccessKeyId(), res.getAccessKeySecret(), res.getSecurityToken(), res.getExpiration());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    };

    /**
     * 單列模式
     *
     * @return
     */
    public static PictureUpload getInstance() {
        if (pictureUpload == null) {
            pictureUpload = new PictureUpload();
        }
        return pictureUpload;
    }

    /**
     * 建構函式
     */
    public PictureUpload() {
        ClientConfiguration conf = new ClientConfiguration();
        conf.setConnectionTimeout(20 * 1000); // 連線超時,預設15秒
        conf.setSocketTimeout(20 * 1000); // socket超時,預設15秒
        conf.setMaxConcurrentRequest(9); // 最大併發請求書,預設5個
        conf.setMaxErrorRetry(3); // 失敗後最大重試次數,預設2次
        oss = new OSSClient(MyApplication.getInstance(), HttpConfig.END_POINT, credetialProvider, conf);
    }

    /**
     * 新增上傳任務
     *
     * @param paths
     * @param listener
     */
    public void setDatas(final List<String> paths, UploadListener listener) {
        this.uploadListener = listener;
        ossAsyncTasks.clear();
        number = 1;
        success.clear();
        failure.clear();
        for (String path : paths) {
            final File file = new File(path);
            /**
             * 阿里雲上檔名稱
             */
            String objectKey = UUID.randomUUID().toString() + "_" + SharePerefrence.getTokenKey() + ".jpg";
            /**
             * 使用者自定義引數
             */
            ObjectMetadata objectMetadata = new ObjectMetadata();
            objectMetadata.addUserMetadata("filePath", file.getPath());
            objectMetadata.addUserMetadata("fileName", file.getName());
            objectMetadata.addUserMetadata("objectKey", objectKey);


            PutObjectRequest put = new PutObjectRequest(HttpConfig.BUCKET_NAME, objectKey, file.getPath());
            put.setProgressCallback(new OSSProgressCallback<PutObjectRequest>() {
                @Override
                public void onProgress(PutObjectRequest putObjectRequest, long currentSize, long totalSize) {

                }
            });
            /**
             * 上傳任務
             */
            OSSAsyncTask task;
            task = oss.asyncPutObject(put, new OSSCompletedCallback<PutObjectRequest, PutObjectResult>() {
                @Override
                public void onSuccess(PutObjectRequest putObjectRequest, PutObjectResult putObjectResult) {
                    number++;
                    String aliPath = formAliPath(putObjectRequest);
                    success.put(putObjectRequest.getMetadata().getUserMetadata().get("fileName"), aliPath);
                    if (number == paths.size()) {
                        uploadListener.onUploadComplete(success, failure);
                    }
                }

                @Override
                public void onFailure(PutObjectRequest putObjectRequest, ClientException e, ServiceException e1) {
                    number++;
                    failure.add(putObjectRequest.getMetadata().getUserMetadata().get("filePath"));
                    if (number == paths.size()) {
                        uploadListener.onUploadComplete(success, failure);
                    }
                }
            });
            /**
             * 新增到上傳記錄
             */
            ossAsyncTasks.add(task);
        }
    }

    /**
     * 新增上傳任務
     * @param paths key:本地地址
     * @param listener
     */
    public void setDatas(final Map<String,Picture>paths, final UploadListener listener){
        this.uploadListener=listener;
        number=1;
        success.clear();
        failure.clear();
        Iterator iterator = paths.keySet().iterator();
        while (iterator.hasNext()){
            final String path= (String) iterator.next();
            final Picture picture=paths.get(path);
            File file=new File(path);

            String objectKey=UUID.randomUUID().toString()+"_"+SharePerefrence.getTokenKey()+".jpg";

            ObjectMetadata objectMetadata=new ObjectMetadata();
            objectMetadata.addUserMetadata("fileTag",picture.getTag());
            objectMetadata.addUserMetadata("fileName",file.getName());
            objectMetadata.addUserMetadata("filePath",file.getPath());
            objectMetadata.addUserMetadata("objectKey", objectKey);

            final PutObjectRequest put=new PutObjectRequest(HttpConfig.BUCKET_NAME,objectKey,file.getPath());
            put.setProgressCallback(new OSSProgressCallback<PutObjectRequest>() {
                @Override
                public void onProgress(PutObjectRequest putObjectRequest, long l, long l1) {
                    //上傳進度回撥
                }
            });

            OSSAsyncTask task;
            task=oss.asyncPutObject(put, new OSSCompletedCallback<PutObjectRequest, PutObjectResult>() {
                @Override
                public void onSuccess(PutObjectRequest putObjectRequest, PutObjectResult putObjectResult) {
                    number++;
                    String aliPath = formAliPath(putObjectRequest);
                    picture.setUrl(aliPath);
                    picture.setName(putObjectRequest.getMetadata().getUserMetadata().get("fileName"));
                    success.put(putObjectRequest.getMetadata().getUserMetadata().get("filePath"),picture);
                    if (number==paths.size()){
                        uploadListener.onUploadComplete(success,failure);
                    }
                }

                @Override
                public void onFailure(PutObjectRequest putObjectRequest, ClientException e, ServiceException e1) {
                    number++;
                    failure.add(putObjectRequest.getMetadata().getUserMetadata().get("filePath"));
                    if (number==paths.size()){
                        uploadListener.onUploadComplete(success,failure);
                    }
                }
            });
            ossAsyncTasks.add(task);
        }

    }

    public void cancleTasks() {
        for (OSSAsyncTask task : ossAsyncTasks) {
            if (task.isCompleted()) {

            }else {
                task.cancel();
            }
        }
    }


    /**
     * 拼接遠端訪問地址
     *
     * @param putObjectRequest
     * @return
     */
    private String formAliPath(PutObjectRequest putObjectRequest) {
        return "http://" + putObjectRequest.getBucketName() + "." + HttpConfig.END_POINT + "/" + putObjectRequest.getObjectKey();
    }

1.為了方便管理裡面的併發任務,我把所有任務都扔到一個list之中,
目前能力下只能想出這個辦法,裡面的setdatas()方法:
是因為專案中有的圖片上傳需要打標記區分。
2.上傳成功之後要求需要返給伺服器檔名和url,當然都有不同的要求。
3.關於進度更新ui問題:我還需要測試一下上傳的速率,
採用handler方法去解決。
4.突然想到到失敗後重新上傳的次數問題,
個人直覺是三次都失敗之後才會返回失敗結果,結果還是測了之後再說。

AliAuthRes程式碼:

public class AliAuthRes extends ResponseMsg implements BaseModel {
    /**
     * 鑑權key
     */
    String accessKeyId;
    /**
     * 鑑權secret
     */
    String accessKeySecret;
    /**
     * 鑑權token
     */
    String securityToken;
    /**
     * 鑑權時間戳
     */
    String expiration;

    public String getAccessKeyId() {
        return accessKeyId;
    }

    public void setAccessKeyId(String accessKeyId) {
        this.accessKeyId = accessKeyId;
    }

    public String getAccessKeySecret() {
        return accessKeySecret;
    }

    public void setAcessKeySecret(String accessKeySecret) {
        this.accessKeySecret = accessKeySecret;
    }

    public String getSecurityToken() {
        return securityToken;
    }

    public void setSecurityToken(String securityToken) {
        this.securityToken = securityToken;
    }

    public String getExpiration() {
        return expiration;
    }

    public void setExpiration(String expiration) {
        this.expiration = expiration;
    }

    @Override
    public <T extends BaseModel> T build() {
        AliAuthRes aliAuthRes=new AliAuthRes();
        aliAuthRes.setSuccess(true);
        return (T) aliAuthRes;
    }
}

UploadListener程式碼:

public interface UploadListener {
    /**
     * 上傳完成
     *
     * @param success
     * @param failure
     */
    void onUploadComplete(Map<String, Object> success, List<String> failure);
}

總結:期間走了不少彎路,事實證明真的要仔細看文件,
補充一點:上傳結束後,如果有圖片上傳失敗,使用者選擇不重試,記得刪除已經上傳成功的圖片,
畢竟是垃圾檔案,耗空間,而空間是要錢的。吸取教訓,以後用百度雲什麼的就可以參考了。