赞
踩
目录
将 一个文件 切割为 一系列特定大小的 数据片段,将这些 数据片段 分别上传到服务端;
全部上传完成后,再由服务端将这些 数据片段 合并成为一个完整的资源;
上传过程中,由于外部因素(比如网络波动)导致上传中断,下次上传时会保留该文件的上传进度(断点续传);
包含三部分:
- <el-form-item label="上传附件" prop="uploadFile">
- <el-upload
- v-if="!editForm.inlineAppVersionModel.fileName"
- class="upload-demo"
- drag
- :show-file-list="false"
- :action="APP_MANAGEMENT.uploadFile"
- // 根据项目的接口传递参数
- :data="{
- applicationId: applicationId,
- applicationVersion: applicationVersion,
- bucketName: 'app'
- }"
- // 覆盖默认的http请求
- :http-request="handleFileUpload"
- >
- <el-icon class="el-icon--upload">
- <upload-filled />
- </el-icon>
-
- <div v-if="!progress" class="el-upload__text">
- Drop file here or <em>click to upload</em>
- </div>
-
- // 进度条
- <el-progress
- v-else
- :text-inside="true"
- :stroke-width="24"
- :percentage="progress"
- status="success"
- />
- </el-upload>
-
- // 上传成功之后隐藏上传文件组件
- <div v-else style="display: flex;">
- <el-input v-model="editForm.inlineAppVersionModel.fileName" readonly>
- </el-input>
- <div style="display: flex;">
- <el-button
- type="primary"
- :icon="Download"
- size="small"
- @click="handleFileDownload"
- />
- <el-button type="primary" :icon="Delete" size="small" @click="handleFileDel" />
- </div>
- </div>
- </el-form-item>
使用 el-upload 选择文件
选择成功的 回调函数 可以读取文件信息,用于前端校验文件的合法性
前端校验文件合法后,将文件进行切片
通过 请求轮询 把切片传递给后端
在这一步,可以获得文件信息
根据文件信息,对文件进行合法性校验
校验成功后,调用文件切片方法
- /**
- * @description: 选择上传文件
- * @param file el-upload 返回的参数
- */
- const handleFileUpload = async (file: any) => {
- console.log('el-upload 返回的参数 === ', file.file);
-
- // 如果文件合法,则进行分片上传
- if (await checkMirrorFile(file)) {
- // 文件信息
- const files = file.file;
- // 从 0 开始的切片
- const shardIndex = 0;
- // 调用 文件切片 方法
- uploadFileSilce(files, shardIndex);
- // 文件非法,则进行提示
- } else {
- ElMessage.error('请检查文件是否合法!');
- }
- };
校验文件格式
校验文件大小
调用接口,校验磁盘剩余空间大小
- /**
- * @description: 校验文件合法性
- */
- const checkMirrorFile = async (file) => {
- // 校验文件格式,支持.zip/.tar
- const fileType = file.file.name.split('.')
- if (fileType[fileType.length - 1] !== 'zip' && fileType[fileType.length - 1] !== 'tar') {
- ElMessage.warning('文件格式错误,仅支持 .zip/.tar')
- return false
- }
-
- // 校验文件大小
- const fileSize = file.file.size;
- // 文件大小是否超出 2G
- if (fileSize > 2 * 1024 * 1024 * 1024) {
- ElMessage.warning('上传文件大小不能超过 2G')
- return false
- }
-
- // 调用接口校验文件合法性,比如判断磁盘空间大小是否足够
- const res = await checkMirrorFileApi()
- if (res.code !== 200) {
- ElMessage.warning('暂时无法查看磁盘可用空间,请重试')
- return false
- }
- // 查看磁盘容量大小
- if (res.data.diskDevInfos && res.data.diskDevInfos.length > 0) {
- let saveSize = 0
- res.data.diskDevInfos.forEach(i => {
- // 磁盘空间赋值
- if (i.devName === '/dev/mapper/centos-root') {
- // 返回值为GB,转为字节B
- saveSize = i.free * 1024 * 1024 * 1024
- }
- })
- // 上传的文件大小没有超出磁盘可用空间
- if (fileSize < saveSize) {
- return true
- } else {
- ElMessage.warning('文件大小超出磁盘可用空间容量')
- return false
- }
- } else {
- ElMessage.warning('文件大小超出磁盘可用空间容量')
- return false
- }
- }
此处文件上传用 MD5 进行加密,需要安装依赖 spark-md5
npm i spark-md5
- /**
- * @description: 文件加密处理
- */
- const getMD5 = (file: any): Promise<string> => new Promise((resolve, reject) => {
- const spark = new SparkMD5.ArrayBuffer();
- // 获取文件二进制数据
- const fileReader = new FileReader();
- fileReader.readAsArrayBuffer(file); // file 就是获取到的文件
- // 异步执行函数
- fileReader.addEventListener('load', (e: any) => {
- spark.append(e.target.result);
- const md5: string = spark.end();
- resolve(md5);
- });
- fileReader.addEventListener('error', (e) => {
- reject(e);
- });
- });
通过接口合并上传文件,接口需要的参数:
接口合并完成后,前端展示已上传的文件名称
- /**
- * @description: 合并文件
- * @param name 文件名
- * @param hash 文件唯一 hash 值
- * @return 命名名称
- */
- const composeFile = async (name: string, hash: string) => {
- console.log('开始文件合并');
- const res = await uploadFileMerge({
- applicationId: props.applicationId,
- applicationVersion: props.applicationVersion,
- bucketName: 'app',
- fileName: name,
- hash,
- });
- console.log('后端接口合并文件 ===', res);
- if (res.status === 200 && res.data.code) {
- // 合并成功后,调整已上传的文件名称
- state.editForm.inlineAppVersionModel.fileName = name;
- }
- };
接口轮询 —— 每次携带一个文件切片给后端;后端接受到切片 并 返回成功状态码后,再进行下一次切片上传
- /**
- * @description: 分片函数
- * @param file 文件
- * @param shardIndex 分片数量
- */
- const uploadFileSilce = async (file: File, shardIndex: number) => {
- // 文件名
- const { name } = file;
- // 文件大小
- const { size } = file;
- // 分片大小
- const shardSize = 1024 * 1024 * 5;
- // 文件加密
- const hash: string = await getMD5(file);
- // 分片总数
- const shardTotal = Math.ceil(size / shardSize);
-
- // 如果 当前分片索引 大于 总分片数
- if (shardIndex >= shardTotal) {
- isAlive.value = false;
- progress.value = 100;
- // 合并文件
- composeFile(name, hash);
- return;
- }
-
- // 文件开始结束的位置
- const start = shardIndex * shardSize;
- const end = Math.min(start + shardSize, size);
- // 开始切割
- const packet = file.slice(start, end);
-
- // 拼接请求参数
- const formData = new FormData();
- formData.append('file', packet);
- formData.append('applicationId', props.applicationId);
- formData.append('applicationVersion', props.applicationVersion);
- formData.append('bucketName', 'app');
- formData.append('hash', hash);
- formData.append('shardSize', shardSize as unknown as string);
- formData.append('seq', shardIndex as unknown as string);
-
- // 如果 当前分片索引 小于 总分片数
- if (shardIndex < shardTotal) {
- // 进度条保留两位小数展示
- progress.value = Number(((shardIndex / shardTotal) * 100).toFixed(2)) * 1;
- // 调用文件上传接口
- const res = await uploadFile(formData);
- if (res.status !== 200) {
- ElMessage.error('上传失败');
- progress.value = 0;
- return;
- }
- if (res.status === 200 && res.data.code === 200) {
- // 这里为所有切片上传成功后进行的操作
- console.log('上传成功');
- }
- // eslint-disable-next-line no-param-reassign
- shardIndex++;
- // 递归调用 分片函数
- uploadFileSilce(file, shardIndex);
- }
- };
nginx 默认上传大小为 1MB,若超过 1MB,则需要修改 nginx 配置 解除上传限制
- /**
- * @description: 动态创建 a 标签,实现大文件下载
- */
- const downloadMirror = async (item) => {
- let t = {
- id: item.id,
- }
- const res = await downloadMirrorApi(t)
- if (res.headers["content-disposition"]) {
- let temp = res.headers["content-disposition"].split(";")[1].split("filename=")[1]
- let fileName = decodeURIComponent(temp)
- // 通过创建a标签实现文件下载
- let link = document.createElement('a')
- link.download = fileName
- link.style.display = 'none'
- link.href = res.data.msg
- document.body.appendChild(link)
- link.click()
- document.body.removeChild(link)
- } else {
- ElMessage({
- message: '该文件不存在',
- type: 'warning',
- })
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。