原由
在專案裡有時候會碰到比如上傳檔案相關的,一般都是後端提供個介面,然後我們上傳的時候後端再傳到阿里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控制檯按鈕
隨後點選開始授權按鈕,之後你就可以得到accessKeyId
、accessKeySecret
、roleArn
、roleSessionName
還有預設的過期時間DurationsSeconds
,如下圖所示,由於我之前授權過一次,所以會有左下角這個提示,這幾個引數一定到儲存好,不要洩露,一旦洩露,請更改RAM賬戶密碼,並重新生成,使之前的失效
完善服務端提供的資料
這個時候其實已經拿到accessKeyId
、accessKeySecret
、stsToken
、expiration
這四個引數了
但是客戶端還需要bucket
:物件儲存的名稱空間和region
:bucket
所在地域這兩個引數
這個bucket
其實就是對應的使用的那個bucket
,這個可以在阿里雲物件儲存頁面看到,有一個bucket
列表,就是你要是用的那個bucket
的名字
region
就是某一個bucket
所在的地域,比如我這個就是oss-cn-beijing
此時服務端的工作已經完結了,可以提供前端一個介面,通過鑑權之後,返回給前端這麼幾個引數,接下來,讓我們把舞臺交給我們的前端~
{
accessKeyId,
accessKeySecret,
stsToken,
bucket,
region,
expiration
}
前端的工作
好了,我們的後端同學的工作已經完成了~
前端er們來跟我 左邊一起畫個龍 在你右邊 畫一道彩虹(bushi)
首先我們也新建一個oss-sts-client.js/ts
,然後安裝一個ali-sdk/ali-oss: Aliyun OSS(open storage service) JavaScript SDK for the browser and Node.js (github.com)的包,對了不支援IE10和之前的IE版本啊
npm install ali-oss --save
然後複製下面的內容到這個檔案中,用js的同學可以把ts相關的程式碼刪掉(趕緊換到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前端直傳的文章,如果有問題,歡迎在評論裡討論,如果能幫助到你,給我們個三連吧