背景
每个OSS的用户都会用到上传服务。Web端常见的上传方法是用户在浏览器或App端上传文件到应用服务器,应用服务器再把文件上传到OSS。具体流程如下图所示。
和数据直传到OSS相比,以上方法有三个缺点:
- 上传慢:用户数据需先上传到应用服务器,之后再上传到OSS。网络传输时间比直传到OSS多一倍。如果用户数据不通过应用服务器中转,而是直传到OSS,速度将大大提升。而且OSS采用BGP带宽,能保证各地各运营商之间的传输速度。
- 扩展性差:如果后续用户多了,应用服务器会成为瓶颈。
- 费用高:需要准备多台应用服务器。由于OSS上传流量是免费的,如果数据直传到OSS,不通过应用服务器,那么将能省下几台应用服务器。
后端说明
关于服务端签名,参考链接:
https://help.aliyun.com/document_detail/31926.html
前端实现
关于前端直传,参考链接:
https://help.aliyun.com/document_detail/31925.html
相关依赖
"ali-oss": "^6.2.1"
定义工具类
// 引入ali-oss
let OSS = require('ali-oss')
/**
* data后端提供数据
* [accessKeyId] {String}:通过阿里云控制台创建的AccessKey。
* [accessKeySecret] {String}:通过阿里云控制台创建的AccessSecret。
* [bucket] {String}:通过控制台或PutBucket创建的bucket。
* [region] {String}:bucket所在的区域, 默认oss-cn-hangzhou。
*/
export function client(data) {
return new OSS({
region: data.region,
accessKeyId: data.accessKeyId,
accessKeySecret: data.accessKeySecret,
stsToken: data.stsToken,
bucket: data.bucket
})
}
/**
* 生成随机文件名称
* 规则八位随机字符,加下划线连接时间戳
*/
export const getFileNameUUID = () => {
function rx() {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)
}
return `${+new Date()}_${rx()}${rx()}`
}
创建实例
<template>
<div>
<!--上传组件-->
<el-upload
v-if="uploadStyle===0"
action=""
list-type="picture-card"
:file-list="fileList"
:auto-upload="true"
:on-exceed="handleExceed"
:before-upload="handleBeforeUpload"
:http-request="handleUploadFile"
:on-preview="handleFileCardPreview"
:on-remove="handleRemove">
<i class="el-icon-plus"></i>
</el-upload>
<el-upload
v-if="uploadStyle===1"
class="upload-demo"
action=""
list-type="picture"
:file-list="fileList"
:auto-upload="true"
:on-exceed="handleExceed"
:before-upload="handleBeforeUpload"
:http-request="handleUploadFile"
:on-preview="handleFileCardPreview"
:on-remove="handleRemove">
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
<el-upload
v-if="uploadStyle===2"
class="upload-demo"
drag
multiple
action=""
:file-list="fileList"
:auto-upload="true"
:on-exceed="handleExceed"
:before-upload="handleBeforeUpload"
:http-request="handleUploadFile"
:on-preview="handleFileCardPreview"
:on-remove="handleRemove">
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
</el-upload>
<!--进度条-->
<el-progress
v-show="showProgress"
:text-inside="true"
:stroke-width="15"
:percentage="progress"
></el-progress>
<!--展示上传的文件-->
<el-dialog :title="dialogFileUrlTitle"
:visible.sync="dialogFileUrlVisible"
:close-on-click-modal="false"
:close-on-press-escape="false"
:destroy-on-close="true"
append-to-body>
<div v-if="dialogFileFormat==='image'">
<img width="100%" :src="dialogFileUrl" alt="">
</div>
<div v-else-if="dialogFileFormat==='video'">
<video
controls
controlslist="nodownload"
preload="auto"
style="width: 98%;text-align: center"
:src="dialogFileUrl">
</video>
</div>
<div v-else>
<el-alert
title="当前格式暂不支持预览!"
type="error"
center
show-icon>
</el-alert>
</div>
</el-dialog>
</div>
</template>
数据方法
import { client, getFileNameUUID } from '@/utils/ali-oss'
// 获取后端鉴权的接口,具体看自己业务实现。
import { getStsPermission } from '@/api/common/common'
export default {
name: 'AliUpload',
created() {
this.initialize()
},
data() {
return {
// 上传样式 0:卡片照片墙 1:缩略图 2:拖拽上传文件
uploadStyle: 0,
// oss上传所需数据对象
ossData: {},
// 文件列表
fileList: [],
// 进度条的显示
showProgress: false,
// 进度条数据
progress: 0,
// 文件参数表单
fileParams: {
// 上传的目录
folder: '/default/'
},
// 展示上传
dialogFileUrlVisible: false,
dialogFileUrlTitle: '',
dialogFileUrl: '',
dialogFileFormat: ''
}
},
model: {
event: 'complete',
prop: ''
},
// 组件插槽
props: {
// 上传位置
position: {
required: true
},
// 使用样式
styleType: {},
// 展示图片
showFile: {
type: String
}
},
// 数据监听
watch: {
showFile: {
handler() {
this.onShowFile()
}
},
position: {
handler() {
this.onPosition()
}
}
},
methods: {
/** 组件初始化 */
initialize() {
this.onPosition()
this.onShowFile()
this.uploadStyle = this.styleType
},
/** showFile */
onShowFile() {
console.log('showFileUrl:' + this.showFile)
if (this.showFile) {
let url = this.showFile
let name = '点击预览文件'
this.fileList = [{ name, url }]
} else {
this.fileList = []
}
},
/** position */
onPosition() {
if (this.position === '' || this.position == null) {
this.$message({ message: '组件初始化失败!缺少[position]', type: 'warning' })
return
}
switch (this.position) {
case 'default':
this.fileParams.folder = '/default/'
break
case 'system':
this.fileParams.folder = '/system/'
break
case 'post':
this.fileParams.folder = '/post/'
break
case 'circle':
this.fileParams.folder = '/circle/'
break
case 'newsPicture':
this.fileParams.folder = '/news/picture/'
break
case 'newsVideo':
this.fileParams.folder = '/news/video/'
break
case 'liveGift':
this.fileParams.folder = '/live/gift/'
break
default:
this.$message({ message: '组件初始化失败!未知[position]', type: 'warning' })
return
}
},
/** 上传文件之前 */
handleBeforeUpload(file) {
this.$emit('upload-success', false)
// 加载OSS配置参数
return new Promise((resolve, reject) => {
getStsPermission().then(response => {
this.ossData = response.data
resolve(true)
}).catch(error => {
this.$message({
message: '加载上传配置失败!error msg:' + error,
type: 'warning'
})
reject(false)
})
})
},
/** 文件超出个数限制 */
handleExceed(files, fileList) {
this.$message({
message: '停!不能再多了~',
type: 'warning'
})
},
/** 文件列表移除文件 */
handleRemove(file, fileList) {
this.$message({
message: '成功移除一个文件~',
type: 'success'
})
},
/** 点击文件列表中已上传的文件 */
handleFileCardPreview(file) {
let fileFormat = this.judgeFileFormat(file.url)
switch (fileFormat) {
case 'image':
this.dialogFileUrlTitle = '图片预览'
break
case 'video':
this.dialogFileUrlTitle = '视频预览'
break
default:
this.$message.error(`当前格式为${fileFormat},暂不支持预览!`)
return
}
this.dialogFileFormat = fileFormat
this.dialogFileUrl = file.url
this.dialogFileUrlVisible = true
},
/** 根据URL判断文件格式 */
judgeFileFormat(fileUrl) {
// 获取最后一个.的位置
const index = fileUrl.lastIndexOf('.')
// 获取后缀
const suffix = fileUrl.substr(index + 1)
console.log(`当前文件后缀格式为 suffix: ${suffix}`)
// 获取类型结果
let result = ''
// 图片格式
const imgList = ['png', 'jpg', 'jpeg', 'bmp', 'gif']
// 进行图片匹配
result = imgList.find(item => item === suffix)
if (result) {
return 'image'
}
// 匹配txt
const txtList = ['txt']
result = txtList.find(item => item === suffix)
if (result) {
return 'txt'
}
// 匹配 excel
const excelList = ['xls', 'xlsx']
result = excelList.find(item => item === suffix)
if (result) {
return 'excel'
}
// 匹配 word
const wordList = ['doc', 'docx']
result = wordList.find(item => item === suffix)
if (result) {
return 'word'
}
// 匹配 pdf
const pdfList = ['pdf']
result = pdfList.find(item => item === suffix)
if (result) {
return 'pdf'
}
// 匹配 ppt
const pptList = ['ppt', 'pptx']
result = pptList.find(item => item === suffix)
if (result) {
return 'ppt'
}
// 匹配 视频
const videoList = ['mp4', 'm2v', 'mkv', 'rmvb', 'wmv', 'avi', 'flv', 'mov', 'm4v']
result = videoList.find(item => item === suffix)
if (result) {
return 'video'
}
// 匹配 音频
const radioList = ['mp3', 'wav', 'wmv']
result = radioList.find(item => item === suffix)
if (result) {
return 'radio'
}
// 其他 文件类型
return 'other'
},
/** 执行文件上传 */
handleUploadFile(file) {
let that = this
// 选中目录
let folder = that.fileParams.folder
// 环境判断
let active = that.ossData.active
// 正式与测试环境存储分离
if (active !== null && active !== undefined) {
if (active === 'dev' || active === 'test') {
folder = '/test' + folder
}
}
// oss-host
let host = 'https://static.v.xxxxxxxxx.com'
async function multipartUpload() {
let temporary = file.file.name.lastIndexOf('.')
let fileNameLength = file.file.name.length
let fileFormat = file.file.name.substring(
temporary + 1,
fileNameLength
)
// 文件路径和文件名 拼接 文件格式
let fileNameUrl = folder + getFileNameUUID() + '.' + fileFormat
client(that.ossData).multipartUpload(fileNameUrl, file.file, {
progress: function(plan) {
that.showProgress = true
that.progress = Math.floor(plan * 100)
}
}).then(result => {
that.$message({
message: '上传成功',
type: 'success'
})
console.log('上传完成 result:' + JSON.stringify(result))
// 文件上传成功后填充输入框
let uploadResult = host + fileNameUrl
that.$emit('complete', uploadResult)
that.$emit('upload-success', true)
that.showProgress = false
}).catch(error => {
that.$message({
message: '上传失败!error:' + error,
type: 'warning'
})
})
}
multipartUpload()
}
}
}
组件使用
<el-form-item label="新闻封面" prop="sImgUrl">
<a8-ali-upload position="newsPicture"
:styleType="2"
:showFile="saveParams.sImgUrl"
v-model="saveParams.sImgUrl">
</a8-ali-upload>
<el-input style="margin-top: 10px" v-model="saveParams.sImgUrl" placeholder="新闻封面链接"/>
</el-form-item>
以上就是在Vue.js下,阿里云OSS WEB直传的具体应用。
Q.E.D.