最近项目中要做一个大文件上传的功能,既然是大文件(以G为单位),那么肯定不能使用常规的上传方式,最先想到的就是分片上传。又因为我们项目中使用的是阿里云 oss 储存服务,所以分片上传使用其官方提供的接口即可,我们只要做好对接就行了。


阿里云分片上传官方文档: https://help.aliyun.com/zh/oss/user-guide/multipart-upload


这个功能其实前后端都可以做,因为官方都提供了对应的 SDK,经过讨论最终决定让前端来实现。既然如此,那么我们 Vue 项目中需要先安装阿里云 oss 的 SDK。SDK 文档:https://help.aliyun.com/zh/oss/developer-reference/browser-js

npm install ali-oss

其次是需要后端配合的一些东西:

1- 后端需要提供一个接口,用于生成 oss 的上传凭证(AccessKeyId、AccessKeySecret、Bucket 等),因为并不是谁都可以往你的 oss 服务器上传文件的,需要进行身份验证。具体的可以查看授权方式章节,我们选用的是方式一:STS 临时访问凭证。

2- 后端需要去阿里云 oss 后台创建一个 Bucket,也就是存放文件的容器。并且需要配置 Bucket 的访问权限,允许前端访问,不然会有跨域问题。


上面这些都准备好之后,前端就可以开始实现分片上传了。为了方便复用,所以我写了一个大文件上传的组件,可以在 Vue 项目中方便地使用。


big-file-upload.vue

<template>
    <div class="big-file-box">
        <div class="big-file" :style="{width: width + 'px', height: height + 'px'}">
            <input type="file" :accept="accept" ref="file" style="opacity: 0;" class="file-input" @change="handleFileChange">
            <el-button type="primary">{{btnText}}</el-button>
        </div>
        <div v-if="isError" style="color: red;">上传中断,请点击进度条后面的按钮【继续上传】</div>
        <div v-if="isUploading && !isFinished && !isError" style="color: #666;">正在上传,请等待…</div>
        <div style="display: flex; align-items: center; gap:5px;">
            <el-progress :percentage="progress" v-if="isUploading && !isFinished" style="flex: 1;"></el-progress>
            <el-button type="text" @click="resumeUpload" v-if="abortCheckpoint && isError">继续上传</el-button>
        </div>
    </div>
</template>

<script>
import OSS from 'ali-oss'
export default {
    name: "BigFileUpload",
    props: {
        btnText: {
            type: String,
            default: "点击上传文件"
        },
        width: {
            type: Number,
            default: 180
        },
        height: {
            type: Number,
            default: 38
        },
        accept: {
            type: String,
            default: "*"
        }
    },
    data() {
        return {
            file: null,
            progress: 0,
            isUploading: false, // 是否正在上传
            isError: false, // 是否上传错误
            isFinished: false, // 是否上传完成
            abortCheckpoint: null,
            client: null,
        }
    },
    mounted() {
        this.getSign()
    },
    methods: {
        // 请求后端接口获取阿里云 oss 临时凭证 STS Token
        getSign() {
            $request.api('common/get_sts_token', {}, (status, data, msg) => {
                if (status != 1) {
                    this.$alert(msg)
                    return
                }
                this.initOssClient(data)
            })
        },

        // 初始化oss客户端
        initOssClient(sign) {
            this.client = new OSS({
                region: 'oss-cn-hangzhou', // 这里填写你的 oss 区域,我们服务器位于杭州,所以填的杭州
                authorizationV4: true,
                accessKeyId: sign.AccessKeyId,
                accessKeySecret: sign.AccessKeySecret,
                stsToken: sign.SecurityToken,
                bucket: '这里填写后端创建的 bucket 的名称'
            })
        },

        async uploadFile(checkpoint) {
            if (!this.file) {
                this.$alert("请选择文件")
                return
            }

            this.isUploading = true
            try {
                const suffix = this.getFileSuffix(this.file.name)
                const result = await this.client.multipartUpload(
                    'big-file/' + Date.now() + '.' + suffix, // 将文件以时间戳命名,避免重复。 big-file 是文件存放的层级。(注意: oss 是没有文件夹概念的)
                    this.file,
                    {
                        checkpoint: checkpoint,
                        progress: (p, cpt, res) => {
                            this.abortCheckpoint = cpt // 将断点信息保存下来,用于后续的断点续传
                            this.progress = Math.round(p * 100) // 进度条显示百分比
                        },
                        mime: "text/plain",
                        parallel: 4, // 设置并发上传的分片数量。
                        partSize: 1024 * 1024 * 1, // 设置每个分片的大小,默认值是 1MB
                    }
                )

                // 上传成功后清理状态
                this.abortCheckpoint = null;
                this.progress = 0;
                this.isError = false

                // 进度条延迟1秒后消失
                setTimeout(() => { this.isUploading = false;this.isFinished = true }, 1000)

                // 上传成功,将结果回传给父组件
                this.$emit('success', result.res)
            } catch(e) {
                if (e.code === 'SecurityTokenExpired' || e.code === 'InvalidSecurityToken') {
                    this.getSign()
                }
                this.isError = true // 上传错误
                this.isFinished = false
                console.log(e.message)
            }
        },

        // 获取文件后缀
        getFileSuffix (fileName) {
            return fileName.split('.').pop()
        },

        async handleFileChange(e) {
            this.file = e.target.files[0]
            
            // 选择新文件后,重置所有状态
            this.abortCheckpoint = null;
            this.progress = 0;
            this.isUploading = false
            this.isFinished = false
            this.isError = false

            this.uploadFile(null)
        },

        // 断点续传
        resumeUpload() {
            if (this.abortCheckpoint) {
                this.isError = false
                this.isFinished = false
                this.isUploading = true
                this.uploadFile(this.abortCheckpoint)
            } else {
                this.$toast('没有可用的断点信息,请重新开始上传')
            }
        },
    }
}
</script>

<style lang="less" scoped>
.big-file {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    position: relative;
    min-width: 100px;
    .file-input {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        cursor: pointer;
    }
}
</style>

组件写好了,下面就是在 Vue 项目中使用它了

<template>
    <div>
        <BigFileUpload btnText="点击上传案例视频(mp4格式)" :width="200" accept=".mp4" @success="handleSuccessVideo" />
    </div>
</template>

<script>
import BigFileUpload from './big-file-upload.vue'
export default {
    components: {
        BigFileUpload
    },
    data() {
        return {
            video_list: []
        }
    },
    methods: {
        handleSuccessVideo(result) {
            const url = new URL(result.requestUrls[0]);
            this.video_list.push(url.pathname)
        },
    }
}
</script>

页面效果截图


1- 正常状态

2- 中断状态


最后需要注意的是,阿里云 oss 分片上传是收费的,按实际请求数进行收费,具体的收费标准见下图


关于收费方面的官方说明如下,分片上传主要用到的是接口请求的费用

对象存储 OSS 按实际使用量付费。将数据存储到 OSS 后,会按存储时长产生存储费用;每次上传、下载或访问数据产生请求费用;通过公网分发数据则会产生外网流出流量费用。此外,启用 CDN 加速、图片处理、传输加速、DDoS 防护等增值服务,会产生对应的增值计费项。如果业务的访问模式稳定,具备可预期的存储量、请求量和外网流出流量,建议根据账单中的具体计费项购买资源包进行抵扣,以降低成本。资源包必须与计费项精准对应(如下行流量包仅能抵扣外网流出流量费用),买错将导致无法抵扣。

本文最后更新于 2026-07-09 11:55:08VUE
天生我材必有用,千金散尽还复来~~
作者:鄢云峰 YYF声明:转载请注明文章出处地址:https://yanyunfeng.com/article/92
评论
提交
来发第一个评论啦~