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

     

  • 相关阅读:
    JMS学习四(ActiveMQ消息过滤)
    JMS学习三(ActiveMQ消息的可靠性)
    JMS学习二(简单的ActiveMQ实例)
    JMS学习一(JMS介绍)
    Linux iostat监测IO状态
    git删除所有提交历史记录
    MySQL查看数据库相关信息
    Java面试通关要点汇总集
    java开发需掌握技能2
    java开发需掌握技能1
  • 原文地址:https://www.cnblogs.com/goloving/p/13974568.html
Copyright © 2020-2023  润新知