最近項目里需要大量上傳文件,考慮使用阿里雲提供的對象存儲,並采用前端直傳方案。這里並不想介紹如何使用,想知道如何使用,可以直接參考官方文檔,也沒啥特多介紹的,所以這里主要是記錄一下使用中的注意點。
阿里雲上傳文件官方文檔:阿里雲上傳文件前端直傳方案文檔
1、使用oss對象
在代碼中使用OSS
對象:
<script type="text/javascript"> let client = new OSS({ region: '<oss region>', //雲賬號AccessKey有所有API訪問權限,建議遵循阿里雲安全最佳實踐,創建並使用STS方式來進行API訪問
accessKeyId: '<Your accessKeyId(STS)>', accessKeySecret: '<Your accessKeySecret(STS)>', stsToken: '<Your securityToken(STS)>', bucket: '<Your bucket name>' }); </script>
export async function initOss (isPublic) { let _sts = await getStsToken() let _bucket = getBucketName(isPublic) let _ossClient = new OSS({ region: 'oss-cn-beijing', bucket: _bucket, accessKeyId: _sts.accessKeyId, accessKeySecret: _sts.accessKeySecret, stsToken: _sts.securityToken
}) return _ossClient }
通過 initOss 創建 OSS對象。這里有2個需要注意的點:
一個是什么情況使用什么桶(公開桶、私有桶),還涉及到根據不同環境取不同桶的問題。
第二個就是accessKeyId、accessKeySecret這種密鑰類型的東西直接存儲在前端是有安全問題的,建議是使用STS進行訪問。
2、根據不同環境取不同桶
export function getBucketName (isPublic) { let _mode = process.env.PATH_TYPE let _bucket = ''
if (isPublic) { _bucket = _mode === 'production' ? 'oss-***prod-public' : 'oss-***test-public' } else { _bucket = _mode === 'production' ? 'oss-***prod-private' : 'oss-***test-private' } return _bucket }
其中 process.env.PATH_TYPE 就是通過 cross-env build不同字段打包設置的環境變量
注意:桶均要設置允許跨域才行。
3、ali-oss獲取臨時憑證
// ali-oss獲取臨時憑證
export async function getStsToken () { // 先從存儲里獲取
let _storageSts = localStorage.getItem('sts') let _expiration = _storageSts && JSON.parse(_storageSts).expiration if (_expiration && new Date().getTime() < new Date(_expiration).getTime()) { return JSON.parse(_storageSts) } else { let { data } = await getOssSTSTokenApi() let _sts = data.credentials localStorage.setItem('sts', JSON.stringify(_sts)) return _sts } }
我們先從存儲里取,然后判斷是否過期,未過期就直接取;過期了就從后台調用獲取,再存入localStorage里
流程主要根據文檔來即可:STS臨時授權訪問OSS,其中有各語言的SDK參考,可以直接選JAVA SDK參考即可
(1)先創建子賬號,然后在添加權限頁面,為已創建的子賬號添加AliyunSTSAssumeRoleAccess權限
(2)創建權限策略。自己取名填寫策略名稱;配置模式選擇可視化配置或腳本配置。以腳本配置為例,為ram-test添加ListObjects、PutObject、GetObject等權限,也可以選通配符 *,就是均允許
(3)創建角色並記錄角色ARN。單擊RAM角色管理,新建RAM角色,選擇可信實體類型為阿里雲賬號,在新建RAM角色頁面,填寫RAM角色名稱和備注,本示例RAM角色名稱為RamOssTest,選擇雲賬號為當前雲賬號,單擊完成,之后單擊為角色授權。
在添加權限頁面,選擇自定義權限策略,添加步驟2中創建的權限策略。記錄角色的ARN,即需要扮演角色的ID。
SDK里需要的 roleArn 和
roleSessionName,就是這個里面的 ARN 和名稱,然后按各語言SDK寫代碼即可。比較簡單。
4、分片上傳與斷點續傳
有簡單上傳和分片上傳,其實基本都會使用分片上傳,代碼也比較簡單,下面是針對批量分片上傳寫的一個 mixin(針對編輯和新增是不同的交互邏輯,新增可以批量,編輯不能批量)
import { getKnowledgeListApi, saveContentApi, transformDocApi, transformVideoApi, getContentDetailApi } from '@/apis' import { picklist, validator, initOss, needDocTrans, isVideoMode, guid, cbSuccess } from '@/utils' import FileList from './components/fileList' export const uploadmixin = { data () { return { perms: picklist.perms, rules: validator, resInfo: { title: '', brief: '', permission: 'team', knowledgeId: '', teamId: '', downloadable: true, ext: '', idx: '' }, btnLoading: false, knowledges: [], filesArray: [], ossClient: null, progress: {}, // 存儲進度
} }, components: { FileList }, methods: { async getKnowledge () { let { data } = await getKnowledgeListApi({ mold: 'resource', type: 'my', pageSize: 0 }) this.knowledges = data.list let _id = this.$route.params.id if (_id) this.fetchData(_id) }, async fetchData (_id) { // 編輯獲取內容詳情 let { data } = await getContentDetailApi(_id) this.knowledges.some((item, idx) => { if (item.id === data.knowledgeId) { data.idx = idx return true } }) this.resInfo = data this.filesArray = [{ title: data.title, storageSize: data.storageSize || 0 }] }, selectKnowledge (idx) { let _select = this.knowledges[idx] this.resInfo.knowledgeId = _select.id this.resInfo.teamId = _select.teamId this.resInfo.idx = idx }, saveCheckpoint (key, point) { // 保存上傳斷點 window.localStorage.setItem(key, JSON.stringify(point)) }, removeCheckpoint (key) { // 清除上傳斷點 window.localStorage.removeItem(key) }, async initOss () { this.ossClient = await initOss(false) },
// 分片上傳文件 async multipartUpload (item, idx, type) { try { let _this = this let _tempCheckpoint = JSON.parse(window.localStorage.getItem('checkpoint' + item.file.lastModified)) || {} _tempCheckpoint.file = item.file let _guid = guid() // 唯一id
let _pathname = 'resource'
if (item.ext === 'mp4') _pathname = 'video/outputs'
else if (isVideoMode(item.ext)) _pathname = 'video/inputs' let _path = `${_pathname}/${item.teamId}/${item.knowledgeId}/${_guid}.${item.ext}` let result = await this.ossClient.multipartUpload(_path, item.file, { progress: function (p, checkpoint) { let _p = Math.floor(p * 100) _this.$set(_this.progress, idx, _p) if (_p < 100) _this.saveCheckpoint('checkpoint' + item.file.lastModified, checkpoint) else _this.removeCheckpoint('checkpoint' + item.file.lastModified) }, checkpoint: _tempCheckpoint } ) let _url = result.res.requestUrls[0] item.url = _url.split('?')[0] let { data } = await saveContentApi(item) if (needDocTrans(item.ext)) { // 文檔需要轉碼 transformDocApi({ baseId: data.operateCallBackObj, url: item.url }) } else if (isVideoMode(item.ext)) { // 視頻需要轉碼 transformVideoApi({ baseId: data.operateCallBackObj, url: item.url }) } if (type === 'edit') { // 編輯的情況直接跳轉,新建的情況等批量 Promise.all() 均請求完畢再跳轉 cbSuccess(data, _ => { this.$router.push(`/base/${item.id}`) }) this.btnLoading = false } } catch(e){ console.log(e) } }, // 視頻和文檔才可選下載
showDownload (ext) { return needDocTrans(ext) || isVideoMode(ext) } }, mounted () { this.initOss() this.getKnowledge() } }
其中需要注意下這里的代碼
let _tempCheckpoint = JSON.parse(window.localStorage.getItem('checkpoint' + item.file.lastModified)) || {} _tempCheckpoint.file = item.file
因為一般文件不變的話,其lastModified就不會變,所以存在localStorage里的上傳斷點使用其lastModified比較好。
而且需要注意的是,存儲在localStorage里文件肯定是存不了的,所以再上傳文件就算有斷點,也不會生效。
解決方案就是需重新賦值一下 file 即可,在代碼里 _tempCheckpoint.file = item.file 重新賦值一下 file 即可。
5、報錯:You have no right to access this object because of bucket acl.
I)該報錯為臨時賬戶沒有對應操作的權限
II)排查創建STS臨時賬戶的子賬戶是否有授權AliyunSTSAssumeRoleAccess 權限或者扮演對應角色的權限,為子賬戶授權AliyunSTSAssumeRoleAccess 權限測試
III)排查創建STS臨時賬戶的角色,是否有授權對應bucket對應接口的權限,為角色授權AliyunOSSFullAccess權限測試
IV 排查創建STS臨時賬戶時傳入的policy是否有對應bucket對應接口的權限,設置policy為OSSFULL權限看看是否正常;
{ "Statement": [ { "Action": "oss:*", "Effect": "Allow", "Resource": "*" } ], "Version": "1" }
我出現這個問題就是因為沒為角色授權AliyunOSSFullAccess權限,給角色增加這個權限之后就可以了。
參考下面這些異常排查:子賬戶及STS臨時賬戶調用OSS的常見問題及排查