下面我将详细讲解“Vue 文件切片上传的项目实现”的完整攻略。该过程主要分为以下五个步骤:
- 安装依赖库
开发项目之前需要先安装以下库:
- axios:用于发起后端请求;
- element-ui:基于 Vue 的组件库,提供了上传文件的组件;
- js-sha256:计算文件的哈希值。
可以使用以下命令进行安装:
npm install axios element-ui js-sha256 --save
- 前端实现
在代码中引用axios
、ElementUI
和js-sha256
库:
import axios from 'axios';
import { Upload, Message } from 'element-ui';
import sha256 from 'js-sha256';
接着,使用Upload
组件制作上传文件的用户界面,核心代码如下:
<template>
<div>
<el-upload
ref="upload"
:action="serverPath"
:auto-upload="false"
:on-exceed="handleExceed"
:file-list="fileList"
:http-request="uploadChunk"
:before-remove="beforeRemove"
:on-progress="handleProgress"
:on-success="handleSuccess"
:on-error="handleError"
multiple>
<el-button type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">限制上传个数和单个文件大小</div>
</el-upload>
</div>
</template>
其中,ElUpload
组件的各项属性含义如下:
action
:文件上传后端接口地址;auto-upload
:是否在文件选取后立即上传;on-exceed
:文件数量超出限制时的回调函数;file-list
:用于展示已上传的文件列表;http-request
:发起上传请求的回调函数;before-remove
:文件删除前的确认提示;on-progress
:文件上传进度的回调函数;on-success
:文件上传成功的回调函数;on-error
:文件上传失败的回调函数;multiple
:是否允许多个文件上传。
接下来是文件上传的协议:
const uploadChunk = ({ file, onSuccess, onError }) => {
const fileName = `${sha256(file.name)}-${file.size}`;
const totalChunk = Math.ceil(file.size / CHUNK_SIZE);
let uploadedChunks = 0;
const loadNext = (chunkId) => {
const start = chunkId * CHUNK_SIZE;
const end = start + CHUNK_SIZE >= file.size ? file.size - 1 : start + CHUNK_SIZE - 1;
const formData = new FormData();
formData.append('chunkId', chunkId);
formData.append('hash', fileName);
formData.append('file', file.slice(start, end + 1));
axios.post(`${serverPath}/${fileName}`, formData)
.then((res) => {
uploadedChunks += 1;
if (uploadedChunks < totalChunk) {
loadNext(chunkId + 1);
} else {
onSuccess({ fileName, fileUrl: res.data });
}
})
.catch((err) => {
onError(err);
});
};
loadNext(0);
};
该协议将文件切成等大小的媒体块,并发送块的哈希值和块本身作为 POST 数据,因此要吸收每块上传的哈希值。当所有块上传成功时,后端服务器返回成功上传的文件地址并通过onSuccess
函数在前端显示。否则,后端服务器没有必要将每块上传的哈希值保存到磁盘上,而是直接返回错误响应。
- 后端实现
后端需要编写一个接口用于处理上传文件的请求,并在该接口中实现请求中上传的媒体块的处理逻辑,这里以 Flask 为例:
from flask import Flask, request, jsonify
import os
from werkzeug.utils import secure_filename
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = '/tmp'
@app.route('/upload/<string:hash>', methods=['POST'])
def upload(hash):
chunk_id = request.form.get('chunkId', type=int)
filename = os.path.join(app.config['UPLOAD_FOLDER'], hash)
if chunk_id == 0:
# First request, remove if file exists
if os.path.exists(filename):
os.remove(filename)
# Save the chunks of file
with open(filename, 'ab') as f:
f.write(request.files['file'].read())
# Return upload file url after all chunk the file saved.
if chunk_id == total_chunk - 1:
return jsonify({'result': os.path.join(app.config['UPLOAD_FOLDER'], hash)})
else:
return '', 200
注意该实现的参数解释:
hash
:上传文件的哈希值;chunkId
:媒体块的 ID;filename
:上传文件名;totalChunk
:媒体块的总数。
这里将每块上传的媒体块存储到文件中,在每次上传媒体块后都会将其添加到同一文件中。传输结束时,返回上传文件的 URL。
- 合并上传的文件
当文件块全部上传完成后,后端服务器将返回上传文件的 URL。这里我们需要编写一个自动合并上传文件的函数:
const mergeChunks = async (fileName, fileUrl) => {
const { data: serverFileUrl } = await axios.post(`${serverPath}/merge`, {
hash: fileName,
fileUrl,
});
return serverFileUrl;
};
在这段代码中,我们向后端服务器发送一个包含上传文件的哈希值和每个块的 URL 的请求。返回的结果是上传文件的所有块已经成功上传到服务器并被合并。合并完成后,我们将接收到的上传文件的 URL 作为最终结果返回。
- 完整代码
完整代码如下:
<template>
<div>
<el-upload
ref="upload"
:action="serverPath"
:auto-upload="false"
:on-exceed="handleExceed"
:file-list="fileList"
:http-request="uploadChunk"
:before-remove="beforeRemove"
:on-progress="handleProgress"
:on-success="handleSuccess"
:on-error="handleError"
multiple>
<el-button type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">限制上传个数和单个文件大小</div>
</el-upload>
</div>
</template>
<script>
import axios from 'axios';
import { Upload, Message } from 'element-ui';
import sha256 from 'js-sha256';
const serverPath = '/upload';
const CHUNK_SIZE = 1024 * 1024;
export default {
name: 'FileUpload',
data() {
return {
fileList: [],
};
},
methods: {
handleExceed(files, fileList) {
Message.warning(`只能选择一个或者一个以上文件`);
},
beforeRemove(file, fileList) {
const fileName = `${sha256(file.name)}-${file.size}`;
axios.delete(`${serverPath}/${fileName}`)
.then((res) => {
if (res.status === 204) {
Message.success(`删除文件 ${file.name} 成功`);
}
})
.catch((err) => {
Message.error(`删除文件 ${file.name} 失败`);
});
},
handleProgress({ file, fileList }) {
const uploadPercent = Math.ceil(file.uploadedChunks / file.totalChunk * 100);
file.percent = uploadPercent;
this.fileList = [...fileList];
},
handleSuccess({ fileName, fileUrl }, file, fileList) {
this.mergeChunks({ fileName, fileUrl })
.then((url) => {
file.url = url;
this.fileList = [...fileList];
Message.success(`${file.name} 上传完成`);
})
.catch((err) => {
Message.error(`${file.name} 上传失败`);
});
},
handleError({ action, response, file, fileList }) {
Message.error(`${file.name} 上传失败`);
},
mergeChunks: async (fileName, fileUrl) => {
const { data: serverFileUrl } = await axios.post(`${serverPath}/merge`, {
hash: fileName,
fileUrl,
});
return serverFileUrl;
},
uploadChunk: ({ file, onSuccess, onError }) => {
const fileName = `${sha256(file.name)}-${file.size}`;
const totalChunk = Math.ceil(file.size / CHUNK_SIZE);
let uploadedChunks = 0;
const loadNext = (chunkId) => {
const start = chunkId * CHUNK_SIZE;
const end = start + CHUNK_SIZE >= file.size ? file.size - 1 : start + CHUNK_SIZE - 1;
const formData = new FormData();
formData.append('chunkId', chunkId);
formData.append('hash', fileName);
formData.append('file', file.slice(start, end + 1));
axios.post(`${serverPath}/${fileName}`, formData)
.then((res) => {
uploadedChunks += 1;
if (uploadedChunks < totalChunk) {
loadNext(chunkId + 1);
} else {
onSuccess({ fileName, fileUrl: res.data });
}
})
.catch((err) => {
onError(err);
});
};
loadNext(0);
},
},
};
</script>
再看一段后端 Flask 代码:
from flask import Flask, request, jsonify
import os
from werkzeug.utils import secure_filename
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = '/tmp'
@app.route('/upload/<string:hash>', methods=['POST'])
def upload(hash):
chunk_id = request.form.get('chunkId', type=int)
filename = os.path.join(app.config['UPLOAD_FOLDER'], hash)
if chunk_id == 0:
# First request, remove if file exists
if os.path.exists(filename):
os.remove(filename)
# Save the chunks of file
with open(filename, 'ab') as f:
f.write(request.files['file'].read())
# Return upload file url after all chunk the file saved.
if chunk_id == total_chunk - 1:
return jsonify({'result': os.path.join(app.config['UPLOAD_FOLDER'], hash)})
else:
return '', 200
@app.route('/upload/merge', methods=['POST'])
def merge():
hash = request.form.get('hash', type=str)
file_url = request.form.get('fileUrl', type=str)
filename = os.path.join(app.config['UPLOAD_FOLDER'], hash)
with open(filename, 'ab') as f:
for chunk_url in file_url:
with open(chunk_url, 'rb') as chunk:
f.write(chunk.read())
# delete chunk file
for chunk_url in file_url:
os.remove(chunk_url)
return jsonify({'result': os.path.join(app.config['UPLOAD_FOLDER'], hash)})
注意在该代码中的参数解释:
hash
:上传文件的哈希值;chunkId
:媒体块的 ID;filename
:上传文件名;totalChunk
:媒体块的总数。
以上就是实现 Vue 文件切片上传的完整攻略。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:vue 文件切片上传的项目实现 - Python技术站