前言
当业务需求中需要上传大量的文件数据时,相比本地服务存储,第三方存储更能满足业务。比如 阿里云对象存储OSS(Object Storage Service)。它是一款分布式云存储服务。那上传数据到OSS有什么好处呢?稳定、可靠、安全、低成本。非常适合用来存储各种非结构化数据,比如视频、图像、日志、文本文件。
OSS使用:
第三方存储服务基本上都是收费的,所以先让领导或Devops人员去申请开通账号。完事登录阿里云控制台,进行服务基础配置。附:开发指南
为了实现跨域访问,保证跨域数据传输的安全进行,需要在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 周二 上海