web端oss直传方案之vue+elementUI+OSS实践篇(附各种踩坑)

news/2024/10/20 0:21:58/

文章目录

  • 解决思路
  • 实践
    • 工具类uploadOss.js
    • 封装上传组件NewUpload
    • 调用上传组件
  • 遇到的问题
    • 从oss获取下载链接错误
    • 分片上传报错 - ETag配置
    • 取消上传
    • STS token 常见问题
      • 有效期
      • 多个Token是否同时有效
  • 总结

      以前的项目上传及下载都是web端上传至服务端,服务器端再上传至OSS,小文件这种方案可以接受,但文件大了性能就会超级糟糕(浏览器崩溃也是常态)!所以呢,不得不探索web端直传oss方案。
      探索过程中若采用最简单方式-将oss配置到前端,appId和appSecret会全部暴露!所以本文采用STS临时访问凭证访问OSS(需要服务器端提供stsToken接口),STS相关文档请查看官网。

解决思路

  1. 引入ali-oss依赖,版本号:"ali-oss": "^6.17.1"
  2. 定义oss工具js文件,uploadOss.js
  3. 封装ElementUI的el-upload组件NewUpload
  4. 在界面中引用NewUpload组件并使用

工程目录

├── src 
│   ├── api  # 服务端API
│   │   ├── oss.js     # 定义服务端API:stsToken 
│   └── components  # 封装组件 
│       ├── Upload       
│       	├── index.vue  
│       	├── uploadOss.js  
├── views # 视图 
│   ├── pay   
│   │   ├── index.vue 
├── package.json # 依赖 

实践

工具类uploadOss.js


该工具类包含三个方法:

  • 分片上传
  • 获取oss文件临时链接
  • 设置取消上传标志位
const OSS = require('ali-oss')
import {stsToken
} from '@/api/oss.js'let ossConfig = null;
// 取消上传控制项
let isCancel = false;// 设置客户端请求访问凭证的地址 
const OssFunc = async() => {let res = await stsToken();ossConfig = res.data;const client = new OSS({// yourRegion填写Bucket所在地域。以华东1(杭州)为例,yourRegion填写为oss-cn-hangzhou。region: 'oss-cn-hangzhou',accessKeyId: ossConfig.accessKeyId,accessKeySecret: ossConfig.accessKeySecret,stsToken: ossConfig.securityToken,bucket: ossConfig.bucketName,timeout: 600000,// HTTPS (secure: true) or HTTP (secure: false) protocolsecure: false,refreshSTSTokenInterval: 3000000,refreshSTSToken: stsToken().then(res => {if (res.status === 0) {console.log('成功刷新oss token');ossConfig.accessKeyId = res.data.accessKeyId;ossConfig.accessKeySecret = res.data.accessKeySecret;ossConfig.securityToken = res.data.securityToken;}})});return client;
};// 分片上传方法(没有设置分片相关设置,采用默认)
async function put(fileName, file) {try {let oss = await OssFunc();const result = await oss.multipartUpload(fileName, file, {'headers': {'Access-Control-Allow-Origin': '*',},'progress': (progress) => {// console.log('progress:', progress)if (isCancel) {oss.cancel();// 复位isCancel = false;}}});return result;} catch (e) {console.log(e);}
}// 设置取消上传标志位为true 
async function cancelUpload() {isCancel = true;return true;
}// 获取oss文件临时路径 
async function getUrl(name) {try {let oss = await OssFunc();const result = await oss.signatureUrl(name);return result;} catch (e) {console.log(e);}
}export {put,getUrl,cancelUpload,
}

封装上传组件NewUpload

<template><div class="images-list1"><el-uploadref="upload":class="className"action="string":data="paramsData":limit="fileLimit":show-file-list="showFile":on-success="handleSuccess":on-error="handleUploadError":on-remove="handleRemove":on-exceed="handleExceed":on-preview="handlePreview":multiple="fileLimit > 1":list-type="listType":file-list="fileList":drag="dragable":http-request="handleUploadFile"><div v-if="className === 'avatar-uploader'"><img v-if="originData.showUrl" :src="originData.showUrl" class="avatar" /><i v-else class="el-icon-plus avatar-uploader-icon"></i><el-dialog :visible.sync="dialogVisible" append-to-body><img width="100%" :src="originData.showUrl" alt="" /></el-dialog></div><div v-else-if="className === 'upload-demo'"><!-- <i v-if="listType === 'picture-card'" class="el-icon-plus"></i><i v-else class="el-icon-upload uploIcon"></i> --><div v-if="loading"><span class="el-icon-loading" style="font-size: 18px" /></div><div v-else><div v-if="fileLimit > 1"><el-button plain :size="btnSize" icon="el-icon-plus">{{ btnText }}</el-button></div><div v-else><div v-if="originData.key"><span class="content">{{ originData.key }}</span></div><div v-else><el-button plain :size="btnSize" icon="el-icon-plus">{{ btnText }}</el-button></div></div></div></div></el-upload><span v-if="loading && className === 'upload-demo'" class="cancel" @click="handleCancel">取消上传</span><div v-if="showTip" class="el-upload__tip">允许文件类型:{{ fileTypeName || 'jpg/png' }}</div><div v-if="showTip" class="el-upload__tip">文件大小上限:{{ fileLimit || 1 }}M</div></div>
</template>
<script>import {put,getUrl,cancelUpload
} from '@/components/Upload/uploadOss.js'export default {name: 'NewUpload',props: {// 值value: [String, Object, Array],// 大小限制(MB)fileSize: {type: Number,default: 1},// 文件类型, 例如["doc", "xls", "ppt", "txt", "pdf"]fileType: {type: Array,default: () => []},// 文件列表类型 text/picture/picture-cardlistType: {type: String,default: 'picture'},// 是否显示提示isShowTip: {type: Boolean,default: true},// 最大允许上传个数fileLimit: {type: Number,default: 99},// 是否显示上传的文件列表showFile: {type: Boolean,default: false},// 文件上传样式classclassName: {type: String,default: 'upload-demo'},// 是否允许拖拽上传dragable: {type: Boolean,default: false},// 源数据originData: {type: [Object, Array],default: {}},// 按钮显示的文案btnText: {type: String,default: '上传'},// 按钮大小btnSize: {type: String,default: 'mini'}},data() {return {uploadUrl: '', // 上传的图片服务器地址paramsData: {}, // 上传携带的参数,看需求要不要fileList: [],tempFileList: [], // 因为 fileList为只读属性,所以用了一个中间变量来进行数据改变的交互。imageUrl: '',loading: false,dialogVisible: false,allowUplad: true}},watch: {value: {handler: function (newVal) {this.tempFileList = newVal},immediate: true,deep: true}},computed: {// 是否显示提示showTip() {return this.isShowTip && (this.fileType || this.fileSize)},fileTypeName() {let typeName = ''this.fileType.forEach((item) => {typeName += `${item}`})return typeName},fileAccept() {let fileAccept = ''this.fileType.forEach((element) => {fileAccept += `.${element},`})return fileAccept}},created() {if (this.value) {this.fileList = JSON.parse(JSON.stringify(this.value))}var token = nullif (!JSON.parse(sessionStorage.getItem('tokenAll'))) {token = null} else {token = JSON.parse(sessionStorage.getItem('tokenAll')).token}this.paramsData = {token: token}},methods: {// 上传前校检格式和大小handleBeforeUpload(file) {let result = trueif (this.fileType && this.fileType.length > 0 && file) {const isTypeOk = this.fileType.some((item) => {let fileExtension = ''if (file.name.lastIndexOf('.') > -1) {fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1)}if (fileExtension && fileExtension.indexOf(item) > -1) {return true} else {return falseresult = false}})if (!isTypeOk && file) {this.$message.error(`文件格式不正确, 请上传${this.fileType.join('/')}格式文件!`)return falseresult = false}}// 校检文件大小if (this.fileSize && file) {const isLt = file.size / 1024 / 1024 < this.fileSizeif (!isLt) {this.$message.error(`上传文件大小不能超过 ${this.fileSize} MB!`)return falseresult = false}}return result},handleUploadError() {this.$message.error('上传失败, 请重试')},// 文件个数超出handleExceed() {this.$message.error(`超出上传文件个数,请检查!`)},// 图片预览handlePreview(file) {this.dialogVisible = true},// 文件上传成功的钩子handleSuccess(res, file, fileList) {console.log(res)},// 文件列表移除文件时的钩子handleRemove(file, fileList) {this.changeFileList(fileList)},// 文件列表改变的时候,更新组件的v-model的文的数据changeFileList(fileList) {const tempFileList = fileList.map((item) => {let tempItem = {name: item.name,url: item.response ? item.response.payload.imgUrl : item.url}return tempItem})this.$emit('input', tempFileList)},// 自定义上传handleUploadFile(option) {if (this.handleBeforeUpload(option.file)) {this.loading = true//获取上传后的urlconst _name = this.getDate() + '/' + option.file.uid + '.' + option.file.name.split('.')[1]put(_name, option.file).then((res) => {if (res) {getUrl(res.name).then((rel) => {this.originData.showUrl = relthis.originData.key = _namethis.$emit('submit', {class: this.className,key: _name,showUrl: rel})this.loading = falsethis.$refs.upload.clearFiles()})} else {this.$refs.upload.clearFiles()}})}},// 取消上传handleCancel() {cancelUpload().then((rel) => {if (rel) {this.$refs.upload.abort()this.loading = false}})},// 获取当前年月日getDate() {const date = new Date()var year = date.getFullYear()var month = date.getMonth() + 1var day = date.getDate()month = month > 9 ? month : '0' + monthday = day < 10 ? '0' + day : dayvar today = year + month + dayreturn today}}
}
</script>

调用上传组件

包含上传按钮及下载显示

<template><div class="uploadBox"><NewUpload:style="{ width: row.fileUrl ? '80%' : '100%' }"class="uploads"className="upload-demo":fileSize="10240":fileLimit="1":showFile="false":isShowTip="false"listType="text":fileType="[]":originData="row.originData"@submit="(val) => {uploadFileRes(val, row)}"/><div v-if="row.fileUrl" class="close"><spanclass="el-icon-download"@click="downFile(row.originData.showUrl)"title="下载文件"/></div></div>
</template>
<script>
import NewUpload from '@/components/Upload/index.vue'
import { getUrl } from '@/components/Upload/uploadOss.js'export default {components: { NewUpload },created() {getUrl(v2.fileUrl).then((rm) => {obj.showUrl = rm})},method: {getOssFileUrl() {getUrl(fileName).then((url) => {// TODO:获取oss文件临时路径后赋值})},uploadFileRes(data, row) {row.fileUrl = data.key},downFile(rel) {const downloadElement = document.createElement('a')downloadElement.href = reldocument.body.appendChild(downloadElement)downloadElement.click() // 点击下载document.body.removeChild(downloadElement) // 下载完成移除元素},}
}
</script>

遇到的问题

从oss获取下载链接错误

使用ali-oss中signatureUrl(name: string, options?: OSS.SignatureUrlOptions): string;获取oss文件临时访问路径,提示如下错误:

错误1:Access denied by authorizer’s policy.
错误2:You have no right to access this object because of bucket acl.

在官网上寻找解决案例,发现是配置的问题(NOTE:若在阿里云管理平台和在代码中分别配置账户权限,只取交集!),可参考官方解决方案-
教程示例:使用RAM Policy控制OSS的访问权限

{"Version":"1","Statement":[{"Effect":"Allow","Action":["oss:ListObjects"],"Resource":["acs:oss:*:*:examplebucket"],"Condition":{"StringLike":{"oss:Prefix":["Development","Development/*"]}}},{"Effect":"Allow","Action":["oss:GetObject","oss:PutObject","oss:GetObjectAcl"],"Resource":["acs:oss:*:*:examplebucket/Development/*"]}]
}

分片上传报错 - ETag配置

Please set the etag of expose-headers in OSS

使用OSS分片上传功能上传文件时报“Please set the etag of expose-headers in OSS”错误

取消上传

官方推荐方案:调用OSS的JS SDK实现取消分块上传及续传

核心点:调用分片上传时,在接口MultipartUploadOptions中属性progress进行取消动作

/*** Upload file with OSS multipart.*/
multipartUpload(name: string, file: any, options: OSS.MultipartUploadOptions): Promise<OSS.MultipartUploadResult>;interface MultipartUploadOptions {/** the number of parts to be uploaded in parallel */parallel?: number | undefined;/** the suggested size for each part */partSize?: number | undefined;/** the progress callback called after each successful upload of one part */progress?: ((...args: any[]) => any) | undefined;/** the checkpoint to resume upload, if this is provided, it will continue the upload from where interrupted, otherwise a new multipart upload will be created. */checkpoint?: Checkpoint | undefined;meta?: UserMeta | undefined;mime?: string | undefined;callback?: ObjectCallback | undefined;headers?: object | undefined;timeout?: number | undefined;/** {Object} only uploadPartCopy api used, detail */copyheaders?: object | undefined;
}

STS token 常见问题

RAM角色和STS Token常见问题

有效期

STS Token的有效期最小值为900秒,最大值为角色最大会话时间设置的值,默认值为3600秒。

多个Token是否同时有效

STS Token在过期之前都是有效的,无论是否创建了新的STS Token。

总结

每一次解决问题,都开启了一片新天地~ 还真是那句话:学无止境


http://www.ppmy.cn/news/87953.html

相关文章

C++——图

图是由节点&#xff08;顶点&#xff09;和连接节点的边组成的一种非线性数据结构。它用于表示不同对象之间的关系或网络结构。图可以用于建模和解决许多现实世界中的问题&#xff0c;例如社交网络分析、路线规划、图像处理等。 在图中&#xff0c;节点表示实体或对象&#xf…

80个Python练手小项目;AI开发者的总结与反思;B站免费Stable Diffusion视频教程;五问ChatGPT+医学影像 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; 『美团大模型已秘密研发数月』在仅剩一年的窗口期里努力奔跑 5月18日下午&#xff0c;美团内部召开大模型技术分享会&#xff0c;美团…

【图床】SpringBoot上传图片

知识目录 一、写在前面✨二、新建开源仓库✨2.1 新建仓库2.2 将仓库设置为开源2.3 生产私人令牌 三、代码实现&#x1f604;3.1 工具类3.2 上传图片 四、总结撒花&#x1f60a; 一、写在前面✨ 大家好&#xff01;我是初心&#xff0c;很高兴再次和大家见面。 今天跟大家分享…

C++模板初阶(函数模板、类模板)知识点+完整思维导图+实操图+深入细节通俗易懂建议收藏

绪论 思想决定行动&#xff0c;行动养成习惯&#xff0c;习惯形成品质&#xff0c;品质决定命运。——陶行知 本章讲的是c的初阶模板&#xff0c;全文不算代码字数少的可怜&#xff0c;但模板是我们c必须学的一个宝物&#xff0c;他的出现可是c的飞跃性成就&#xff01;下面将主…

无界AI绘画基础教程,和Midjourney以及Stable Diffusion哪个更好用?

本教程收集于:AIGC从入门到精通教程 无界AI绘画基础教程,和Midjourney以及Stable Diffusion哪个更好用? 目录 简单的总结 注册 简单介绍

工作模式(2)

输入捕捉 输入捕捉功能的主要特点&#xff1a; ⚫ 上升沿或下降沿捕捉 ⚫ 脉冲宽度捕捉或脉冲周期捕捉 ⚫ 带清零的捕捉或自由计数捕捉 ⚫ 单次捕捉或连续捕捉 捕捉模式只能工作在16bit级联模式下&#xff0c;从0开始计数。当选择上升沿捕捉周期模式时&#xff0c;电路在检测到…

华为OD机试之最远足迹(Java源码)

最远足迹 题目描述 某探险队负责对地下洞穴进行探险。探险队成员在进行探险任务时&#xff0c;随身携带的记录器会不定期地记录自身的坐标&#xff0c;但在记录的间隙中也会记录其他数据。探索工作结束后&#xff0c;探险队需要获取到某成员在探险过程中相对于探险队总部的最远…

linux centos 安装JDK、tomcat、nginx教程记录

一、安装jdk 1、查看linux系统的jdk位数&#xff08;64/32位&#xff09; 查看本机位数命令&#xff1a; sudo uname --m 2、进入jdk下载官网 Java Downloads | Oracle 现在默认是最新的jdk20 以为我是之前的项目&#xff0c;使用的是jdk1.8_181版本&#xff0c;所以我需要…