vue 文件切片上传的项目实现

yizhihongxing

下面我将详细讲解“Vue 文件切片上传的项目实现”的完整攻略。该过程主要分为以下五个步骤:

  1. 安装依赖库

开发项目之前需要先安装以下库:

  • axios:用于发起后端请求;
  • element-ui:基于 Vue 的组件库,提供了上传文件的组件;
  • js-sha256:计算文件的哈希值。

可以使用以下命令进行安装:

npm install axios element-ui js-sha256 --save
  1. 前端实现

在代码中引用axiosElementUIjs-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函数在前端显示。否则,后端服务器没有必要将每块上传的哈希值保存到磁盘上,而是直接返回错误响应。

  1. 后端实现

后端需要编写一个接口用于处理上传文件的请求,并在该接口中实现请求中上传的媒体块的处理逻辑,这里以 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。

  1. 合并上传的文件

当文件块全部上传完成后,后端服务器将返回上传文件的 URL。这里我们需要编写一个自动合并上传文件的函数:

const mergeChunks = async (fileName, fileUrl) => {
  const { data: serverFileUrl } = await axios.post(`${serverPath}/merge`, {
    hash: fileName,
    fileUrl,
  });
  return serverFileUrl;
};

在这段代码中,我们向后端服务器发送一个包含上传文件的哈希值和每个块的 URL 的请求。返回的结果是上传文件的所有块已经成功上传到服务器并被合并。合并完成后,我们将接收到的上传文件的 URL 作为最终结果返回。

  1. 完整代码

完整代码如下:

<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技术站

(0)
上一篇 2023年5月28日
下一篇 2023年5月28日

相关文章

  • Vue中CSS scoped的原理详细讲解

    Vue中的CSS scoped可以实现局部作用域,从而避免全局CSS样式造成的样式冲突。在Vue组件中使用scoped属性,可以使得CSS只作用于当前组件,而不会影响到其他组件或全局CSS的样式。 下面是实现scoped的原理: Vue编译器会将组件的模板和样式分别处理,然后生成纯JS代码 在处理样式时,编译器会将scoped属性添加到组件的根元素上 在生成…

    Vue 2023年5月28日
    00
  • 详解jquery和vue对比

    详解jQuery和Vue对比 本文将对jQuery和Vue两个前端框架进行详细比较,包括以下内容: 两个框架的基本概念和功能; 两个框架间的异同点; 如何选择一个框架; 基于两个框架的示例说明。 基本概念和功能 jQuery jQuery是一个基于JavaScript的库,为JavaScript提供了跨浏览器的操作和事件处理的功能,使开发者可以使用更简单的语…

    Vue 2023年5月27日
    00
  • vue 移动端注入骨架屏的配置方法

    下面我会详细讲解在 Vue 移动端项目中如何注入骨架屏。 什么是骨架屏? 骨架屏是一种用于提高移动端用户体验的技术,它是在页面内容还未加载完成时展示的一种占位元素,可以提高用户对页面加载进度的感知。骨架屏通常采用灰色填充块线条等元素,展示页面结构和布局,让用户感知到页面正在加载内容。 注入骨架屏的配置方法 在 Vue 移动端项目中,可以使用 vue-skel…

    Vue 2023年5月28日
    00
  • 对vue事件的延迟执行实例讲解

    下面给您详细讲解“对Vue事件的延迟执行实例讲解”: 什么是对Vue事件的延迟执行 对Vue事件的延迟执行,即是指在某个事件触发后,不立即执行对应的回调函数,而是在一定时间延迟后再去执行。 为什么需要对Vue事件进行延迟执行 在某些场景下,如输入框keyup事件、下拉框change事件等,用户操作频繁,可能会导致回调函数被频繁地执行,造成性能问题。此时,可以…

    Vue 2023年5月29日
    00
  • 一文读懂vue动态属性数据绑定(v-bind指令)

    一文读懂Vue动态属性数据绑定(v-bind指令) Vue.js 是一种现代的、简洁的 JavaScript 框架,专注于构建用户界面。动态属性数据绑定是 Vue.js 提供的一个强大的组件,用于将 Vue 的数据响应式地绑定到 DOM 元素属性。其中,v-bind 指令可以用于将组件中的数据绑定至元素的属性上。 v-bind 指令语法 在 Vue 组件中使…

    Vue 2023年5月27日
    00
  • Vue参数的增删改实例详解

    《Vue参数的增删改实例详解》是一篇介绍Vue.js中参数操作的文章,其中包括了参数的添加、修改和删除操作。下文将从以下三个部分对该文章进行详细解释。 一、参数的添加 在Vue.js中添加参数有以下几种方式: 1. 在data对象中添加参数 在Vue中,可以通过在data对象中声明参数来添加参数,如下所示: data() { return { msg: ‘h…

    Vue 2023年5月29日
    00
  • 解决Springboot 2 的@RequestParam接收数组异常问题

    下面就是解决Springboot 2中的@RequestParam接收数组异常问题的完整攻略: 问题描述 在使用Springboot 2的@Controller或@RestController接口接收请求参数时,如果使用@RequestParam注解接收数组参数时,有时候会出现异常,例如: Failed to convert value of type ja…

    Vue 2023年5月29日
    00
  • vue组件实现文字居中对齐的方法

    为了在Vue中实现文字居中对齐,我们可以使用CSS来为Vue组件设置样式。在Vue中设置样式的方法可以通过<style>标签来实现。 以下是两种示例说明: 示例一:使用flex 一种常见的设置文字居中对齐的方法是使用flexbox布局。在Vue组件中,我们可以为其容器添加.center类,通过CSS样式中的display: flex和align-…

    Vue 2023年5月28日
    00
合作推广
合作推广
分享本页
返回顶部