客户端Vue 2.0框架使用JavaScript上传阿里云SDK

800 阅读3分钟

一、需求

最近用户反馈大视频上传到系统项目中失败,排查问题得到原因是后台服务器带宽限制,出现上传过程中响应超时情况,导致视频上传失败。

原有的上传流程如下:

客户端视频上传至服务器后,再由服务器上传至阿里云,阿里云处理完毕后返回给服务端数据,最后服务器返还给客户端视频oss地址。

image.png

得知原因后,经讨论决定大文件上传省去中间服务端环节,由客户端直接与阿里云VOD进行数据交互。修改后的流程如下:

image.png

二、客户端上传SDK代码实现(使用JavaScript上传SDK)

1. 准备工作

  • 注册阿里云账号,完成实名认证
  • 开通视频点播服务,并完成相关配置
  • 准备好访问点播服务使用的Access Key
  • 客户端上传:配置RAM子账号,然后通过其Access Key访问媒体上传接口获取上传地址和凭证,再下发给客户端进行上传。
  • 开发环境:客户端上传SDK,支持Android、iOS、移动端和PC端Web浏览器上传。(本文章为PC端Web浏览器上传)

2. 上传方式(上传地址和凭证方式)

  • 上传地址和凭证方式(推荐)
  • STS方式(有兴趣可以研究一下)

3. 操作步骤

1. 在页面引入JavaScript脚本。JavaScript脚本下载,请参见SDK下载

截图.png

之后在index.html文件中将lib代码引入:

<!DOCTYPE html>
<html>
  <head>
    <script src="<%= BASE_URL %>lib/aliyun-upload-sdk/lib/es6-promise.min.js"></script>
    <script src="<%= BASE_URL %>lib/aliyun-upload-sdk/lib/aliyun-oss-sdk-6.13.0.min.js"></script>
    <script src="<%= BASE_URL %>lib/aliyun-upload-sdk/aliyun-upload-sdk-1.5.2.min.js"></script>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

2. 封装VideoUpload组件

其中两个接口需要服务端根据Access Key获取上传凭证和地址:


<template>
  <div class="upload">
    <!-- 上传框 -->
    <div class="upload-item upload-input">
      <input id="fileUpload" hidden type="file" @change="fileChange($event)">
      <el-button type="primary" @click="hChooseFile">选择文件</el-button>
      <div class="file_name">{{ file_name }}</div>
    </div>
    <!-- 上传状态 -->
    <div class="upload-item">
      <span class="status"><span>{{ statusText }}</span></span>
    </div>
    <!-- 操作 -->
    <div class="upload-item">
      <el-button
        type="primary"
        size="small"
        :disabled="uploadDisabled"
        @click="authUpload"
      >开始上传</el-button>
      <el-button
        type="danger"
        size="small"
        :disabled="pauseDisabled"
        @click="pauseUpload"
      >暂停上传</el-button>
      <el-button
        type="primary"
        size="small"
        :disabled="resumeDisabled"
        @click="resumeUpload"
      >恢复上传</el-button>
    </div>
    <!-- 进度 -->
    <div
      v-if="status===1"
      class="upload-item progress"
    >上传进度: <span id="auth-progress">{{ authProgress }}</span> %
    </div>
  </div>
</template>
<script>
import axios from 'axios'
export default {
  name'VideoUpload',
  data() {
    return {
      timeout''// 请求过期时间
      partSize''// 分片大小
      parallel''// 上传分片数 5
      retryCount''// 网络失败重试次数 3
      retryDuration''// 网络失败重试间隔 2s
      region'cn-shanghai'// 地区, 默认 cn-shanghai
      userId'1303984639806000'// 随机用户id
      filenull// 本地file
      authProgress0// 上传进度
      uploadDisabledtrue// 上传按钮禁止
      resumeDisabledtrue// 恢复上传按钮禁止
      pauseDisabledtrue// 暂停按钮禁止
      uploadernull// new AliyunUpload.Vod({}) 返回值
      statusText'请选择正确的视频格式,视频大小不能超过500M!'// 状态信息
      file_name''// 文件名称
      status0
    }
  },
  methods: {
    hChooseFile() {
      document.querySelector('#fileUpload').click()
    },
    // 1.input文件框发生变化
    fileChange(e) {
      console.log(e)
      if (!['video/mpeg4''video/mp4''video/webm''video/avi''video/wma'].includes(e.target.files[0].type)) {
        document.querySelector('#fileUpload').value = ''
        this.$message.error('请选择正确的视频格式!')
        return
      }
      console.log(e.target.files[0].size / 1024 / 1024)
      if (e.target.files[0].size / 1024 > 500 * 1 * 1024) {
        this.$message.error('视频大小不能超过500M!')
        return
      }
      this.file = e.target.files[0]
      if (!this.file) {
        alert('请先选择需要上传的文件!')
        return
      }
      this.file_name = this.file.name
      var userData = '{"Vod":{}}'
      if (this.uploader) {
        this.uploader.stopUpload()
        this.authProgress = 0
        this.statusText = ''
      }
      this.uploader = this.createUploader()
      this.uploader.addFile(this.filenullnullnull, userData)
      this.uploadDisabled = false
      this.pauseDisabled = true
      this.resumeDisabled = true
    },
    // 2.然后调用 startUpload 方法, 开始上传
    authUpload() {
      if (this.uploader !== null) {
        this.uploader.startUpload()
        this.uploadDisabled = true
        this.pauseDisabled = false
        // this.status = 1
      }
    },
    // 3.暂停上传
    pauseUpload() {
      if (this.uploader !== null) {
        this.uploader.stopUpload()
        this.resumeDisabled = false
        this.pauseDisabled = true
      }
    },
    // 4.恢复上传
    resumeUpload() {
      if (this.uploader !== null) {
        this.uploader.startUpload()
        this.resumeDisabled = true
        this.pauseDisabled = false
      }
    },
    // 5.初始化上传实例
    createUploader(type) {
      const self = this
      // eslint-disable-next-line no-undef
      const uploader = new AliyunUpload.Vod({
        timeout: self.timeout || 60000,
        partSize: self.partSize || 1048576,
        parallel: self.parallel || 5,
        retryCount: self.retryCount || 3,
        retryDuration: self.retryDuration || 2,
        region: self.region,
        userId: self.userId,
        // 添加文件成功
        addFileSuccessfunction(uploadInfo) {
          console.log(uploadInfo, 156)
          self.uploadDisabled = false
          self.resumeDisabled = false
          self.statusText = '添加文件成功, 等待上传...'
          console.log('addFileSuccess: ' + uploadInfo.file.name)
        },
        // 开始上传
        onUploadstartedfunction(uploadInfo) {
          // 如果是 UploadAuth 上传方式, 需要调用 uploader.setUploadAuthAndAddress 方法
          // 如果是 UploadAuth 上传方式, 需要根据 uploadInfo.videoId是否有值,调用点播的不同接口获取uploadauth和uploadAddress
          // 如果 uploadInfo.videoId 有值,调用刷新视频上传凭证接口,否则调用创建视频上传凭证接口
          // 注意: 这里是测试 demo 所以直接调用了获取 UploadAuth 的测试接口, 用户在使用时需要判断 uploadInfo.videoId 存在与否从而调用 openApi
          // 如果 uploadInfo.videoId 存在, 调用 刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html)
          // 如果 uploadInfo.videoId 不存在,调用 获取视频上传地址和凭证接口(https://help.aliyun.com/document_detail/55407.html)
          if (!uploadInfo.videoId) {
    	// 注意:此接口需要服务端配合添加
            const createUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/CreateUploadVideo?Title=testvod1&FileName=aa.mp4&BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&VideoId=5bfcc7864fc14b96972842172207c9e6'
            axios.get(createUrl).then(({ data }) => {
              console.log(data)
              const uploadAuth = data.UploadAuth
              const uploadAddress = data.UploadAddress
              const videoId = data.VideoId
              uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId)
            })
            self.statusText = '文件开始上传...'
          } else {
            // 如果videoId有值,根据videoId刷新上传凭证
            // https://help.aliyun.com/document_detail/55408.html?spm=a2c4g.11186623.6.630.BoYYcY
            // 注意:此接口需要服务端配合添加
            const refreshUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/RefreshUploadVideo?BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&Title=haha1&FileName=xxx.mp4&VideoId=' + uploadInfo.videoId
            axios.post(refreshUrl).then(({ data }) => {
              const uploadAuth = data.UploadAuth
              const uploadAddress = data.UploadAddress
              const videoId = data.VideoId
              uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId)
            })
          }
        },
        // 文件上传成功
        onUploadSucceedfunction(uploadInfo) {
          console.log(uploadInfo, '上传成功')
          self.statusText = '文件上传成功!'
          self.$emit('success', uploadInfo)
        },
        // 文件上传失败
        onUploadFailedfunction(uploadInfo, code, message) {
          self.statusText = '文件上传失败!'
        },
        // 取消文件上传
        onUploadCanceledfunction(uploadInfo, code, message) {
          self.statusText = '文件已暂停上传'
        },
        // 文件上传进度,单位:字节, 可以在这个函数中拿到上传进度并显示在页面上
        onUploadProgressfunction(uploadInfo, totalSize, progress) {
          const progressPercent = Math.ceil(progress * 100)
          self.authProgress = progressPercent
          self.statusText = '文件上传中...'
          self.status = 1
        },
        // 上传凭证超时
        onUploadTokenExpiredfunction(uploadInfo) {
          // 上传大文件超时, 如果是上传方式一即根据 UploadAuth 上传时
          // 需要根据 uploadInfo.videoId 调用刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html)重新获取 UploadAuth
          // 然后调用 resumeUploadWithAuth 方法, 这里是测试接口, 所以我直接获取了 UploadAuth
          // 注意:此接口需要服务端配合添加
          const refreshUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/RefreshUploadVideo?BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&Title=haha1&FileName=xxx.mp4&VideoId=' + uploadInfo.videoId
          axios.get(refreshUrl).then(({ data }) => {
            const uploadAuth = data.UploadAuth
            uploader.resumeUploadWithAuth(uploadAuth)
          })
          self.statusText = '文件超时...'
        },
        // 全部文件上传结束
        onUploadEndfunction(uploadInfo) {
          self.statusText = '文件上传完毕'
          self.status = 0
        }
      })
      return uploader
    },
    // 6.初始化
    init() {
      this.file = null// 本地file
      this.authProgress = 0// 上传进度
      this.uploadDisabled = true// 上传按钮禁止
      this.resumeDisabled = true// 恢复上传按钮禁止
      this.pauseDisabled = true// 暂停按钮禁止
      this.uploader = null// new AliyunUpload.Vod({}) 返回值
      this.statusText = '请选择正确的视频格式,视频大小不能超过500M!'// 状态信息
      this.file_name = '' // 文件名称
    }
  }
}
</script>
<style lang="scss" scoped>
.upload {
  &-item {
    margin16px 0;
    font-size14px;
    color#181818;
  }
  &-input {
    display: flex;
    align-items: center;
    input {
      background-color#409EFF;
    }
    .file_name {
      margin-left16px;
    }
  }
}
</style>

样式可自行调整。最终效果如下:

image.png

欢迎批评指正!