彻底搞懂文件上传带完整实现过程

传统原生表单上传样式已经不满足现在前端发展的需求,现在市场通常采用各种第三方UI组件库满足日常所需。但在开发中包括我在内的不少开发者知其然却不知其所以然,这篇文章让我们彻底搞懂文件上传的各个场景

单一文件上传

实现思路

  • 实现上传功能仍然需要借助 input 的 file 属性,并隐藏隐藏元素
  • 使用 js 手动触发 input 事件
  • 用 html 元素优化代替原生的 input 的样式

优化结果

实现代码

html

  <div class="container">
   <div class="buttonContainer">
     <input type="file" id="inputInput" style="display:none">
     <div class="button-select button">选择文件</div>
     <div class="button-submit button">上传文件</div>
   </div>
   <div>文件仅支持png格式的文件</div>
   <div>
     <ul id="list"></ul>
   </div>
 </div>

HTML 部分的样式内容忽略,但其中的技术点在于 隐藏了 input 元素 ,用 div 模拟了选择文件和上传文件按钮

javaScript

let inputInput = document.querySelector("#inputInput")
let select = document.querySelector(".button-select")
let submit = document.querySelector(".button-submit")
let list = document.querySelector("#list")
let filesData;

// 获取本地文件
inputInput.onchange=()=>{
 let files = inputInput.files[0]
 console.log(files)
 if(files.type !== "image/png"){
   alert("文件只支持png格式")
   return
}
 if(files.size>2*1024*1024){
   alert("文件最大支持2MB")
   return
}
 filesData = files
 list.innerHTML = `
   <li>${files.name} <span class="del">移除</span></li>
 `
}
// 时间委托
list.onclick = (event)=>{
 if(event.target.innerHTML === "移除"){
   list.innerHTML = "";
   filesData = null
}
}
// 触发上传的click事件
select.onclick = ()=>{
 inputInput.click()
}
// 提交
submit.onclick = ()=>{
 if(filesData){
   let formData = new FormData();
   formData.append("files",filesData)
   fetch("www.baidu.com",{
     method:"POST",
     body:formData
  }).then((response)=>{
     console.log(response)
  })
}
}

上面的 javascript 代码, 获取“选择文件” select ,在 select 点击的时候触发 input 的点击事件,弹出文件选择器; 绑定 input 的 change 事件 监听选中的文件对象, 通过 files 属性获取对象全部信息并作相应的业务判断,比如 文件类型、文件大小、文件上传列表等。通过点击 submit 元素手动创建 formData 实例 将文件元素添加进去,传给后端


单一文件上传BASE64

单一文件上传将文件转为 base64编码 , 主要借助 FileReader 实例对象,该对象不仅可以转化为 base64,还支持 readAsArrayBuffer 的转换。

实现结果

以上代码的实现形式,点击 选择文件 按钮,选择文件后自动上传。

实现方式

html

  <div class="container">
   <div class="buttonContainer">
     <input type="file" id="inputInput">
     <div class="button-select button">选择文件</div>
   </div>
   <div>文件仅支持png格式的文件</div>
 </div>

javascript

let inputInput = document.querySelector("#inputInput")
let select = document.querySelector(".button-select")

let fileReader =(files)=>{
 return new Promise((resolve,reject)=>{
   let fileReader = new FileReader();
   fileReader.readAsDataURL(files)
   fileReader.onload=(event)=>{
     resolve(event.target.result)
  }
})
}
// 获取本地文件
inputInput.onchange=async ()=>{
 let files = inputInput.files[0]
 if(files.size>2*1024*1024){
   alert("文件最大支持2MB")
   return
}
 let base64 = await fileReader(files)
 fetch("http://www.baidu.com",{
   method:"POST",
   headers:{
     'Content-Type': 'application/x-www-form-urlencoded'
  },
   body:JSON.stringify({
     files:base64,
     filesName:files.name
  })
})
}
// 触发上传的click事件
select.onclick = ()=>{
 inputInput.click()
}

实现自动上传的逻辑,主要是监听 input file 的 change 事件,在 change 事件中直接处理图片为 base64 编码,然后请求后台接口。

值得注意的是 FileReader 的实例为异步方法,所以上面代码对 FileReacer 做了 Promise 的封装,至于不了解 Promise 的小伙伴请自行百度知识盲区。


文件上传缩略图

借助 base64 编码 实现文件缩略图效果

实现结果

实现逻辑

将文件 编码为base64,复制给 img 元素

html

  <div class="container">
   <div class="buttonContainer">
     <input type="file" id="inputInput">
     <div class="button-select button">选择文件</div>
     <div class="button-submit button">上传文件</div>
   </div>
   <div>
     <img src="" alt="" id="img">
   </div>
 </div>

javascript

let inputInput = document.querySelector("#inputInput")
let select = document.querySelector(".button-select")
let submit = document.querySelector(".button-submit")
let img = document.querySelector("#img")
let filesData;

// 将文件转成base64
const toBase64 = (files)=>{
 return new Promise((resolve,reject)=>{
   let fileReader = new FileReader();
   fileReader.readAsDataURL(files)
   fileReader.onload=(event)=>{
     resolve(event.target.result)
  }
})
}

// 获取本地文件
inputInput.onchange=async ()=>{
 let files = inputInput.files[0]
 console.log(files)
 if(files.size>2*1024*1024){
   alert("文件最大支持2MB")
   return
}
 filesData = files;
 let base64 = await toBase64(files);
 console.log("base64",base64)
 img.src = base64
}
// 触发上传的click事件
select.onclick = ()=>{
 inputInput.click()
}
// 提交
submit.onclick = ()=>{
 if(filesData){
   let formData = new FormData();
   formData.append("files",filesData)
   fetch("www.baidu.com",{
     method:"POST",
     body:formData
  }).then((response)=>{
     console.log(response)
  })
}
}

文件上传进度管控

使用 axios 或者 原生 XHR 发起接口请求,其中这两个实例中提供了 onUploadProgress 属性, onUploadProgress 为一个回调函数,监听上传状态 其中 loaded 为当前上传的数量,total 为文件总量。

值得注意的是新的 fetch 请求方式,无法监听文件上传和下载进度状态

实现结果

实现方式

html

  <div class="container">
   <div class="buttonContainer">
     <input type="file" id="inputInput">
     <div class="button-select button">选择文件</div>
     <div class="button-submit button">上传文件</div>
   </div>
   <div>文件仅支持png格式的文件</div>
   <div class="progress">
     <div class="progressBar" style="width: 10%;"></div>
   </div>
 </div>

javascript

let inputInput = document.querySelector("#inputInput")
let select = document.querySelector(".button-select")
let submit = document.querySelector(".button-submit")
let progressBar = document.querySelector(".progressBar")
let filesData;


// 获取本地文件
inputInput.onchange=()=>{
 let files = inputInput.files[0]
 console.log(files)
 if(files.type !== "text/plain"){
   alert("文件只支持text格式")
   return
}
 if(files.size>2*1024*1024){
   alert("文件最大支持2MB")
   return
}
 filesData = files
}
// 触发上传的click事件
select.onclick = ()=>{
 inputInput.click()
}
// 提交
submit.onclick = ()=>{
 if(filesData){
   let formData = new FormData();
   formData.append("files",filesData)
   axios({
     method:"POST",
     url:"https://jsonplaceholder.typicode.com/posts/",
     data:formData,
     onUploadProgress:(ev)=>{
       let {loaded,total} = ev;
       progressBar.style.width = `${loaded/total*100}%`
    }
  }).then((response)=>{
     console.log(response)
  })
}
}

多文件上传及进度管控

同单文件实现思路一样,多文件上传针对每个文件进行进度管控的方案在于,每一个文件都会单独请求一次接口。

优点:可以对每一个文件进行管控,并且会降低文件上传到容量

缺点:会重复多次的调用一个接口,对接口和服务器造成的压力较大

针对于合并多文件请求的方式之前在 element 模块中《文件上传》中总结过 实现的方法, 感兴趣的小伙伴可以移步去另一篇文章

实现结果:

实现方式

html

  <div class="container">
   <div class="buttonContainer">
     <input type="file" id="inputInput" multiple>
     <div class="button-select button">选择文件</div>
     <div class="button-submit button">上传文件</div>
   </div>
   <div>文件仅支持png格式的文件</div>
   <div>
     <ul id="list">
       
     </ul>
   </div>
 </div>

javascript

let inputInput = document.querySelector("#inputInput")
let select = document.querySelector(".button-select")
let submit = document.querySelector(".button-submit")
let list = document.querySelector("#list")
let filesData;

// 获取本地文件
inputInput.onchange=()=>{
 filesData =Array.from(inputInput.files)
 console.log("多文件上传",filesData)
 filesData.map((item)=>{
   list.innerHTML+= `
     <li>
       ${item.name}
       <div class="progress">
         <div class="progressBar" data-name="${item.name}" style="width:10%"></div>
       </div>
     </li>
   `
})
}
// 触发上传的click事件
select.onclick = ()=>{
 inputInput.click()
}
// 提交
submit.onclick = ()=>{
 console.log(filesData)
 if(filesData.length){
   filesData.map((item)=>{
     let formData = new FormData();
     formData.append("files",item)
     axios({
       method:"POST",
       url:"https://jsonplaceholder.typicode.com/posts/",
       data:formData,
       onUploadProgress:(ev)=>{
         let el = document.querySelector(`div[data-name="${item.name}"]`)
         let {loaded,total} = ev;
         el.style.width = `${loaded/total*100}%`
      }
    }).then((response)=>{
       console.log(response)
    })
  })
}
}

上面 JS 的实现逻辑为 每一个 li 元素绑定唯一的 自定义属性目前以为文件名称作为唯一值,在调用接口的中监听 onUploadProgress 利用当前的 file 对象的 name 属性,找到相对的 DOM 节点,计算进度并赋值。


拖拽上传

拖拽上传的实现方式 主要于借助了 H5 的API dragover \ drop 方法,用来监听在此区域内的鼠标移动和放下事件。

实现结果

实现方式

html

  <div class="container" id="upWrapper">
   <div class="content">
     <img src="./img/上传.png" alt="">\
   </div>
 </div>

javascript

let upWrapper = document.querySelector("#upWrapper")
 // 进入事件
 upWrapper.addEventListener("dragover",(event)=>{
   event.preventDefault();
})
 // 放置事件
 upWrapper.addEventListener("drop",(event)=>{
   event.preventDefault();
   submit(event.dataTransfer.files)
})
 // 提交事件
 const submit = (filesData)=>{
   let formData = new FormData();
   formData.append("files",filesData)
   axios({
     method:"POST",
     url:"https://jsonplaceholder.typicode.com/posts/",
     data:formData,
  }).then((response)=>{
     console.log(response)
  })
}

上面 js 的主要逻辑为, 在 drop 事件中获取 文件的属性 event.dataTransfer.files ,然后将文件属性传给服务端。


大文件上传和断点续传

在一些业务需求中,需要用户上传一些大容量视频,这时用传统的文件上传方式会造成传输效率变慢的问题。在上传文件过程中一旦出现上传失败,就需要重新将文件整个重新上传一遍。这样在无论是用户提体验还是服务端都是一种时间和资源的浪费。

在观察一些存储大厂,比如七牛云、百度云盘等服务商,他们在上传和下载的时候会把文件碎片化,这样不仅可以大大提高传输速度,且也兼容的断电续传的功能,即文件上传失败,系统对自动检索失败的文件碎片,等待用户重新上传该文件时会自动真甄别失败的文件碎片进行上传。

下面就介绍下前端是如何实现大文件上传和断点续传的

实现的技术点

  • 需要借助 FileReader 实例对象进行 Budder 的转换
  • 需要使用第三放 Spark-md5 来生成HASH
  • 需要使用正则获取文件的扩展名,后期和结合 HASH 值合并成一个新的文件名称(上图所示)
  • 需要借助 file 对象中的 slice 方法对文件切割;使用方法和数组的 slice 参数一致

代码实现

HTML

 <div class="container">
   <div class="buttonContainer">
     <input type="file" id="inputInput">
     <div class="button-select button">选择文件</div>
     <div class="button-submit button">上传文件</div>
   </div>
   <div>文件仅支持png格式的文件</div>
   <div class="progress">
     <div class="progressBar" style="width: 10%;"></div>
   </div>
 </div>

javascript

let inputInput = document.querySelector("#inputInput")
let select = document.querySelector(".button-select")
let submit = document.querySelector(".button-submit")
let progressBar = document.querySelector(".progressBar")
let chunks=[]; // 切片的集合

// 生成文件Buffer的封装
const changeBuffer = (file)=>{
 return new Promise((resolve)=>{
   let fileReader = new FileReader();
   fileReader.readAsArrayBuffer(file);
   fileReader.onload = (event)=>{
     // 根据文件内容生成HASH值
     let bufferData = event.target.result;
     let spark = new SparkMD5.ArrayBuffer();
     let HASH; // 文件的Hash名
     let suffix; //获取文件后缀名
     spark.append(bufferData);
     HASH = spark.end();
     suffix = file.name.replace(/.+\./, "");
     resolve({
       bufferData,
       HASH,
       suffix,
       fileName:`${HASH}.${suffix}`
    })
  }
})
}

// 获取本地文件
inputInput.onchange= async ()=>{
 let files = inputInput.files[0]
 let {HASH,suffix,bufferData,fileName} = await changeBuffer(files);
 let section = []; //文件切片上传的记录
 /*文件切片处理(以固定大小上传优先,如果切片数量过大,就采用固定数量)*/
 let max = 1024*100 // 每个文件最大在100KB
 let count = Math.ceil(files.size / max); // 一共要有多少切片
 /*超过100个切片,就采用固定数量方式上传*/
 if(count>100){
   max = files.size / 100;
   count = 100;
}
 for(let index=0;index<count;index++){
   chunks.push({
       file:files.slice(index*max,max*(index+1)),
       fileName:`${HASH}_${index+1}.${suffix}`
  })
}
}

// 触发上传的click事件
select.onclick = ()=>{
 inputInput.click()
}

// 提交
submit.onclick = ()=>{
 // 上传成功的处理
 let ChunkState = [];
 const upSuccess = (state)=>{
   ChunkState.push(state);
   // 判断是否全部为true
   let ChunkStateEvery = ChunkState.every((item)=>{return item==true})
   if(chunks.length === ChunkState.length){
     if(ChunkStateEvery){
       // 调用后台接口 通知服务器可以对文件进行合并
       //...
       alter("文件上传成功")
    }else{
       alter("文件上传出错成重新上传")
    }
  }
   // 更新进度条
   progressBar.style.width = `${count(ChunkState,true)/chunks.length*100}%`
}
 // 循环切片集合逐个请求
 chunks.map((item)=>{
   let formData = new FormData();
   formData.append("files",item.file)
   formData.append("fileName",item.fileName)
   axios({
     method:"POST",
     url:"https://jsonplaceholder.typicode.com/posts/",
     data:formData,
  }).then((response)=>{
     if(response.code == 200){
       upSuccess(true);
    }
  }).catch(()=>{
     upSuccess(false);
  })
})
}

// 算法封装查找数组中某个元素出现的次数
const count = (arr, item)=>{
 var count = 0;
 arr.forEach((e)=> item === e ? count++:0);
 return count;
}

大文件切片上传前后端整体实现逻辑

整体流程可参考上方流程图,文件采取 steam 流方式传递给后端

实现技术栈 vue3 + KOA2 demo地址

文件切片上传

关键字

  • 文件切片
  • 并发上传
  • 合并文件
  • 数据流式传输
  • fs 依赖增强
  • 进度管控
  • 暂停上传

前端完整部分

<template>
  <div>
    <el-upload
      ref="uploadRef"
      class="upload-demo"
      drag
      action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
      multiple
      :on-change="handleUPload"
      :auto-upload="false"
    >
      <el-icon class="el-icon--upload"><upload-filled /></el-icon>
      <div class="el-upload__text">
        Drop file here or <em>click to upload</em>
      </div>
      <template #tip>
        <div class="el-upload__tip">
          jpg/png files with a size less than 500kb
        </div>
      </template>
    </el-upload>
  </div>
  <img :src="imgUrl" alt="">
  <div> 
    <video :src="imgUrl" controls></video>
  </div>
  <!-- 进度 -->
  <div>总进度: {{progressAllNum}}</div>
  <div v-for="(value, key) in progressList" :key="key">
    {{ key }}: {{ value }}
  </div>

  <el-button class="ml-3" type="success" @click="handleSubmit">
    上传
  </el-button>
  <el-button class="ml-3" type="success" @click="handleClose">
    取消上传
  </el-button>
</template>

<script lang="ts" setup>
import { UploadFilled } from '@element-plus/icons-vue'
import {ref,reactive} from "vue"
import { ElMessage, type UploadInstance } from 'element-plus'
import axios from "axios"

/**上传表单实例 */
const uploadRef = ref<UploadInstance>();

/**上传文件实例 */
const fileInfo = ref<any>();

/**axios token */
const axiosTokenList = reactive<any[]>([]);

/**进度条 */
const progressList = reactive({})
const progressAllNum = ref(0)

/**预览本地路径 */
const imgUrl = ref("")
/*文件 hash 名称 */
let fileHashName = "";

/*手动提交*/
const handleSubmit = async ()=>{
   /**生成hash */
  fileHashName = await createHash(fileInfo.value.raw)
   /*根据文件的 hash 值,请求服务端文件是否存在实现大文件秒传*/
  const result:any = await verifyFileUpload(fileHashName)
  console.log("测试文件秒传",result)
  if(result.data.code===101){
    ElMessage({
      message: '文件传输完成.',
      type: 'success',
    })
    return
  }
  /*文件切片*/
  const chunks = fileChunk(fileInfo.value.raw,fileHashName)
  console.log(chunks)
  /*并行上传切片文件*/  
  const requestList = chunks.map((item)=>{
    /*记录每一个请求的 axios token 用于取消请求*/  
    const createToken = axios.CancelToken.source();
    axiosTokenList.push(createToken)
    return createFileUploadRequest(fileHashName,item.chunkFileName,item.chunk,createToken)
  })
  try {
    await Promise.all(requestList);
    /**请求成功后通知服务端合并文件*/
    await axios.get(`/api/marge/${fileHashName}`)
    console.log("上传成功")
  } catch (error) {
    console.log("上传失败",error)
  }
}


const handleUPload = async (file)=>{
  console.log(file)
  fileInfo.value = file;
  /**预览 */
  imgUrl.value = URL.createObjectURL(file.raw)
}


/**获取 hash 值,该方法为手动获取hash值,可以是用  Spark-md5 替代*/
const createHash = async (file)=>{
  const arrayBuffer = await file.arrayBuffer()
  console.log(arrayBuffer)
  /**
   * crypto.subtle:这是浏览器提供的Web Cryptography API的一个对象,用于进行底层密码学操作,如加密、解密、哈希等。
   * 'SHA-256' 表示哈希算法的一种格式,可以将任务长度的数据映射成固定长度(32字节)
   * */
  const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer);
  const hashResult = Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, '0')).join("")
  return hashResult;
}


/**文件切片 */
const fileChunk = (file,fileHashName)=>{
  let chunks:any[] = [];
  let chunkSize = 1024 * 1024 * 100;
  let count = Math.ceil(file.size / chunkSize);
  for(let i=0;i<count;i++){
    let chunk = file.slice(i * chunkSize , (i + 1) * chunkSize)
    chunks.push({
      chunk,
      chunkFileName:`${fileHashName}-${i}`
    })
  }
  return chunks;
}

/**创建文件上传请求 */
const createFileUploadRequest = (fileName,chunkFileName,chunk,createToken)=>{
  return axios({
    url:`/api/upload/${fileName}`,
    method:"post",
    data:chunk,
    headers:{
      "Content-Type": "application/octet-stream"
    },
    params:{
      chunkFileName
    },
    /*根据 axios 计算上传进入*/
    onUploadProgress:(progressEvent:any)=>{
      const percentCompleted = Math.round(progressEvent.loaded * 100 / progressEvent.total);
      progressList[chunkFileName] = percentCompleted;
      var progressAll = calculateAverage();
      progressAllNum.value = progressAll;
    },
    cancelToken:createToken.token
  })
}

/*计算上传总进度*/
const calculateAverage = ()=>{
  // 获取对象的值数组
  const values = Object.values(progressList);
  // 如果对象为空,则返回 0
  if (values.length === 0) {
    return 0;
  }
  // 使用reduce方法计算总和
  const sum:any = values.reduce((acc:any, curr:any) => acc + curr, 0);
  // 返回平均值
  return sum / values.length;
}


/**文件秒传验证 */
const verifyFileUpload = async (fileName)=>{
  return await axios.get(`/api/quickUpload/${fileName}`);
}

/**取消上传 */
const handleClose = ()=>{
  axiosTokenList.forEach((item,index)=>{
    item.cancel('请求被用户取消');
  })
}

</script>

上述代码中并没有采用传统的 formData 的上传方式,而是 application/octet-stream ,因为后者直接将数据转换成二进制流传给后台,这种方式针对传输的数据更加灵活,避免了被 formData 类型的限制。

node端 koa

避免使用 koa-body 和 koa-bodyparser 中间件,因为该中间件屏蔽了二进制流数据传输格式

清除掉 app.js 中的

const bodyparser = require('koa-bodyparser')
app.use(bodyparser({
  enableTypes:['json', 'form', 'text']
}))

创建路由,接受文件流

const router = require('koa-router')()
const fs = require("fs-extra");
const path = require('path');

/**上传文件*/
router.post('/upload/:fileName', async (ctx, next) => {
  const chunkFileName = ctx.params.fileName;
  const fileName = ctx.request.query;
  // 确保块文件夹存在
  await fs.ensureDir(`./uploads/${chunkFileName}`);
  /*文件写入方法封装*/
  const result = await fileInit(ctx,chunkFileName,fileName);
  if(result.code === 101){
    ctx.body = {
      code:101,
      data:{
        chunkFileName,
        fileName,
        body:ctx.request.body
      },
      message:"请求成功"
    }
  }else{
    ctx.body = {
      code:102,
      data:{
        chunkFileName,
        fileName,
        body:ctx.request.body
      },
      message:"请求失败"
    }
  }
})

/**文件处理 */
const fileInit = (ctx,chunkFileName,fileName)=>{
  return new Promise((resolve,reject)=>{
    /*创建一个 buffer 对象*/
    let bufferData = Buffer.alloc(0);
    ctx.req.on('data', async (chunk) => {
      /*将请求的 chunk 字符串,转换成 budder 对象*/
      const chunkData = Buffer.from(chunk);
      /*由于 chunk 会被请求分成不同的数据包,所以在接受的同时合并到 bufferData 变量中存储*/
      bufferData = Buffer.concat([bufferData, chunkData]);
    })
    /*当传输完成后触发 end 监听*/
    ctx.req.on('end', async () => {
      try {
        /*将临时存储的 buffer 对象到文件中*/
        await fs.writeFileSync(`./uploads/${chunkFileName}/${fileName.chunkFileName}`,bufferData);
        console.log("传输完成")
        resolve({code:101,message:"文件上传成功"})
      } catch (error) {
        console.log(error)
        resolve({code:102,message:"文件上传失败"})
      }
    })
    /*请求中断后会触发该监听*/
    ctx.req.on("close",async ()=>{
      console.log("请求被中断")
      ctx.body = {
        code:103,
        message:"请求被中断"
      }
    })
  })
}

/**合并文件接口 */
router.get("/marge/:fileName",async (ctx,next)=>{
  const chunkFileName = ctx.params.fileName;
  /**上传目录路径*/
  const uploadsDirectory = `./uploads/${chunkFileName}`;
  /**合并后的文件路径和文件名*/
  const mergedFilePath = `./uploads/${chunkFileName}.mp4`;
  /**创建一个空的合并文件*/
  fs.ensureFileSync(mergedFilePath);
  /**读取上传目录中的所有文件*/
  fs.readdir(uploadsDirectory,async (err, files) => {
    const storedFiles = files.sort(compare)
    /**对每个文件进行迭代*/
    storedFiles.forEach((file) => {
        const filePath = path.join(uploadsDirectory, file);
        /**将当前文件的内容追加到合并文件中*/
        fs.appendFileSync(mergedFilePath, fs.readFileSync(filePath));
        /**删除原始文件*/
        fs.unlinkSync(filePath);
        console.log(`${file} 合并完成,已被删除`);
    });
    await fs.remove(uploadsDirectory)
    console.log('文件合并完成');
  });



  /**根据文件名排序 */
  function compare(a, b) {
    const numA = parseInt(a.split('-')[1]);
    const numB = parseInt(b.split('-')[1]);
    return numA - numB;
  }

  ctx.body = {
    code:101,
    data:{},
    message:"请求成功"
  }

})

/*验证当前文件是否存在,如果存在则不需要客户端重新上传,直接返回相对的成功结果*/
router.get("/quickUpload/:fileName",async (ctx,next)=>{
  const fileName = ctx.params.fileName;
  const result = await fs.pathExists(`./uploads/${fileName}.mp4`);
  if(result){
    ctx.body = {
      code:101,
      message:"请求成功"
    }
  }else{
    ctx.body = {
      code:102,
      message:"请求成功"
    }
  }
  
})


module.exports = router
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇