上传数据到阿里云OSS

444 阅读4分钟

前言

      当业务需求中需要上传大量的文件数据时,相比本地服务存储,第三方存储更能满足业务。比如 阿里云对象存储OSS(Object Storage Service)。它是一款分布式云存储服务。那上传数据到OSS有什么好处呢?稳定、可靠、安全、低成本。非常适合用来存储各种非结构化数据,比如视频、图像、日志、文本文件。

OSS使用:

第三方存储服务基本上都是收费的,所以先让领导或Devops人员去申请开通账号。完事登录阿里云控制台,进行服务基础配置。附:开发指南

process

为了实现跨域访问,保证跨域数据传输的安全进行,需要在OSS控制台正确设置跨域CORS规则:

技术方案:

目前通过Web端将文件上传到OSS,有以下三种方案:

  • 利用OSS Browser.js SDK将文件上传到OSS

  •  该方案通过OSS Browser.js SDK直传数据到OSS,详细的SDK Demo请参见上传文件。在网络条件不好的状况下可以通过断点续传的方式上传大文件。该方案在个别浏览器上有兼容性问题,目前兼容IE10及以上版本浏览器,主流版本的Edge、Chrome、Firefox、Safari浏览器,以及大部分的Android、iOS、WindowsPhone手机上的浏览器。

  • 使用表单上传方式将文件上传到OSS

  • 利用OSS提供的PostObject接口,通过表单上传的方式将文件上传到OSS。该方案兼容大部分浏览器,但在网络状况不好的时候,如果单个文件上传失败,只能重试上传。操作方法请参见PostObject上传方案

  • 通过小程序上传文件到OSS

  • 通过小程序,如微信小程序和支付宝小程序,利用OSS提供的PostObject接口来实现表单上传。操作方式请参见微信小程序直传实践支付宝小程序直传实践

最佳实践:

      Web端上传数据至OSS,我个人采用的是 “服务端签名后直传”(在服务端完成签名,然后通过表单直传数据到OSS。),优势在于无需将Accesskey暴露在前端页面,具有更高的安全性。

时序图

PC端上传文件到阿里云OSS(Vue+Ant Design UI)

关键代码示范:

HTML

<a-upload    v-decorator="['mediaItems', { rules: [{ required: true, message: '媒体资源不能为空' }] }]"    accept=".jpg, .jpeg, .png, .gif, .mp4, .mov"    :data="paramsData"    list-type="picture-card"    multiple    :file-list="fileList"    :showUploadList="false"    :before-upload="beforeUpload"    :custom-request="customRequest">  <div :class="$style.antUploadLayout">     <a-icon type="plus" />     <span :class="$style.antUploadText">上传资源</span>  </div></a-upload>


JS

data() {
   return {
      action: '', // 上传的地址      paramsData: {}, // 上传的参数
      fileList: [],   }
},
methods: {
    beforeUpload(file) {      const fileType = ['image/jpg', 'image/jpeg', 'image/png', 'image/gif', 'video/mp4', 'video/quicktime']      const isImageOrVideo = fileType.includes(file.type)      if (!isImageOrVideo) {        this.$message.error('资源格式不正确!')        return false      }      const isLt100M = file.size / 1024 / 1024 < 50      if (!isLt100M) {        this.$message.error('资源大小不允许超过50MB!')        return false      }      return this.getToken(file, file.name)    },    getToken(file, fileName) {      return new Promise((resolve, reject) => {        this.getStsToken({ fileName })          .then((res) => {            const { key, policy, ossRamAccessKeyId, signature, stsToken, ossHost, expire, publicUrl } = res            this.action = ossHost            this.paramsData = {              key,              success_action_status: '200',              OSSAccessKeyId: ossRamAccessKeyId,              policy,              Signature: signature,              'x-oss-security-token': stsToken,              'x-oss-content-type': 'multipart/form-data',              Expires: expire,              publicUrl,            }            resolve(file)          })          .catch(() => {            this.$message.error('上传失败,请稍后再试')            reject()          })      })    },    customRequest(fileInfo) {      const { file } = fileInfo      this.uploadResource({        url: this.action.replace('http://', 'https://'),        data: { ...this.paramsData, file },      })        .then(async () => {          const {            data: { key, publicUrl },            file: { uid, name, type },          } = fileInfo          const uploadFile = {            uid: `-${uid}`,            name,            status: 'done',            url: publicUrl,            thumbUrl: publicUrl,            type,            filePath: key,            fileType: type.indexOf('video/') !== -1 ? MediaType.VIDEO : MediaType.IMAGE,          }          this.fileList = await [...this.fileList, uploadFile].filter((x) => x.status === 'done')          await this.form.setFieldsValue({            mediaItems: this.fileList.map((item) => ({              fileType: item.type.indexOf('video/') !== -1 ? MediaType.VIDEO : MediaType.IMAGE,              fileName: item.name,              filePath: item.filePath,            })),          })        })        .catch(async () => {          this.$message.error('上传失败,请稍后再试')          this.fileList = await this.fileList.filter((x) => x.status === 'done')        })    },},

微信小程序上传文件到阿里云OSS(Taro+React+TypeScript)

关键代码示范:

HTML

<Button className={styles.uploadButton} onClick={this.handleOpenUpload}>    <RIcon type="add" className={styles.icon} />    <Text>{tips || '点击上传'}</Text></Button>


JS

enum FILE_TYPE {  IMAGE = 'IMAGE',  VIDEO = 'VIDEO',}interface Media {  fileType: FILE_TYPE  fileName: string  filePath: string  url: string}
type State = {  mediaList: Media[]}state: State = {    mediaList: [],}

handleUpload = (type) => {    if (type === FILE_TYPE.IMAGE) {      chooseImage({        sizeType: ['original'],        success: (res) => {          res.tempFiles.forEach(async ({ path, size }) => {            const index = path.lastIndexOf('.')            const isFileType = ['jpg', 'jpeg', 'png', 'gif'].includes(path.substring(index + 1))            if (!isFileType) {              messager.error('资源格式不正确!')              return false            }            const isLt100M = size / 1024 / 1024 < 50            if (!isLt100M) {              messager.error('资源大小不允许超过50MB!')              return false            }            await this.uploadMedia(path, type)          })        },      })    } else {      chooseVideo({        compressed: false,        success: (res) => {          const { tempFilePath, size } = res          const index = res.tempFilePath.lastIndexOf('.')          const isFileType = ['mp4', 'mov', 'MOV'].includes(res.tempFilePath.substring(index + 1))          if (!isFileType) {            messager.error('资源格式不正确!')            return false          }          const isLt100M = size / 1024 / 1024 < 50          if (!isLt100M) {            messager.error('资源大小不允许超过50MB!')            return false          }          this.uploadMedia(tempFilePath, type)        },      })    }},

uploadMedia = async (path, type) => {    const header = {      Authorization: getStorageSync(ACCESS_TOKEN),    }    messager.showLoading('上传中', true)    const fileName = path.replace('http://tmp/', '').replace('wxfile://', '').replace('://', '')    const {      key,      policy,      ossRamAccessKeyId,      signature,      stsToken,      ossHost,      expire,      publicUrl,    } = await dispatch.user.getStsToken({ fileName })    const formData = {      key,      success_action_status: '200',      OSSAccessKeyId: ossRamAccessKeyId,      policy,      Signature: signature,      'x-oss-security-token': stsToken,      'x-oss-content-type': 'multipart/form-data',      Expires: expire,      publicUrl,    }    uploadFile({      url: ossHost,      filePath: path,      header,      name: 'file',      formData,      success: () => {        const media = {          fileType: type,          fileName,          filePath: key,          url: publicUrl,        }        this.setState(          {            mediaList: [...this.state.mediaList, media],          }        )      },      fail: () => {        messager.error('上传失败,请重试')      },      complete: () => {        messager.hideLoading()      },    })  }

记录于 2022-03-01 周二 上海