最近项目中上传文件需要用阿里云的OSS上传,在此之前也没有接触过。一开始项目leader转发了一个关于【前端OSS直传】链接https://help.aliyun.com/document_detail/31926.html给我,所以我一开始研究的是用最简单的方法:引入一个ali-oss的npm包
npm install ali-oss -S
阿里云关于第一种简单方法文档链接:https://help.aliyun.com/document_detail/120092.htm?spm=a2c4g.11186623.0.0.74366fedJb1Pmx#section-iy3-bfe-7mn
一开始也没有太仔细看,直到后端那边准备就绪我这边开始做的时候才发现这种方法和我项目中用的element-plus的upload组件匹配度不高。由于upload的二次封装已经提前做好了,我也不想改,就在网上看看还有没有其他的方法。功夫不负有心人啊,总算找到了:https://blog.csdn.net/qq_34761385/article/details/121352962,代码如下:
<template>
<div class="uploadimg">
<el-upload
:action="url"
list-type="picture-card"
accept="image/*"
:data="basedata"
:file-list="fileList"
:limit="limit"
:auto-upload="true"
:on-preview="handlePictureCardPreview"
:on-success="handleSuccess"
:before-upload="handleUpload"
:on-exceed="handleExceed"
:on-remove="handleRemove">
<i class="el-icon-plus"></i>
</el-upload>
<el-dialog :visible.sync="dialogVisible" append-to-body>
<img width="100%" :src="dialogImageUrl" alt="">
</el-dialog>
</div>
</template>
<script>
import { filePolicy } from "@/api/upload"
export default {
name:'Uploadimg',
props:{
limit:Number,
filelist:Array(),
},
data() {
return {
url:'',
fileList:[],
// filelist:[], //上传文件url列表
basedata:{},
dialogImageUrl: '',
dialogVisible: false
};
},
mounted() {
this.fileList=this.filelist.map(item=>{return {url:item}})
},
methods: {
handleUpload(file){
console.log("开始上传:",file);
const that=this
return new Promise((resolve, reject) => {
filePolicy().then(key=>{
that.url=key.data.data.host;
var name=file.name;
key.data.data.OSSAccessKeyId=key.data.data.accessid;
key.data.data.key=key.data.data.dir+'/'+file.uid+name;
delete key.data.data.callback
that.basedata=key.data.data;
var img=key.data.data.cdnHost+'/'+key.data.data.key;
that.filelist.push(img);
resolve();
}).catch(err => {reject(err);})
})
},
handleSuccess(response, file, fileList){
console.log("上传成功:",response,file, fileList);
this.fileList=fileList;
this.$emit("getfile",this.filelist);
},
handleExceed(files, fileList) {
this.$message.warning(`当前限制选择 ${this.limit} 个文件`);
},
handleRemove(file, fileList) {
console.log("删除文件:",file);
this.fileList=fileList;
var f = this.filelist.find(item=>item.indexOf(file.uid)!=-1);
var i = this.filelist.indexOf(f);
this.filelist.splice(i,1);
this.$emit("getfile",this.filelist);
},
handlePictureCardPreview(file) {
console.log("文件预览:",file);
this.dialogImageUrl = file.url;
this.dialogVisible = true;
}
},
}
</script>
这里的filePolicy是一个promise异步请求,主要获取阿里云的上传秘钥。代码如下:
import request from '@/request/request'
export function filePolicy() {
return new Promise((resolve, reject)=>{
request.get('/user/file/policy',{}).then(res=>{
console.log("秘钥:",res.data);
resolve(res);
}).catch(err=>{console.log("网络错误",err);});
})
}
我直接拷贝了上面的代码,在本地测试遇到如下问题:
- 上传文件upload有的时候post方法调的url是本地项目服务地址(本地前端服务地址:localhost:3000),造成这额原因是在请求filePolicy这个接口的时候还没有响应结果,upload组件的【action】属性值还未获取到但是在before-upload钩子函数中没有做相应的处理,upload组件就已经在执行上传(post)操作,解决办法见代码
- 解决第一个问题最后总算是上传成功,但是OSS阿里云那边不返回上传文件的地址,阿里云服务没有响应内容;
- 最后通过查资料了解到图片地址是固定变量拼接的,由于我们的有权限设置,所以地址后面还得加`${OSS_FLAG.host}/${basedata.value.key}?OSSAccessKeyId=${basedata.value.OSSAccessKeyId}&Expires=${Expires.value}&Signature=${basedata.value.signature}`,加了之后由于有时间限制,时间一到图片就会报403的错误,解决办法:上传成功在on-success钩子函数中处理,将图片地址改为本地的地址
下面是我的完整代码如下:
upload组件:
<template>
<el-upload
v-model:file-list="fileLists"
:action="actionUrl"
:limit="limit"
:disabled="disabled"
list-type="picture-card"
:auto-upload="true"
:multiple="false"
:data="basedata"
:accept="accept"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:before-upload="beforeUpload"
:on-success="upSuccess"
:class="{ limitOpctin: limit === fileLists.length }"
>
<template #tip>
<div class="tips">
{
{ tips }}
</div>
</template>
<el-icon>
<Plus />
</el-icon>
</el-upload>
<el-dialog v-model="dialogVisible">
<img
style="width: 100%"
w-full
:src="dialogImageUrl"
alt="图片预览"
/>
</el-dialog>
</template>
<script setup lang="ts">
/*
example: <UpLoad :limit='1' v-model:fileLists='ruleForm.fileLists' accept='image/png,image/jpg'/>
*/
import { filePolicy } from '~/api'
import type { UploadProps, UploadUserFile, UploadRawFile ,UploadFile,UploadFiles} from "element-plus";
import { OSS_FLAG } from '~/utils/const'
import { AxiosResponse } from "axios";
import { RESULT } from "~/api/types";
import { Auth, BaseData } from './index'
interface Props {
limit: number;
accept: string;
tips: string;
fileLists: UploadUserFile[];
disabled: boolean;
sizeMax: number,
fileType: string
}
const props = withDefaults(defineProps<Props>(), {
limit: 1,
accept: "image/png,image/jpeg,image/gif,image/jpg",
tips: "",
fileLists: () => [],
disabled: false,
sizeMax: 5,
fileType: 'img'
});
const emit = defineEmits(["update:fileLists"]);
const infoList = ref([]);
const dialogImageUrl = ref("");
const fileName = ref('')
const Expires = ref('')
const percent = ref(0);
// const actionUrl = "api/upload";
const actionUrl = OSS_FLAG.host
const dialogVisible = ref(false);
let basedata = ref<BaseData>(null)
onMounted(() => {
infoList.value = props.fileLists || [];
});
const handleRemove = (uploadFile) => {
infoList.value.forEach((v, i) => {
if (v.uid == uploadFile.uid) {
infoList.value.splice(i, 1);
}
});
emit("update:fileLists", infoList.value);
};
const handlePictureCardPreview = (uploadFile) => {
dialogImageUrl.value = uploadFile.url;
dialogVisible.value = true;
};
const upSuccess = (res:any, file:UploadFile,uploadFiles:UploadFiles) => {
// console.log('success>>>>',res);
// console.log('success>>>>',file.url);
// console.log('success>>>>',uploadFiles);
infoList.value = [
...infoList.value,
{
// url: `${OSS_FLAG.host}/${basedata.value.key}?OSSAccessKeyId=${basedata.value.OSSAccessKeyId}&Expires=${Expires.value}&Signature=${basedata.value.signature}`,
url:file.url,//由于图片会过期,所以url使用本地图片路径
name: fileName.value,
fileName:basedata.value.key
// url: 'https://snc-ai-prod-oss.oss-cn-hangzhou.aliyuncs.com/gamehub/1663662037237avatar.png?OSSAccessKeyId=LTAI5tHB6AeMcUBz85fxHjm6&Expires=1663671680&Signature=v%2FVJDq4F7HcDzcNqiV6NfxtnBmg%3D'
}
]
emit("update:fileLists", infoList.value);
}
const beforeUpload = async (rawFile: UploadRawFile) => {
const { size, name, uid } = rawFile;
console.log('UploadRawFile>>>>>>', rawFile);
if (size / 1024 / 1024 > props.sizeMax) {
ElMessage.error(`文件大小超过了 ${props.sizeMax}MB!`)
return false
}
//以下处理一定要加,不然会出现问题1
try {
const result: any = await filePolicy()
const data: Auth = result.data
fileName.value = rawFile.name
Expires.value = data.expire
basedata.value = {
key: data.dir + props.fileType + '/' + rawFile.uid + '_' + rawFile.name,
success_action_status: '200',
policy: data.policy,
OSSAccessKeyId: data.accessId,
signature: data.signature
}
return true
} catch (err) {
return false
}
}
</script>
<style lang="scss" scoped>
.limitOpctin :deep(.ep-upload--picture-card) {
display: none !important;
}
:deep(.ep-upload-list__item .ep-icon--close-tip) {
display: none !important;
}
.tips {
color: #999 !important;
font-size: 12px;
}
</style>
父组件:
<el-form
ref="ruleFormRef"
:model="ruleForm"
:rules="rules"
label-width="140px"
class="ruleForm-add"
>
<el-form-item
label="icon图标:"
prop="gameIco"
>
<Upload
v-model:fileLists="ruleForm.gameIco"
:tips="TIPS.icon"
/>
</el-form-item>
</el-form-item>
后端filePolicy接口返回参数如下
方法二阿里云文档地址:https://help.aliyun.com/document_detail/31926.html