# 大文件上传 **Repository Path**: hi_lyon/large-file-upload ## Basic Information - **Project Name**: 大文件上传 - **Description**: 使用Vue3+Ts+ ant-design-vue大文件上传的学习 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2025-10-09 - **Last Updated**: 2025-10-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 大文件上传`————`断点续传`————`文件识别秒传 > 我们在做文件导入功能的时候,如果导入的文件过大,可能会导致所需要的时间够长,且失败后需要重新上传,我们需要前后端结合的方式解决问题 > > - 不考虑文件大小上传`————`调用后端提供的post 接口`————`FormData 里面携带 file 对象或者是 Blob 对象 > > 一旦文件过大,有可能传输失败—失败之后重头再来 > > 假如这个文件传过,就没有必要在传了`————`用什么判断 ### 思路 ```mermaid graph TB A[文件]-->B1[分片1] A-->B2[分片2] A-->B3[分片3] A-->B4[分片4] ``` > 最简单粗暴的回答就是分片 > > 补上一句使用 File 对象的 slice 方法进行分片`————`分片成 Blob 格式`————`二进制 > > 传完之后在去组合分片就好啦 > > 我们需要做几件事情 > > - **对文件做切片**, 即将一个请求拆分成多个请求,每个请求的时间就会缩短,且如果某个请求失败,只需要重新发送这一次请求即可,无需从头开始 > - **通知服务器合并切片** ,在上传完切片后,前端通知服务器做合并切片操作 > - **控制多个请求的并发量**,防止多个请求同时发送,造成浏览器内存溢出,导致页面卡死 > - **做断点续传**,当多个请求中有请求发送失败,例如出现网络故障、页面关闭等,我们得对失败的请求做处理,让它们重复发送 ### 后端接口 > 后端需要提供三个接口 > > - 1.检查文件接口`————`接收参数`————`文件的hash > - 文件已经上传过,并且上传完成,此时直接告知客户端不需要在传,秒传成功 > - 文件已经上传过,但是没传完,此时告知客户端需要继续上传,并告诉需要从第几个分片进行开始上传 > - 文件没有上传过 > - 2.上传文件接口`————`接收参数`————` FormData 类型的 file,filename等其他 > - 接收分片文件,保存到临时目录 > - 3.合并文件分片接口`————`接收参数`————`文件的hash值 > - 合并文件分片,并告诉前端上传成功 ### 前端实现 #### 1.搭建Vue3+Ts+ ant-design-vue 的项目 ```bash yarn create vue upload ``` ![](./assets/20221025161307.png) > - 安装 ant-design-vue ```bash yarn add ant-design-vue ``` > - 安装自动打包识别组件的插件 ```bash yarn add unplugin-vue-components --dev ``` > 在 vite.config.ts 中配置自动编译打包项目 ```ts import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import Components from 'unplugin-vue-components/vite' import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers' // https://vitejs.dev/config/ export default defineConfig({ plugins: [vue(), Components({ resolvers:[AntDesignVueResolver()],// 放上组件库 ant-design-vue 对于的 resolver 就会识别相应的前缀 自动引入组件 }) ], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } } }) ``` > - 配置 eslint 解决一下报错 ```js /* eslint-env node */ require("@rushstack/eslint-patch/modern-module-resolution"); module.exports = { root: true, extends: [ "plugin:vue/vue3-essential", "eslint:recommended", // 当前项目不支持ts "@vue/eslint-config-typescript/recommended", ], env:{ "vue/setup-compiler-macros": true }, rules:{ "vue/multi-word-component-names":[ "error", { igmores:["index"] } ], "vue/no-setup-props-destructure":["off"], // 支持对 defineProps 结构 "vue/no-mutating-props":["off"], } }; ``` > - 修改App.vue ```vue ``` > - 在tsconfig.json 中添加对组件库的类型引入 ```bash { "extends": "@vue/tsconfig/tsconfig.web.json", "include": ["env.d.ts", "src/**/*", "src/**/*.vue","components.d.ts"], "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["./src/*"] } }, "references": [ { "path": "./tsconfig.config.json" } ] } ``` > - 新建 src/components/upload-huge.vue 组件 ```vue ``` > - 在HomeView.vue 中引用 ```vue ``` > - 把main.css 里面的样式全部删除 > > 得到页面效果 > ![](./assets/QQ%E6%88%AA%E5%9B%BE20221025170637.png) #### 2.axios的按和TS的封装 > - 安装axios ```bash yarn add axios ``` > - 新建 utils/request.ts ```ts import axios from "axios"; import type { Method,AxiosRequestConfig } from "axios"; const serivce = axios.create({ baseURL: "http://localhost:70010", // server 的 aggjs的域名, timeout: 5000, }); // 处理响应拦截器 serivce.interceptors.response.use((response) => response.data); // axios 中的ts认为 data 是有的,但是响应拦截器实际是去掉了 // 再次对请求方法进行封装 type Data = { code: number; method: string; data: T; }; const request = (url: string, method: Method, SubmitData: Object, config?: AxiosRequestConfig) => { // ts 里面可以传泛型 传入一个类型进去 return serivce.request>({ url, method, [method.toLowerCase()==="get"?"params":"data"]:SubmitData, ...config }); }; export default request ``` #### 3.大文件上传 > 1.定义fileList ,绑定upload 事件 ```vue ``` > 2. 定义几个方法 > > - createFileChunk 创建分片 > - calcFileHash 创建文件Hash > - uploadChunks 上传分片 ```js //创建分片 const createFileChunk=()=>{} // 创建文件Hash——判断唯一性 const calcFileHash=()=>{} // 上传分片 const uploadChunks=()=>{} ``` > 文件 hash 唯一标识需要依赖 spark-md5 的包,并且需要安装对用的 ts ```bash yarn add spark-md5 @types/spark-md5 ``` > 由于文件大小的,所以创建文件hash值的方法是一个promise > > - 识别文件的唯一性 > > 将唯一标识传到后端比对`————` 看看有没有这个文件,如果有 ```ts const calcFileHash= async (file:File):Promise=>{ return new Promise(resolve=>{ const spark = new sparkMD5.ArrayBuffer() const reader= new FileReader() const size = file.size const offset= 2 *1024*1024 // 第一个2M ,最后一个区块数据全要 const chunks= [file.slice(0, offset)] let cur= offset while(cur=size){ chunks.push(file.slice(cur,cur+offset)) } else { // 中间的区块 const mid= cur+offset/2 const end = cur+offset chunks.push(file.slice(cur,cur+2)) chunks.push(file.slice(mid,mid+2)) chunks.push(file.slice(end-2,end)) } cur += offset } // 中间的,取前中后各2各字节 reader.readAsArrayBuffer(new Blob(chunks)) reader.onloadend = e => { spark.append(e?.target?.result as ArrayBuffer) resolve(spark.end()) } }) } ``` ```ts const upload=async (params:UploadRequestOption)=>{ //获取上传的文件——进行检查——检查过后——分片——上传——合并 // 当选择一个文件就会触发 upload 方法 const hash = await calcFileHash(params.file as File) // 类型断言 console.log('hash',hash); // 有了 hash 值就可以去后端检查 这个文件有没有传过 } ``` > 3. 实现分片方法 ```ts const ChunkSize=1024*1024*10 // 分片的大小 //创建分片 const createFileChunk=(file:File,size=ChunkSize,hash:string)=>{ // 最终要分文件 File // 定义一个起始值 let curr= 0 const chunks=[] let index =0 // 要记录是第几片 while(curr 4. 调用接口,进行判断 ```ts import request from '@/utils/request' // 工具引入 import { message } from "ant-design-vue" import 'ant-design-vue/lib/message/style/index.css' // 由于用了 unplugin 不会引入样式 interface CheckResponse{ uploaded:boolean, url?:string, lastSlice?:number //上一次的分片 } // ref 声明泛型——泛型类是从ant-design-vue中取出来的 const upload=async (params:UploadRequestOption)=>{ //获取上传的文件——进行检查——检查过后——分片——上传——合并 // 当选择一个文件就会触发 upload 方法 const hash = await calcFileHash(params.file as File) // 类型断言 const { data}= await request("/checkFile","POST",{hash}) if(data.uploaded){ // 如果这个值为true 意味着文件已经上传完成 message.success("秒传成功") // 告诉 upload 组件 文件已经上传完了 if(params.onSuccess){ params.onSuccess(data) } return } const chunks = createFileChunk(params.file as File,ChunkSize,hash) //创建分片 // 有了 hash 值就可以去后端检查 这个文件有没有传过 } ``` > 6. 如果没有上传过,或者没有上传完成,则生成切片。继续上传 ```ts interface Chunks{ name:string, chunk:Blob //File 分片之后就是 blob } ```