原由 

在項目裏有時候會碰到比如上傳文件相關的,一般都是後端提供個接口,然後我們上傳的時候後端再傳到阿里OSS或者其他服務商的對象存儲,然後把最終的url拿到存起來或者返回給前端,這種方式其實在上傳圖片的頻率不高的業務場景中可能並無大礙,但是如果你的項目是相冊類的,資源提供類的,總之就是有很頻繁的上傳文件的場景,可能服務器的帶寬就有點扛不住了,那麼有沒有更好的解決方案呢?服務端簽名,客户端直傳其實像阿里、騰訊、七牛等雲服務廠商都提供的有類似阿里的STS(Security Token Service)臨時訪問權限管理服務,這次就以阿里云為例,給大家介紹下如何使用STS Token,來實現在服務端簽名出STS token,然後提供給前端,讓前端直接用這個Token向阿里雲直傳文件服務端簽名,獲取到STS token我們這裏直接以Node.js為例,其他語言的服務可以在阿里雲的SDK參考(STS)文檔裏面找到,有Python、Java...首先我們需要先裝一個sts-sdk的npm包:@alicloud/sts-sdk(Nodejs version >= 8.5.0)

後端

npm install @alicloud/sts-sdk


然後我們在utils新建一個文件oss-sts-server.js,用來生成STS Token提供給前端使用(這裏只作為實例,後續大家可以自行封裝)

const StsClient = require('@alicloud/sts-sdk');

/**
 * 生成STStoken
 * @param accessKeyId AccessKey ID
 * @param accessKeySecret 從STS服務獲取的臨時訪問密鑰AccessKey Secret
 * @param roleArn 指定角色的ARN
 * @param roleSessionName 時臨時Token的會話名稱,自己指定用於標識你的用户,或者用於區分Token頒發給誰
 * @param durationSeconds token 有效事件,單位:秒
 * @param policy 指定的授權策略 默認為null
 * @return
 *   RequestId, 請求id
 *   AssumedRoleUser: {
 *     Arn, ${roleArn}/${roleSessionName}
 *     AssumedRoleId
 *   },
 *   Credentials: {
 *     SecurityToken, sts token
 *     AccessKeyId, accessKeyId
 *     AccessKeySecret, accessKeySecret
 *     Expiration 過期時間
 *   }
 */
export default function generateSTSToken(accessKeyId, accessKeySecret, roleArn, roleSessionName = 'external-username', durationSeconds = 3600, policy = null) {
  const sts = new StsClient({
    endpoint: 'sts.aliyuncs.com', // check this from sts console
    accessKeyId, // check this from aliyun console
    accessKeySecret // check this from aliyun console
  });
  return res = await sts.assumeRole(roleArn, roleSessionName, policy, durationSeconds);

這個generateSTSToken函數的幾個入參我來解釋一下,通常我們在用阿里雲或者騰訊雲的時候通常會開一個RAM賬户也是就子賬户,我們用子賬户登錄到阿里雲後台後,到對象存儲(OSS)控制枱頁面,找到安全令牌(子賬號授權),也就是下圖中標記的地方,點擊上面的前往RAM控制枱按鈕

阿里雲對象存儲OSS的前端直傳-demo_服務端

隨後點擊開始授權按鈕,之後你就可以得到accessKeyId、accessKeySecret、roleArn、roleSessionName還有默認的過期時間DurationsSeconds,如下圖所示,由於我之前授權過一次,所以會有左下角這個提示,這幾個參數一定到保存好,不要泄露,一旦泄露,請更改RAM賬户密碼,並重新生成,使之前的失效

阿里雲對象存儲OSS的前端直傳-demo_服務端_02

完善服務端提供的數據
這個時候其實已經拿到accessKeyId、accessKeySecret、stsToken、expiration這四個參數了

但是客户端還需要bucket:對象存儲的命名空間和region:bucket所在地域這兩個參數

這個bucket其實就是對應的使用的那個bucket,這個可以在阿里雲對象存儲頁面看到,有一個bucket列表,就是你要是用的那個bucket的名字region就是某一個bucket所在的地域,比如我這個就是oss-cn-beijing

阿里雲對象存儲OSS的前端直傳-demo_對象存儲_03


此時服務端的工作已經完結了,可以提供前端一個接口,通過鑑權之後,返回給前端這麼幾個參數,接下來,讓我們把舞台交給我們的前端~

{
  accessKeyId,
  accessKeySecret,
  stsToken,
  bucket,
  region,
  expiration
}

前端

前端er們來跟我 左邊一起畫個龍 在你右邊 畫一道彩虹(bushi)首先我們也新建一個oss-sts-client.js/ts,然後安裝一個ali-sdk/ali-oss的包,對了不支持IE10和之前的IE版本啊

npm install ali-oss --save


然後複製下面的內容到這個文件中,用js的同學可以把ts相關的代碼刪掉

// 這個是服務端提供給前端的一個請求接口,返回上面我們提到的幾個參數
import { getOssSTSToken } from "./request"; 
// @ts-ignore 忽略ts報錯,ali-oss趕緊提供@types包吧,文檔難看懂,庫也沒個文檔,你們文檔要是維護的好,我還用寫這個?我都不想吐槽……(bushi)
import OSS from 'ali-oss'

type OssStsType = {
  accessKeyId: string
  accessKeySecret: string
  stsToken: string
  expiration: number // 這個是前端計算出的還有多少秒token過期
  region: string
  bucket: string
}

/**
 * 獲取OSSClient
 * @param accessKeyId AccessKey ID
 * @param accessKeySecret 從STS服務獲取的臨時訪問密鑰AccessKey Secret
 * @param stsToken 從STS服務獲取的安全令牌(SecurityToken)
 * @param region Bucket所在地域
 * @param bucket Bucket名稱
 */
export default async function getOssClient () {
  const { code, data: params } = await getOssSTSToken();
  if (code !== 200) return false; // 如果請求出錯,在上游處理
  const client = new OSS({
    ...params,
    refreshSTSTokenInterval: params.expiration,
    // 刷新臨時訪問憑證的時間間隔,單位為毫秒。
    //(這個refreshSTSToken是文檔裏的,為了保險各位可以在每次上傳前先檢查一次過期沒有,不要依賴提供的這個方法)
    refreshSTSToken: async () => {
      const { code, data } = await getOssSTSToken(); // 過期後刷新token
      if (code === 200) {
        return data
      }
    },
  })
  return client
}

好了,到現在為止我們已經封裝好了這個前端需要在上傳文件的時候調用的方法了

前端維護STS Token
首先我們在前端頁面第一次上傳文件的時候,要調用這個getOssClient方法獲取到oss-client這個對象實例,才能用這個實例進行上傳操作,之後上傳的時候需要先判斷一下token過期了沒有,如果沒有過期,還是用這個實例進行上傳操作,如果過期了,重新生成一個實例!這裏我們就拿一個簡單的上傳小文件為例(大文件分片上傳,和上傳成功回調(需要後端同學提供回調地址) 可以自己去看文檔,我就不展開細説了)

async function uploadFileAction(file, client) {
  let newClient = client;
  // 偽代碼:
  // if (!newClient || token is expired) { // 如果是沒有實例對象或者token過期了就要重新生成
  //  newClient = await getOssClient(); // 調用上面我們封裝好的一個方法
  // }
  const filePath = 'xxx/xxx/' // 最中在bucket中的存放的路徑根據業務需要自行設置,文件名也是可以自行設置
  const { res, name, url } = await newClient.put(`${filePath}${file.name}`, file);
  if (res.status === 200) {
    // 這裏拿到上傳成功的文件的url
    return url
  }  
}


關於這裏oss-client的維護策略,各位就仁者見仁智者見智吧,方案很多,怎麼貼合業務怎麼來,但是不推薦往localStorage和sessionStorage和indexDB裏面存STS token等那些參數,你怎麼就確定你的用户不是一名前端er呢?

CORS的問題
還沒完啊,xdm 稍等一下,以上的都完了之後,我們在本地聯調的時候如果沒有開代理還是會有CORS的問題,這時候還是要去服務端去配置,找到跨域設置,進去創建一個規則,方法看你用什麼就勾上什麼,來源和允許Headers 直接給幹成*就完事了

阿里雲對象存儲OSS的前端直傳-demo_上傳_04

阿里雲對象存儲OSS的前端直傳-demo_服務端_05


案例-分片上傳

阿里雲對象存儲OSS的前端直傳-demo_服務端_06

注意

由於阿里雲oss(ali-oss )SDK的問題,有可能在項目中使用出現打包報錯的問題

阿里雲對象存儲OSS的前端直傳-demo_服務端_07

阿里雲對象存儲OSS的前端直傳-demo_對象存儲_08


編輯

可以直接使用cdn鏈接的方式



https://gosspublic.alicdn.com/aliyun-oss-sdk-6.21.0.min.js