zoukankan      html  css  js  c++  java
  • 阿里云OSS前端直传文件方案注意点

      最近项目里需要大量上传文件,考虑使用阿里云提供的对象存储,并采用前端直传方案。这里并不想介绍如何使用,想知道如何使用,可以直接参考官方文档,也没啥特多介绍的,所以这里主要是记录一下使用中的注意点。

      阿里云上传文件官方文档:阿里云上传文件前端直传方案文档

    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的常见问题及排查

     

  • 相关阅读:
    JZOJ 3034. 【NOIP2012模拟10.17】独立集
    JZOJ 3035. 【NOIP2012模拟10.17】铁轨
    JZOJ 1259. 牛棚安排
    数位DP JZOJ 3316. 非回文数字
    JZOJ 3046. 游戏
    JZOJ 3013. 填充棋盘
    debian 安装oracle提供的java8
    java 汉字转拼音 PinYin4j
    debian ssh设置root权限登陆 Permission denied, please try again
    java并发下订单生成策略
  • 原文地址:https://www.cnblogs.com/goloving/p/13974568.html
Copyright © 2011-2022 走看看