阿里雲OSS文件上傳(分片上傳、斷點續傳)先後端實現

關於阿里雲 OSS 的介紹請參考官方文檔:阿里雲 OSShtml

出於帳號安全的考慮,前端使用 OSS 服務須要走臨時受權,即拿一個臨時憑證(STS Token)去調用 aliyun-oss SDK。關於臨時受權請參考:RAM 和 STS 介紹RAM 子帳號STS 臨時受權訪問 OSS前端

以 NodeJs 爲例,後端給前端頒發臨時憑證的實現可參考:Node STS 受權訪問git

前端上傳文件到阿里雲的相關操做可參考:瀏覽器端上傳文件github

瞭解以上概念以後,接下來能夠去阿里雲 OSS 的控制檯進行相關的設置了(前提是開通了 OSS 服務)。web

阿里雲 OSS 控制檯配置

1. 建立 Bucket

首先,咱們建立一個 bucket,一個存儲文件的容器:npm

add bucket

接着,咱們須要給 bucket 設置跨域,這樣咱們才能在網頁中調用 Aliyun OSS 服務器的接口:json

bucket set cros

2. 建立 RAM 用戶

接下來,前往 RAM 控制檯進行子帳號和權限的配置。後端

首先,咱們建立一個用戶,並給該用戶分配調用 STS 服務 AssumeRole 接口的權限,這樣待會兒後端就能以該用戶的身份給前端分配 STS 憑證了:api

ram use sts assume role

咱們須要保存一下該用戶的 access key 和 access key secret,後端須要以此覈實用戶的身份。跨域

ram user access key

3. 建立 RAM 角色

該角色即有權限在前端調用 aliyun-oss SDK 上傳文件的用戶角色,例如咱們建立一個只有上傳權限的角色,命名爲 uploader:

ram role

接下來咱們須要給該角色分配權限,能夠經過建立一條權限策略並分配給角色,該權限策略裏面只包含了上傳文件、分片上傳相關的權限:

ram role priorities

策略具體內容爲:

{
  "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 角色:

ramrole set strategy

到此,阿里雲 OSS 後臺相關配置結束。接下來,咱們來關注先後端的實現。

後端實現

因爲是前端負責上傳,因此後端的任務比較簡單,就是提供一個 STS Token 給前端。本文以 NodeJs 爲例實現以下。

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,方法接收的四個參數分別爲:role arn, policy, expiration, session name

Role arn 能夠從 RAM 角色的詳情頁面獲取:

role arn

Policy 是自定義的策略,因爲已經爲角色添加了權限策略,因此能夠傳null

Expiration 是 STS Token 的過時時間,應該在 15min ~ 60min 之間。當 Token 失效時前端須要從新獲取。

Session name 爲自定義的一個會話名稱。

後端實現完成!

前端實現

<!-- 本文的前端實現基於 React + Ant design pro。最終效果以下:

preview

針對 Andt 的 Upload控件進行了簡單的封裝,當添加文件的時候不會當即上傳,而要等到點擊「提交」按鈕時再上傳。 -->

本文前端實現使用原生 JS,另外還有 ant design pro 的版本請參考 github 項目。

前端實現有幾個關鍵點:

  1. 調用 aliyun-oss SDK 以前獲取 STS Token
  2. 定義上傳分片大小,若是文件小於分片大小則使用普通上傳,不然使用分片上傳
  3. 上傳過程當中能展現上傳進度
  4. 上傳過程當中,若是 STS Token 快過時了,則先暫停上傳從新獲取 Token,接着進行斷點續傳
  5. 支持手動暫停、續傳功能
  6. 上傳完成後返回文件對應的下載地址

1. 引入 aliyun-oss SDK

參考 引入 aliyun-oss SDK

2. HTML

HTML 中包含文件選擇器,上傳、暫停、續傳按鈕,狀態顯示:

<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客戶端實例
const fileInput = document.getElementById('fileInput'); // 文件選擇器
const status = document.getElementById('status'); // 狀態顯示元素
const bucket = 'mudontire-test'; // bucket名稱
const region = 'oss-cn-shanghai'; // oss服務區域名稱
const partSize = 1024 * 1024; // 每一個分片大小(byte)
const parallel = 3; // 同時上傳的分片數
const checkpoints = {}; // 全部分片上傳文件的檢查點

4. 獲取 STS 憑證,建立 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);
    });
}

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

5. 點擊上傳按鈕事件

async function upload() {
  status.innerText = 'Uploading';
  // 獲取STS Token
  await getCredential();
  const { files } = fileInput;
  const fileList = Array.from(files);
  const uploadTasks = fileList.forEach(file => {
    // 若是文件大學小於分片大小,使用普通上傳,不然使用分片上傳
    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);
  });
}

9. 斷點續傳

// 斷點續傳
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);
    });
  });
}

10. 分片上傳進度

在 progress 回調中咱們能夠判斷 STS Token 是否快過時了,若是快過時了則先取消上傳獲取新 Token 後在從以前的斷點開始續傳。

// 分片上傳進度改變回調
async function onMultipartUploadProgress(progress, checkpoint) {
  console.log(`${checkpoint.file.name} 上傳進度 ${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();
  }
}

11. 暫停、續傳按鈕點擊事件

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

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

github 示例項目

項目地址:https://github.com/MudOnTire/...。若是對你們有幫助,star一下吧。

相關文章
相關標籤/搜索