最近开发项目过程中需要有一些图片上传的功能,本想用普通的上传方式,即后台提供上传的地址,前端直接调用的方法,但此方法有一点缺点,用户数据需先上传到应用服务器,之后再上传到OSS。网络传输时间比直传到OSS多一倍。如果用户数据不通过应用服务器中转,而是直传到OSS,速度将大大提升。为了提高上传效率,决定使用服务端签名后直传的方法来实现
服务端签名后直传的原理如下:
- 用户发送上传Policy请求到应用服务器
- 应用服务器返回上传Policy和签名给用户
- 用户直接上传数据到OSS
1、先创建一个upload.vue进行封装方便其它组件使用
<template>
<div class="el-upload-model">
<el-upload class="el-avatar-uploader"
action="#"
ref="upload"
:show-file-list="false"
:http-request="handleUploadImg"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload">
<img class="el-avatar" v-if="pictureUrl" v-lazy="pictureUrl">
<i v-else class="el-upload-icon">
<i class="el-icon-plus avatar-uploader-icon"></i>
</i>
</el-upload>
<div class="el-progress-grid">
<el-progress class="el-progressbar"
type="circle"
v-if="progressFlag"
color="#67c23a"
:percentage="progressPercent">
</el-progress>
</div>
</div>
</template>
<script>
// 获取阿里云oss token api
import { getOssToken } from '@/api/user'
export default {
name: 'uploadCompt',
props: {
pictureUrl: {
type: String,
default: ''
}
},
data () {
return {
imageUrl: '',
progressFlag: false,
progressPercent: 0
}
},
methods: {
// 截取上传文件后缀
getSuffix(filename) {
let pos = filename.lastIndexOf('.')
let suffix = ''
if (pos != -1) suffix = filename.substring(pos)
return suffix
},
// 生成随机数
randomString(len) {
len = len || 32
const chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'
const maxPos = chars.length
let pwd = ''
for (let i = 0; i < len; i++) {
pwd += chars.charAt(Math.floor(Math.random() * maxPos))
}
return pwd
},
beforeAvatarUpload(file) {
const isJPG = file.type === 'image/png' || file.type === 'image/gif' || file.type === 'image/jpg' || file.type === 'image/jpeg' || file.type === 'image/svg+xml'
const isLt1M = file.size / 1024 / 1024 < 1
const isSize = new Promise((resolve, reject) => {
let width = 1080
let height = 540
let _URL = window.URL || window.webkitURL
let img = new Image()
img.onload = () => {
let valid = img.width <= width && img.height <= height
valid ? resolve() : reject()
}
img.src = _URL.createObjectURL(file)
}).then(() => {
return file
}).catch((err) => {
this.$message.warning('上传的图片像素必须是小于或等于1080px * 540px!')
return Promise.reject()
})
if (!isJPG) {
this.$message.warning('上传的图片只能是PNG、GIF、JPG、JPEG、SVG格式!')
} else if (!isLt1M) {
this.$message.warning('上传的图片大小不能超过1MB!')
}
return isJPG && isLt1M && isSize
},
handleUploadImg(file) {
getOssToken().then((res) => {
if (res.code && res.code === 200) {
let picName = this.randomString(10) + this.getSuffix(file.file.name)
let keyValue = res.data.dir + '/' + picName
//注意formData里append添加的键的大小写
let formData = new FormData()
formData.append('name', file.file.name) // 文件名称
formData.append('key', keyValue) // 存储在oss的文件路径
formData.append('OSSAccessKeyId', res.data.accessid) // //accessKeyId
formData.append('policy', res.data.policy) // policy
formData.append('Signature', res.data.signature) //签名
formData.append('success_action_status', 200)
formData.append('file', file.file, file.file.name) // 如果是base64文件,那么直接把base64字符串转成blob对象进行上传即可
this.progressFlag = true
return new Promise((resolve, reject) => {
this.$axios.post(res.data.accessHost, formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
// 图片上传进度
onUploadProgress: (progressEvent) => {
this.progressPercent = Math.floor((progressEvent.loaded * 100) / progressEvent.total)
}
}).then((rep) => {
if (rep.status === 200) {
this.imageUrl = res.data.accessHost + '/' + keyValue
this.$emit('uploadSuccess', this.imageUrl)
console.log('Uploaded successfully', rep)
if (this.progressPercent >= 100) {
this.progressFlag = false
setTimeout(() => {
this.progressPercent = 0
},1000)
}
}
resolve(rep)
}).catch((err) => {
reject(err)
})
})
}
}).catch((err) => {
console.log(err)
})
},
handleRemove(file) {
this.$refs.upload.abort()
this.$message({
message: '成功移除' + file.name,
type: 'success'
})
},
handleAvatarSuccess(res, file) {
this.imageUrl = URL.createObjectURL(file.raw)
}
}
}
</script>