1. 程式人生 > >如何簡單地在瀏覽器中使用阿里雲的檔案上傳功能?

如何簡單地在瀏覽器中使用阿里雲的檔案上傳功能?

前言

我開通了一個微信公共號“王和陽的航海日誌”,在上面記錄著自己的學習、思考、實踐和成長的過程,歡迎關注、交流和拍磚。

公共號二維碼

正文

明哥從辦公室裡發出一聲怒吼:“這阿里雲OSS服務的上傳功能也太難用了,沒有批量重新命名功能,難道這幾百個檔案要我一個個手動重新命名??!!”然後我和明哥就花了半天時間研究了下阿里雲 SDK for JavaScript,順利把檔案上傳功能整合到咱們的網站裡了。
首先明確這次的需求:

  1. 能在後臺進行檔案的上傳(單個檔案大概在20M左右)
  2. 自動根據傳入的ID和資料夾名對檔案進行重新命名
  3. 智慧識別檔案的字尾名,減少傳錯的可能性
  4. 顯示上傳進度且對上傳的結果進行提示

接著去看一看阿里開放雲端儲存OSS的介紹和基礎概念 在申請了服務之後,阿里會提供一個bucket(可以理解為在阿里雲上你上傳的檔案所在的資料夾名),同時擁有有對應的 Access Key ID & Access Key Secret (用於加密),阿里雲把每一個檔案當做Object對待,我們在瀏覽器端使用POST方式來上傳檔案,對於用Node.js上傳檔案,阿里在GitHub上提供了一個例子,基本上參考這個例子就可以寫出完整的上傳功能了。地址如下:https://github.com/aliyun-UED
首先我們要用npm安裝阿里雲SDK:

npm install aliyun-sdk

接下來需要新增aliyun-sdk.min和oss-js-upload這兩個js檔案,前者能在瀏覽器端呼叫aliyun sdk,後者對檔案上傳進行了一系列的封裝,在這裡我們對oss-js-upload.js檔案進行了改造,使其增加了顯示上傳進度的功能,這是本文的重點所在,具體改動詳見原始碼檔案,一開始根本不知道如何顯示進度,但是在除錯GitHub上阿里雲團隊提供的例子時,發現後臺會打印出諸如 “Completed part 1”、”Completed part 2” 的資訊,聯絡到Http POST請求的特徵(一次最多傳2M左右的檔案),所以說在oss-js-upload中就是使用Mutilpart方法進行上傳的,那麼我們只要獲取到各個塊的上傳進度,除以總的塊數,不就可以顯示上傳進度了麼?關鍵的思路就在這裡!想通了這一點,那麼接下來就好辦多了!

最後貼上原始碼供大家參考,重點需要注意的地方我已經加了標註,如果再有什麼不懂的可以在評論區給我留言。

upload.js

/**
 * Created by Young on 2015/8/28.
 * [email protected]
 */

//阿里雲提供功能的上傳類
var ossUpload = new OssUpload({
  bucket: 'keju-video',
  // 選擇杭州的 oss 例項所在地區選擇填入,這裡選的是
  endpoint: 'http://oss-cn-hangzhou.aliyuncs.com',
  // 如果檔案大於 chunkSize 則分塊上傳, chunkSize 不能小於 100KB 即 102400
  chunkSize: 1048576,
  // 分塊上傳的併發數
  concurrency: 5,
  aliyunCredential: {
    "accessKeyId": "在阿里雲申請的accessKeyId",
    "secretAccessKey": "在阿里雲申請的secretAccessKey"
  },
  stsToken: null
});
var videoMp4;
var folderName;
var lessonId = "將要傳的檔案的ID以某種方式傳入即可"; //獲取要上傳視訊的ID,便於填寫檔名

//檢查視訊檔案的字尾名
String.prototype.endsWith = function (suffix) {
  return !!this.match(suffix + "$");
};

var uploadVideo = function (type, file, fnProgress, fnSuccess, fnFail) {

  var video = file;
  var progress = 0;

  ossUpload.upload({
    // 必傳引數, 需要上傳的檔案物件
    file: video,
    // 必傳引數, 檔案上傳到 oss 後的名稱, 包含路徑
    key: type + '/' + folderName + '/' + lessonId + '.' + type,
    // 上傳失敗後重試次數
    maxRetry: 3,
    headers: {
      'CacheControl': 'public',
      'Expires': '',
      'ContentEncoding': '',
      'ContentDisposition': '',
      // oss 支援的 header, 目前僅支援 x-oss-server-side-encryption
      'ServerSideEncryption': ''
    },
    // 檔案上傳失敗後呼叫, 可選引數
    onerror: function (evt) {
      console.log("error");
      console.log(evt);
      fnFail();
    },
    // 檔案上傳時的進度,每完成一片更新一次
    onprogress: function (loaded, total) {
      console.log(loaded, total, (loaded / total * 100).toFixed(1) + "%");
      progress = (loaded / total * 100).toFixed(1) + "%";

      fnProgress(progress);
    },
    // 檔案上傳成功呼叫, 可選引數
    oncomplete: function (res) {
      console.log("success");
      console.log(res);
      fnSuccess();
    }
  });
};

//選擇上傳的MP4檔案
$("#btnSelectMp4").click(function () {
  $("#mp4File")
    .trigger("click")
    .change(function (evt) {
      //得到上傳的檔案物件
      videoMp4 = evt.target.files[0];
      folderName = $("#inputFolderName").val();
      if (videoMp4.name.endsWith('mp4')) {
        $("#btnUploadMp4").show();
      } else {
        return $("#tipNotMp4").show();
      }
    });
});

//點選開始上傳Mp4檔案
$("#btnUploadMp4").click(function () {
  var btnUploadMp4 = $("#btnUploadMp4");
  btnUploadMp4.prop('disabled', true);
  btnUploadMp4.text("正在上傳");
  uploadVideo('mp4', videoMp4, function (progress) {
    console.log("the progress is " + progress);
    btnUploadMp4.text(progress);
  }, function () {
    btnUploadMp4.prop('disabled', false);
    btnUploadMp4.hide();
    btnUploadMp4.text('上傳.mp4檔案');
    $("#uploadMp4Succeed").show();
    $("#btnSelectMp4").show();
    $('#mp4Address').val('');
    $("#mp4File").replaceWith($("#mp4File").val('').clone(true));
  }, function () {
      //上傳如果失敗,則再次顯示按鈕讓使用者重新上傳
    btnUploadMp4.prop('disabled', false);
    btnUploadMp4.text('上傳.mp4檔案');
    btnUploadMp4.hide();
    $("#uploadMp4Failed").show();
    $("#btnSelectMp4").show();
    $('#mp4Address').val('');
    $("#mp4File").replaceWith($("#mp4File").val('').clone(true));
  });
});

oss-js-upload.js

'use strict';
(function () {

  var detectIEVersion = function () {
    var v = 4,
      div = document.createElement('div'),
      all = div.getElementsByTagName('i');
    while (
      div.innerHTML = '<!--[if gt IE ' + v + ']><i></i><![endif]-->',
        all[0]
      ) {
      v++;
    }
    return v > 4 ? v : false;
  };

  var _extend = function (dst, src) {
    for (var i in src) {
      if (Object.prototype.hasOwnProperty.call(src, i) && src[i]) {
        dst[i] = src[i];
      }
    }
  };

  function OssUpload(config) {
    if (!config) {
      // console.log('需要 config');
      return;
    }
    this._config = {
      chunkSize: 1048576    // 1MB
    };

    if (this._config.chunkSize && this._config.chunkSize < 102400) {
      // console.log('chunkSize 不能小於 100KB');
      return;
    }

    _extend(this._config, config);

    if (!this._config.aliyunCredential && !this._config.stsToken) {
      // console.log('需要 stsToken');
      return;
    }

    if (!this._config.endpoint) {
      // console.log('需要 endpoint');
      return;
    }

    var ALY = window.ALY;
    if (this._config.stsToken) {
      this.oss = new ALY.OSS({
        accessKeyId: this._config.stsToken.Credentials.AccessKeyId,
        secretAccessKey: this._config.stsToken.Credentials.AccessKeySecret,
        securityToken: this._config.stsToken.Credentials.SecurityToken,
        endpoint: this._config.endpoint,
        apiVersion: '2013-10-15'
      });
    }
    else {
      this.oss = new ALY.OSS({
        accessKeyId: this._config.aliyunCredential.accessKeyId,
        secretAccessKey: this._config.aliyunCredential.secretAccessKey,
        endpoint: this._config.endpoint,
        apiVersion: '2013-10-15'
      });
    }

    var arr = this._config.endpoint.split('://');
    if (arr.length < 2) {
      // console.log('endpoint 格式錯誤');
      return;
    }
    this._config.endpoint = {
      protocol: arr[0],
      host: arr[1]
    }

  }

  OssUpload.prototype.upload = function (options) {
    if (!options) {
      if (typeof options.onerror == 'function') {
        options.onerror('需要 options');
      }
      return;
    }

    if (!options.file) {
      if (typeof options.onerror == 'function') {
        options.onerror('需要 file');
      }
      return;
    }
    var file = options.file;

    if (!options.key) {
      if (typeof options.onerror == 'function') {
        options.onerror('需要 key');
      }
      return;
    }
    // 去掉 key 開頭的 /
    options.key.replace(new RegExp("^\/"), '');

    var self = this;

    var readFile = function (callback) {
      var result = {
        chunksHash: {},
        chunks: []
      };
      var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
      var chunkSize = self._config.chunkSize;
      var chunksNum = Math.ceil(file.size / chunkSize);
      var currentChunk = 0;

      var frOnload = function (e) {
        result.chunks[currentChunk] = e.target.result;
        currentChunk++;
        if (currentChunk < chunksNum) {
          loadNext();
        }
        else {
          result.file_size = file.size;
          callback(null, result);
        }
      };
      var frOnerror = function () {
        console.error("讀取檔案失敗");
        if (typeof options.onerror == 'function') {
          options.onerror("讀取檔案失敗");
        }
      };

      function loadNext() {
        var fileReader = new FileReader();
        fileReader.onload = frOnload;
        fileReader.onerror = frOnerror;

        var start = currentChunk * chunkSize,
          end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
        var blobPacket = blobSlice.call(file, start, end);
        fileReader.readAsArrayBuffer(blobPacket);
      }

      loadNext();
    };

    var uploadSingle = function (result, callback) {
      var params = {
        Bucket: self._config.bucket,
        Key: options.key,
        Body: result.chunks[0],
        ContentType: file.type || ''
      };
      _extend(params, options.headers);

      self.oss.putObject(params, callback);
    };

    var uploadMultipart = function (result, callback) {
      var maxUploadTries = options.maxRetry || 3;
      var uploadId;
      var loadedNum = 0;
      var latestUploadNum = -1;
      var concurrency = 0;

      var multipartMap = {
        Parts: []
      };

     ``` javascript 
        //這裡使用arguments讀取傳入的onProgres函式並回調
      if(3===arguments.length){
        var fnProgress = arguments[2];
      }
     ```

      var init = function () {
        var params = {
          Bucket: self._config.bucket,
          Key: options.key,
          ContentType: file.type || ''
        };
        _extend(params, options.headers);

        self.oss.createMultipartUpload(params,
          function (mpErr, res) {
            if (mpErr) {
              // console.log('Error!', mpErr);
              callback(mpErr);
              return;
            }

            // console.log("Got upload ID", res.UploadId);
            uploadId = res.UploadId;

            uploadPart(0);
          });
      };

      var uploadPart = function (partNum) {
        if(partNum >= result.chunks.length) {
          return;
        }

        concurrency++;
        if(latestUploadNum < partNum) {
          latestUploadNum = partNum;
        }
        if(concurrency < self._config.concurrency && (partNum < (result.chunks.length - 1))) {
          uploadPart(partNum + 1);
        }
        var partParams = {
          Body: result.chunks[partNum],
          Bucket: self._config.bucket,
          Key: options.key,
          PartNumber: String(partNum + 1),
          UploadId: uploadId
        };

        var tryNum = 1;

        var doUpload = function () {
          self.oss.uploadPart(partParams, function (multiErr, mData) {
            if (multiErr) {
              // console.log('multiErr, upload part error:', multiErr);
              if (tryNum > maxUploadTries) {
                console.log('上傳分片失敗: #', partParams.PartNumber);
                callback(multiErr);
              }
              else {
                console.log('重新上傳分片: #', partParams.PartNumber);
                tryNum++;
                doUpload();
              }
              return;
            }
            // console.log(mData);
            concurrency--;

            multipartMap.Parts[partNum] = {
              ETag: mData.ETag,
              PartNumber: partNum + 1
            };

            console.log("Completed part", partNum + 1);
            //console.log('mData', mData);
            loadedNum++;
            ``` JavaScript
            //回撥upload.js檔案中寫的onprogress函式,顯示進度
            if("function" === typeof fnProgress){
              fnProgress(loadedNum, result.chunks.length);
            }
            ```
            if (loadedNum == result.chunks.length) {
              complete();
            }
            else {
              uploadPart(latestUploadNum + 1);
            }
          });
        };

        doUpload();

      };

      var complete = function () {
        // console.log("Completing upload...");

        var doneParams = {
          Bucket: self._config.bucket,
          Key: options.key,
          CompleteMultipartUpload: multipartMap,
          UploadId: uploadId
        };

        self.oss.completeMultipartUpload(doneParams, callback);
      };

      init();
    };

    readFile(function (err, result) {
      var callback = function (err, res) {
        if (err) {
          if (typeof options.onerror == 'function') {
            options.onerror(err);
          }
          return;
        }

        if (typeof options.oncomplete == 'function') {
          options.oncomplete(res);
        }
      };

      if (result.chunks.length == 1) {
        uploadSingle(result, callback)
      }
      else {
      //若檔案大於2MB,使用分塊上傳,顯示進度,若小於2MB,則不顯示進度直接上傳
        if('function' === typeof options.onprogress){
          uploadMultipart(result, callback, options.onprogress);
        } else {
          uploadMultipart(result, callback);
        }
      }
    });

  };

  window.OssUpload = OssUpload;

})();

相關推薦

如何簡單瀏覽器使用阿里檔案功能

前言 我開通了一個微信公共號“王和陽的航海日誌”,在上面記錄著自己的學習、思考、實踐和成長的過程,歡迎關注、交流和拍磚。 正文 明哥從辦公室裡發出一聲怒吼:“這阿里雲OSS服務的上傳功能也太難用了,沒有批量重新命名功能,難道這幾百個檔案要我一個個手

阿里伺服器 ---- 下載檔案

1.xshell 使用xshell來操作服務非常方便,傳檔案也比較方便。 就是使用rz(上傳),sz(下載) 首先,伺服器要安裝了rz,sz   伺服器執行  yum install lrzsz 2.兩個伺服器之間 傳輸檔案  使用scp命令

阿里 javascript檔案(圖片、視訊、壓縮包等檔案)到 物件儲存 OSS ,返回檔案、圖片、音訊、視訊等URL路徑

目的:前端上傳檔案(圖片、視訊、音訊等)到阿里雲伺服器裡面,並且獲得上傳檔案的URL路徑 前提:首先要買一個阿里雲伺服器,自己百度不會; 第一步:登入阿里雲賬號,點選管理控制檯-->物件儲存 OSS 第二步:新建儲存空間(圖一、圖二) (圖一) (圖二

使用阿里OSS下載專案檔案

1、登入阿里雲開通阿里雲OSS,根據官網說明進行操作獲取需要的配置引數 'id'=> '你的accessKeyId', 'key'=> '你的accessKeySecret', 'host' => 'oss-cn-beijing.aliyunc

tp5呼叫阿里oss檔案

推薦使用composer方式 composer方式安裝SDK的步驟如下: 在專案的根目錄執行composer require aliyuncs/oss-sdk-php,或者在composer.j

怎麼簡便去掉html難看的檔案按鈕並實現圖片預覽功能

問題描述 通常的檔案上傳按鈕是這樣的: 選擇了檔案過後是這樣的: 很顯然,這樣的按鈕並不好看。 解決方法 用一個label標籤來裝載樣式,其for屬性指向type=file的inp

阿里OSS 檔案 刪除檔案自封裝 —python

<pre name="code" class="python"># -*- coding: utf-8 -*- """ wrapper of oss2. """ import oss2 from PwLogging import PwLogging cl

SpringBoot基於阿里OSS檔案

一:需求背景.       Web系統開發中,檔案上傳是非常常見的功能,本來也沒啥好說的,就是通過IO流將檔案寫到另外一個地方,這個地方可以是       1. 專案的目錄中的某個資料夾.      2. 本地碟符的某個檔案下.      3. 雲服務OSS裡面.例如七牛雲,

阿里oss本地檔案到伺服器例項

Java 專案開發中阿里雲 oss上傳本地到伺服器以及下載功能實現例項。 AliyunOSSUtil類: public class AliyunOSSUtil { /** * 該示例程式碼展示瞭如果在OSS中建立和刪除一個Bucket,以及如何上傳和下載一個檔案。 *

通過阿里路徑獲取EXCEL檔案進行資料讀取

通過需求,要對上傳的EXCEL檔案進行資料讀取併入庫。由於EXCEL是由前端直傳到阿里雲,所以只有一個上傳後的檔案路徑。對於先下載在讀取在刪除的方式覺得十分耗時且無用,所以試圖直接根據URL地址來讀取流,生成EXCEL物件並讀取資料。程式碼如下:public static v

阿里OSS檔案工具類

/**    *  *  *  阿里雲OSS上傳檔案工具 *   *  支援普通檔案上傳,限制大小檔案上傳,限制大小圖片上傳 *    *   @version1.0 */public class AliyunOssUtil {private Logger logger =

阿里本地資料夾內所有內容程式碼

public static void main(String[] args){ // Endpoint以杭州為例,其它Region請按實際情況填寫。 String endpoint = ""; // 阿里雲主賬號AccessKey擁有所有API的訪問許可權,風險很高。強烈建議您建立並使用RA

webupload外掛,單個檔案處理

上傳外掛如何使用,我這裡就不多說了,網上大把教程。今天我要講的是如何限制上傳一個檔案,以及當檔案選擇錯誤是,再重新選擇檔案導致的問題。 預設給上傳外掛支援多檔案上傳,但是我們有很多需求是之上傳單個檔案,下面就來一起學習學習吧! 外掛html <div class="form-g

tp框架 阿里oss ,刪除,判斷是不是存在

composer require aliyuncs/oss-sdk-php  配置檔案如下 'ALIOSS_CONFIG' => array( 'KEY_ID' => '', 'KEY_SECRET' => '',

Django - 實現簡單檔案功能

前端 <form action="" method="post" enctype="multipart/form-data"> {#<form action="" method="post" enctype="application/x-www-form-urlenc

Atitit 檔案功能的實現 圖片 視訊 目錄 1. 原理 1 1.1. http post編碼 multipart / form-data 1 1.2. 臨時檔案模式 最簡單 2 1.3

Atitit 檔案上傳功能的實現 圖片 視訊   目錄 1. 上傳原理 1 1.1. http post編碼 multipart / form-data 1 1.2. 臨時檔案模式  最簡單 2 1.3. 位元組陣列模式  簡單 2

ASP.NET簡單實現APP使用者個人頭像和裁剪

  最近有個微信專案的使用者個人中心模組中,客戶要求使用者頭像不僅僅只是上傳圖片,還需要能對圖片進行裁剪。考慮到flash在IOS和Android上的相容性問題,於是想著能從js這塊入手,在網上發現了devotion博主寫的《適應各瀏覽器圖片裁剪無重新整理上傳js外掛》文章,從中受

Java專案整合阿里OSS圖片

工具類: import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import org.slf4j.Lo

關於阿里oss圖片之後會被旋轉90度的解決辦法

問題描述:正常的圖片前端上傳到oss成功之後的資源地址。在html上引用的時候被旋轉了90度oss資源地址直接在瀏覽器開啟就不會旋轉問題原因:某些手機拍攝出來的照片可能帶有旋轉引數(存放在照片exif資訊裡面)。而oss資源上傳之後會預設自適應方向所以在img標籤中圖片就被旋轉了90度。 解決辦法:阿里雲給

阿里oss與下載

public class OSSClientUtil { //填入相應字串 private static String endpoint = ""; private static String accessKeyId = ""; private static Str