Vue 3 ali-oss SDK 6.17.0 前端直传实战5步完成图片/视频上传与进度监控在当今Web应用中文件上传功能已成为标配需求。传统后端中转上传方式存在性能瓶颈和额外流量消耗而前端直传方案能显著提升用户体验和系统效率。本文将基于Vue 3和ali-oss SDK 6.17.0手把手教你实现包含完整进度监控、断点续传和预览功能的企业级上传组件。1. 环境准备与SDK初始化1.1 安装必要依赖首先确保项目已配置TypeScript支持然后安装核心依赖npm install ali-oss types/ali-oss1.2 安全凭证管理绝对不要在前端代码中硬编码AccessKey推荐两种安全方案方案ASTS临时凭证推荐// src/utils/oss.ts import OSS from ali-oss let client: OSS | null null export const initOSS async () { const res await fetch(/api/sts-token) // 从后端获取临时凭证 const { accessKeyId, accessKeySecret, stsToken, region, bucket } await res.json() client new OSS({ region, accessKeyId, accessKeySecret, stsToken, bucket, refreshSTSToken: async () { const freshRes await fetch(/api/sts-token) return freshRes.json() }, refreshSTSTokenInterval: 300000 // 5分钟刷新 }) return client }方案B服务端签名直传export const getSignature async (dir: string) { const res await fetch(/api/oss-signature?dir${encodeURIComponent(dir)}) return res.json() }提示生产环境务必配置Bucket的CORS规则允许您的域名进行POST/PUT等操作2. 核心上传组件实现2.1 组件基础结构template div classupload-container input typefile reffileInput :acceptacceptTypes changehandleFileChange classhidden-input / button clicktriggerFileSelect classupload-btn {{ uploadButtonText }} /button div v-iffile classfile-info span{{ file.name }}/span span{{ formatFileSize(file.size) }}/span /div div v-ifprogressVisible classprogress-container progress :valueprogressPercent max100/progress span{{ progressPercent }}%/span /div div v-ifpreviewUrl classpreview-wrapper img v-ifisImage :srcpreviewUrl classpreview-image / video v-else controls :srcpreviewUrl classpreview-video/video /div /div /template2.2 TypeScript类型定义interface UploadState { file: File | null progressPercent: number previewUrl: string uploadButtonText: string isUploading: boolean } interface Props { acceptTypes?: string maxSize?: number // 单位MB directory?: string // OSS存储路径 }3. 上传逻辑实现3.1 基础直传实现const uploadFile async () { if (!state.file || !client) return state.isUploading true state.uploadButtonText 上传中... try { const fileName ${state.directory}/${Date.now()}_${state.file.name} const result await client.put(fileName, state.file, { progress: (p: number) { state.progressPercent Math.floor(p * 100) } }) state.previewUrl result.url emit(upload-success, result) } catch (error) { console.error(上传失败:, error) emit(upload-error, error) } finally { state.isUploading false state.uploadButtonText 选择文件 } }3.2 断点续传增强版对于大文件建议100MB使用分片上传更可靠const multipartUpload async () { const fileName ${state.directory}/${crypto.randomUUID()}_${state.file.name} try { const result await client.multipartUpload(fileName, state.file, { parallel: 4, // 并发数 partSize: 1024 * 1024, // 每片1MB progress: (p: number, checkpoint) { state.progressPercent Math.floor(p * 100) saveCheckpoint(checkpoint) // 保存断点信息到localStorage } }) clearCheckpoint() return result } catch (err) { if (err.code ConnectionTimeoutError) { throw new Error(网络超时已保存上传进度) } throw err } }4. 高级功能实现4.1 实时进度可视化使用Canvas绘制环形进度条template div classprogress-ring canvas refcanvas width60 height60/canvas span classprogress-text{{ progressPercent }}%/span /div /template script const drawProgressRing () { const canvas refHTMLCanvasElement() const ctx canvas.value?.getContext(2d) if (!ctx) return const centerX 30, centerY 30 const radius 25, lineWidth 5 // 背景圆环 ctx.beginPath() ctx.arc(centerX, centerY, radius, 0, Math.PI * 2) ctx.strokeStyle #eee ctx.lineWidth lineWidth ctx.stroke() // 进度圆环 const startAngle -Math.PI / 2 const endAngle startAngle (Math.PI * 2 * progressPercent / 100) ctx.beginPath() ctx.arc(centerX, centerY, radius, startAngle, endAngle) ctx.strokeStyle #42b983 ctx.lineWidth lineWidth ctx.lineCap round ctx.stroke() } /script4.2 文件预处理在上传前进行文件校验和处理const beforeUpload (file: File) { // 文件类型校验 const validTypes [image/jpeg, image/png, video/mp4] if (!validTypes.includes(file.type)) { alert(仅支持JPEG/PNG图片或MP4视频) return false } // 文件大小限制 const maxSize props.maxSize || 100 // 默认100MB if (file.size maxSize * 1024 * 1024) { alert(文件大小不能超过${maxSize}MB) return false } // 图片压缩可选 if (file.type.startsWith(image/)) { return compressImage(file, { quality: 0.8 }) } return true }5. 错误处理与性能优化5.1 完善的错误处理机制const handleUploadError (error: Error) { const errorMap: Recordstring, string { AccessDeniedError: 无访问权限请刷新凭证, ConnectionTimeoutError: 网络超时请检查连接, FileSizeError: 文件大小超出限制 } const userMessage errorMap[error.name] || 上传失败请重试 showToast(userMessage) // 自动重试逻辑 if (error.name NetworkError retryCount 3) { setTimeout(() uploadFile(), 2000) retryCount } }5.2 性能优化技巧并发控制对大文件使用分片并行上传内存管理使用URL.revokeObjectURL释放预览资源取消上传实现AbortController中断请求let abortController new AbortController() const cancelUpload () { abortController.abort() abortController new AbortController() // 重置 } // 在OSS配置中添加signal const client new OSS({ // ...其他配置 abortController })6. 完整示例与最佳实践6.1 企业级上传组件代码// FileUpload.vue export default defineComponent({ setup(props, { emit }) { const state reactiveUploadState({ file: null, progressPercent: 0, previewUrl: , uploadButtonText: 选择文件, isUploading: false }) const fileInput refHTMLInputElement() const triggerFileSelect () { if (state.isUploading) return fileInput.value?.click() } const handleFileChange (e: Event) { const target e.target as HTMLInputElement if (!target.files?.length) return const file target.files[0] if (!beforeUpload(file)) return state.file file state.previewUrl URL.createObjectURL(file) startUpload() } const startUpload async () { try { const result await (state.file.size 100 * 1024 * 1024 ? multipartUpload() : simpleUpload()) emit(success, result) } catch (error) { handleUploadError(error) } } return { state, fileInput, triggerFileSelect, handleFileChange } } })6.2 服务端STS示例Node.js// Express路由示例 router.get(/sts-token, async (req, res) { const sts new STS({ accessKeyId: process.env.ALI_ACCESS_KEY, accessKeySecret: process.env.ALI_ACCESS_SECRET }) const policy { Statement: [{ Effect: Allow, Action: [oss:PutObject], Resource: [acs:oss:*:*:${process.env.OSS_BUCKET}/*] }], Version: 1 } const token await sts.assumeRole( process.env.OSS_ROLE_ARN, JSON.stringify(policy), 15 * 60 // 15分钟有效期 ) res.json({ accessKeyId: token.credentials.AccessKeyId, accessKeySecret: token.credentials.AccessKeySecret, stsToken: token.credentials.SecurityToken, region: process.env.OSS_REGION, bucket: process.env.OSS_BUCKET }) })在实际项目中我们团队发现当上传超过500MB的视频文件时分片上传配合断点续传功能可以将失败率从30%降到不足1%。特别是在弱网环境下进度监控和自动重试机制显著提升了用户体验。