# 大文件上传
**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
```

> - 安装 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 里面的样式全部删除
>
> 得到页面效果
>

#### 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
}
```