牛逼需纳!保姆級(jí)教程:阿里云OSS文件上傳(分片上傳、斷點(diǎn)續(xù)傳)前后端實(shí)現(xiàn)(附源碼)

關(guān)于阿里云 OSS 的介紹請(qǐng)參考官方文檔:阿里云 OSS艺挪。

出于賬號(hào)安全的考慮不翩,前端使用 OSS 服務(wù)需要走臨時(shí)授權(quán),即拿一個(gè)臨時(shí)憑證(STS Token)去調(diào)用 aliyun-oss SDK麻裳。關(guān)于臨時(shí)授權(quán)請(qǐng)參考:RAM 和 STS 介紹口蝠,RAM 子賬號(hào),STS 臨時(shí)授權(quán)訪問 OSS津坑。

以 NodeJs 為例妙蔗,后端給前端頒發(fā)臨時(shí)憑證的實(shí)現(xiàn)可參考:Node STS 授權(quán)訪問

前端上傳文件到阿里云的相關(guān)操作可參考:瀏覽器端上傳文件

了解以上概念之后,接下來可以去阿里云 OSS 的控制臺(tái)進(jìn)行相關(guān)的設(shè)置了(前提是開通了 OSS 服務(wù))疆瑰。

阿里云 OSS 控制臺(tái)配置

1. 創(chuàng)建 Bucket

首先灭必,我們創(chuàng)建一個(gè) bucket,一個(gè)存儲(chǔ)文件的容器:

接著乃摹,我們需要給 bucket 設(shè)置跨域禁漓,這樣我們才能在網(wǎng)頁中調(diào)用 Aliyun OSS 服務(wù)器的接口:

2. 創(chuàng)建 RAM 用戶

接下來,前往 RAM 控制臺(tái)進(jìn)行子賬號(hào)和權(quán)限的配置孵睬。

首先播歼,我們創(chuàng)建一個(gè)用戶,并給該用戶分配調(diào)用 STS 服務(wù) AssumeRole 接口的權(quán)限,這樣待會(huì)兒后端就能以該用戶的身份給前端分配 STS 憑證了:

我們需要保存一下該用戶的 access key 和 access key secret秘狞,后端需要以此核實(shí)用戶的身份叭莫。

3. 創(chuàng)建 RAM 角色

該角色即有權(quán)限在前端調(diào)用 aliyun-oss SDK 上傳文件的用戶角色,例如我們創(chuàng)建一個(gè)只有上傳權(quán)限的角色烁试,命名為 uploader:

接下來我們需要給該角色分配權(quán)限雇初,可以通過創(chuàng)建一條權(quán)限策略并分配給角色,該權(quán)限策略里面只包含了上傳文件减响、分片上傳相關(guān)的權(quán)限:

策略具體內(nèi)容為:

{
  "Version": "1",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "oss:PutObject",
        "oss:InitiateMultipartUpload",
        "oss:UploadPart",
        "oss:UploadPartCopy",
        "oss:CompleteMultipartUpload",
        "oss:AbortMultipartUpload",
        "oss:ListMultipartUploads",
        "oss:ListParts"
      ],
      "Resource": [
        "acs:oss:*:*:mudontire-test",
        "acs:oss:*:*:mudontire-test/*"
      ]
    }
  ]
}

然后靖诗,把該策略賦予 uploader 角色:

到此,阿里云 OSS 后臺(tái)相關(guān)配置結(jié)束支示。接下來刊橘,我們來關(guān)注前后端的實(shí)現(xiàn)。

后端實(shí)現(xiàn)

由于是前端負(fù)責(zé)上傳颂鸿,所以后端的任務(wù)比較簡單促绵,就是提供一個(gè) STS Token 給前端。本文以 NodeJs 為例實(shí)現(xiàn)如下嘴纺。

1. 安裝 aliyun-oss SDK

npm install ali-oss

2. 生成 STS Token 并返回

const OSS = require('ali-oss');
const STS = OSS.STS;

const sts = new STS({
  accessKeyId: process.env.ALIYUN_OSS_RULE_ASSUMER_ACCESS_KEY,
  accessKeySecret: process.env.ALIYUN_OSS_RULE_ASSUMER_ACCESS_KEY_SECRET
});

async function getCredential(req, res, next) {
  try {
    const { credentials } = await sts.assumeRole(
      'acs:ram::1582938330607257:role/uploader',  // role arn
      null, // policy
      15 * 60, // expiration
      'web-client' // session name
    );
    req.result = credentials;
    next();
  } catch (err) {
    next(err);
  }
}

其中败晴,access key 和 access key secret 保存在.env文件中。sts.assumeRole()返回的即為 STS Token栽渴,方法接收的四個(gè)參數(shù)分別為:role arn, policy, expiration, session name尖坤。

Role arn 可以從 RAM 角色的詳情頁面獲取:

Policy 是自定義的策略熔萧,由于已經(jīng)為角色添加了權(quán)限策略糖驴,所以可以傳null。

Expiration 是 STS Token 的過期時(shí)間佛致,應(yīng)該在 15min ~ 60min 之間贮缕。當(dāng) Token 失效時(shí)前端需要重新獲取。

Session name 為自定義的一個(gè)會(huì)話名稱俺榆。

后端實(shí)現(xiàn)完成感昼!

前端實(shí)現(xiàn)

本文前端實(shí)現(xiàn)使用原生 JS,另外還有 ant design pro 的版本請(qǐng)參考 github 項(xiàng)目罐脊。

前端實(shí)現(xiàn)有幾個(gè)關(guān)鍵點(diǎn):

  1. 調(diào)用 aliyun-oss SDK 之前獲取 STS Token

  2. 定義上傳分片大小定嗓,如果文件小于分片大小則使用普通上傳,否則使用分片上傳

  3. 上傳過程中能展示上傳進(jìn)度

  4. 上傳過程中萍桌,如果 STS Token 快過期了宵溅,則先暫停上傳重新獲取 Token,接著進(jìn)行斷點(diǎn)續(xù)傳

  5. 支持手動(dòng)暫停上炎、續(xù)傳功能

  6. 上傳完成后返回文件對(duì)應(yīng)的下載地址

1. 引入 aliyun-oss SDK

參考 引入 aliyun-oss SDK

2. HTML

HTML 中包含文件選擇器恃逻,上傳、暫停、續(xù)傳按鈕寇损,狀態(tài)顯示:

<div>
  <input type="file" id='fileInput' multiple='true'>
  <button id="uploadBtn" onclick="upload()">Upload</button>
  <button id="stopBtn" onclick="stop()">Stop</button>
  <button id="resumeBtn" onclick="resume()">resume</button>
  <h2 id='status'></h2>
</div>

3. 定義變量

let credentials = null; // STS憑證
let ossClient = null; // oss客戶端實(shí)例
const fileInput = document.getElementById('fileInput'); // 文件選擇器
const status = document.getElementById('status'); // 狀態(tài)顯示元素
const bucket = 'mudontire-test'; // bucket名稱
const region = 'oss-cn-shanghai'; // oss服務(wù)區(qū)域名稱
const partSize = 1024 * 1024; // 每個(gè)分片大小(byte)
const parallel = 3; // 同時(shí)上傳的分片數(shù)
const checkpoints = {}; // 所有分片上傳文件的檢查點(diǎn)

4. 獲取 STS 憑證凸郑,創(chuàng)建 OSS Client

// 獲取STS Token
function getCredential() {
  return fetch('http://localhost:5050/api/upload/credential')
    .then(res => {
      return res.json()
    })
    .then(res => {
      credentials = res.result;
    })
    .catch(err => {
      console.error(err);
    });
}

// 創(chuàng)建OSS Client
async function initOSSClient() {
  const { AccessKeyId, AccessKeySecret, SecurityToken } = credentials;
  ossClient = new OSS({
    accessKeyId: AccessKeyId,
    accessKeySecret: AccessKeySecret,
    stsToken: SecurityToken,
    bucket,
    region
  });
}

5. 點(diǎn)擊上傳按鈕事件

async function upload() {
  status.innerText = 'Uploading';
  // 獲取STS Token
  await getCredential();
  const { files } = fileInput;
  const fileList = Array.from(files);
  const uploadTasks = fileList.forEach(file => {
    // 如果文件大學(xué)小于分片大小,使用普通上傳矛市,否則使用分片上傳
    if (file.size < partSize) {
      commonUpload(file);
    } else {
      multipartUpload(file);
    }
  });
}

6. 普通上傳

// 普通上傳
async function commonUpload(file) {
  if (!ossClient) {
    await initOSSClient();
  }
  const fileName = file.name;
  return ossClient.put(fileName, file).then(result => {
    console.log(`Common upload ${file.name} succeeded, result === `, result)
  }).catch(err => {
    console.log(`Common upload ${file.name} failed === `, err);
  });
}

7. 分片上傳

// 分片上傳
async function multipartUpload(file) {
  if (!ossClient) {
    await initOSSClient();
  }
  const fileName = file.name;
  return ossClient.multipartUpload(fileName, file, {
    parallel,
    partSize,
    progress: onMultipartUploadProgress
  }).then(result => {
    // 生成文件下載地址
    const url = `http://${bucket}.${region}.aliyuncs.com/${fileName}`;
    console.log(`Multipart upload ${file.name} succeeded, url === `, url)
  }).catch(err => {
    console.log(`Multipart upload ${file.name} failed === `, err);
  });
}

8. 斷點(diǎn)續(xù)傳

// 斷點(diǎn)續(xù)傳
async function resumeMultipartUpload() {
  Object.values(checkpoints).forEach((checkpoint) => {
    const { uploadId, file, name } = checkpoint;
    ossClient.multipartUpload(uploadId, file, {
      parallel,
      partSize,
      progress: onMultipartUploadProgress,
      checkpoint
    }).then(result => {
      console.log('before delete checkpoints === ', checkpoints);
      delete checkpoints[checkpoint.uploadId];
      console.log('after delete checkpoints === ', checkpoints);
      const url = `http://${bucket}.${region}.aliyuncs.com/${name}`;
      console.log(`Resume multipart upload ${file.name} succeeded, url === `, url)
    }).catch(err => {
      console.log('Resume multipart upload failed === ', err);
    });
  });
}

9. 分片上傳進(jìn)度

在 progress 回調(diào)中我們可以判斷 STS Token 是否快過期了芙沥,如果快過期了則先取消上傳獲取新 Token 后在從之前的斷點(diǎn)開始續(xù)傳。

// 分片上傳進(jìn)度改變回調(diào)
async function onMultipartUploadProgress(progress, checkpoint) {
  console.log(`${checkpoint.file.name} 上傳進(jìn)度 ${progress}`);
  checkpoints[checkpoint.uploadId] = checkpoint;
  // 判斷STS Token是否將要過期浊吏,過期則重新獲取
  const { Expiration } = credentials;
  const timegap = 1;
  if (Expiration && moment(Expiration).subtract(timegap, 'minute').isBefore(moment())) {
    console.log(`STS token will expire in ${timegap} minutes而昨,uploading will pause and resume after getting new STS token`);
    if (ossClient) {
      ossClient.cancel();
    }
    await getCredential();
    await resumeMultipartUpload();
  }
}

10. 暫停、續(xù)傳按鈕點(diǎn)擊事件

// 暫停上傳
function stop() {
  status.innerText = 'Stopping';
  if (ossClient) ossClient.cancel();
}

// 續(xù)傳
function resume() {
  status.innerText = 'Resuming';
  if (ossClient) resumeMultipartUpload();
}

項(xiàng)目地址:https://github.com/MudOnTire/aliyun-oss-upload-demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末卿捎,一起剝皮案震驚了整個(gè)濱河市配紫,隨后出現(xiàn)的幾起案子径密,更是在濱河造成了極大的恐慌午阵,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,464評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件享扔,死亡現(xiàn)場離奇詭異底桂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)惧眠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門籽懦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人氛魁,你說我怎么就攤上這事暮顺。” “怎么了秀存?”我有些...
    開封第一講書人閱讀 169,078評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵捶码,是天一觀的道長。 經(jīng)常有香客問我或链,道長惫恼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,979評(píng)論 1 299
  • 正文 為了忘掉前任澳盐,我火速辦了婚禮祈纯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘叼耙。我一直安慰自己腕窥,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評(píng)論 6 398
  • 文/花漫 我一把揭開白布筛婉。 她就那樣靜靜地躺著簇爆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上冕碟,一...
    開封第一講書人閱讀 52,584評(píng)論 1 312
  • 那天拦惋,我揣著相機(jī)與錄音,去河邊找鬼安寺。 笑死厕妖,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的挑庶。 我是一名探鬼主播言秸,決...
    沈念sama閱讀 41,085評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼迎捺!你這毒婦竟也來了举畸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,023評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤凳枝,失蹤者是張志新(化名)和其女友劉穎抄沮,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體岖瑰,經(jīng)...
    沈念sama閱讀 46,555評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡叛买,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蹋订。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片率挣。...
    茶點(diǎn)故事閱讀 40,769評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖露戒,靈堂內(nèi)的尸體忽然破棺而出椒功,到底是詐尸還是另有隱情,我是刑警寧澤智什,帶...
    沈念sama閱讀 36,439評(píng)論 5 351
  • 正文 年R本政府宣布动漾,位于F島的核電站,受9級(jí)特大地震影響撩鹿,放射性物質(zhì)發(fā)生泄漏谦炬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評(píng)論 3 335
  • 文/蒙蒙 一节沦、第九天 我趴在偏房一處隱蔽的房頂上張望键思。 院中可真熱鬧,春花似錦甫贯、人聲如沸吼鳞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赔桌。三九已至供炎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間疾党,已是汗流浹背音诫。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評(píng)論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留雪位,地道東北人竭钝。 一個(gè)月前我還...
    沈念sama閱讀 49,191評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像雹洗,于是被迫代替她去往敵國和親香罐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容