• 大文件上传


    前端代码(文件名需要判断是否存在  -  ,存在会后台出错)

    <script setup>
    import { ref } from 'vue'
    import SparkMD5 from 'spark-md5'
    
    import {uploadFile,mergeChunks} from './utils/request'
    const DefualtChunkSize = 5 * 1024 * 1024
    const fileChunkList= {
      value:[]
    }
    const currFile =  {
      value:{
        
      }
    }
    // 获取文件分块
    const getFileChunk = (file, chunkSize = DefualtChunkSize) => {
      return new Promise((resovle) => {
        let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
          chunks = Math.ceil(file.size / chunkSize),
          currentChunk = 0,
          spark = new SparkMD5.ArrayBuffer(),
          fileReader = new FileReader();
    
        fileReader.onload = function (e) {
          console.log('read chunk nr', currentChunk + 1, 'of');
    
          const chunk = e.target.result;
          spark.append(chunk);
          currentChunk++;
    
          if (currentChunk < chunks) {
            loadNext();
          } else {
            let fileHash = spark.end();
            console.info('finished computed hash', fileHash);
            resovle({ fileHash });
          }
        };
    
        fileReader.onerror = function () {
          console.warn('oops, something went wrong.');
        };
    
        function loadNext() {
          let start = currentChunk * chunkSize,
            end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
          let chunk = blobSlice.call(file, start, end);
          fileChunkList.value.push({ chunk, size: chunk.size, name: currFile.value.name });
          fileReader.readAsArrayBuffer(chunk);
        }
    
        loadNext();
      });
    }
    // 上传请求
    const uploadChunks = (fileHash) => {
      const requests = fileChunkList.value.map((item, index) => {
        const formData = new FormData();
        formData.append(`${currFile.value.name}-${fileHash}-${index}`, item.chunk);
        formData.append("filename", currFile.value.name);
        formData.append("hash", `${fileHash}-${index}`);
        formData.append("fileHash", fileHash);
        return uploadFile('/file/upload', formData, onUploadProgress(item));
      });
    
      Promise.all(requests).then(() => {
        mergeChunks('/file/mergeChunks', { size: DefualtChunkSize, filename: currFile.value.name });
      });
    }
    // 总进度条
    const totalPercentage = () => {
      if (!fileChunkList.value.length) return 0;
      const loaded = fileChunkList.value
        .map(item => item.size * item.percentage)
        .reduce((curr, next) => curr + next);
      return parseInt((loaded / currFile.value.size).toFixed(2));
    }
    // 分块进度条
    const onUploadProgress = (item) => (e) => {
      item.percentage = parseInt(String((e.loaded / e.total) * 100));
    }
    const getFile = async(e)=>{
      const files = e.target.files
      currFile.value = files[0]
      console.log(files[0])
      const {fileHash} = await getFileChunk(files[0])
     await uploadChunks(fileHash)
      console.log(fileChunkList)
      console.log(totalPercentage())
      
      
    }
    </script>
    
    <template>
      <div>
        <input type="file"  ref="file" @change="getFile"/>
        
      </div>
    </template>
    
    <style>
    
    </style> 

    axios

    import axios from "axios";
    
    const baseURL = 'http://localhost:3001';
    
    export const uploadFile = (url, formData, onUploadProgress = () => { }) => {
      return axios({
        method: 'post',
        url,
        baseURL,
        headers: {
          'Content-Type': 'multipart/form-data'
        },
        data: formData,
        onUploadProgress
      });
    }
    
    export const mergeChunks = (url, data) => {
      return axios({
        method: 'post',
        url,
        baseURL,
        headers: {
          'Content-Type': 'application/json'
        },
        data
      });
    }
    

      

    后端代码

    const router = require('koa-router')()
    const koaBody = require('koa-body')
    const fs = require('fs')
    const path = require('path')
    
    router.prefix('/file')
    const outputPath = './src/server/resources/'
    // 上传请求
    router.post(
      '/upload',
      // 处理文件 form-data 数据
      koaBody({
        multipart: true,
        formidable: {
          uploadDir: outputPath,
          onFileBegin: (name, file) => {
            console.log(name)
            const [filename, fileHash, index] = name.split('-');
            const dir = path.join(outputPath, filename);
            // 保存当前 chunk 信息,发生错误时进行返回
            currChunk = {
              filename,
              fileHash,
              index
            };
    
            // 检查文件夹是否存在如果不存在则新建文件夹
            if (!fs.existsSync(dir)) {
              fs.mkdirSync(dir);
            }
    
            // 覆盖文件存放的完整路径
            file.path = `${dir}/${fileHash}-${index}`;
            console.log(file.path)
          },
          onError: (error) => {
            app.status = 400;
            app.body = { code: 400, msg: "上传失败", data: currChunk };
            return;
          },
        },
      }),
      // 处理响应
      async (ctx) => {
        ctx.set("Content-Type", "application/json");
        ctx.body = JSON.stringify({
          code: 2000,
          message: 'upload successfully!'
        });
      });
    // 合并请求
    router.post('/mergeChunks', async (ctx) => {
      const { filename, size } = ctx.request.body;
      // 合并 chunks
      await mergeFileChunk(path.join(outputPath, '_' + filename), filename, size);
    
      // 处理响应
      ctx.set("Content-Type", "application/json");
      ctx.body = JSON.stringify({
        data: {
          code: 2000,
          filename,
          size,
        path:`/src/server/resources/_${filename}`
        },
        message: 'merge chunks successful!'
      });
    });
    
    // 通过管道处理流 
    const pipeStream = (path, writeStream) => {
      return new Promise(resolve => {
        const readStream = fs.createReadStream(path);
        readStream.pipe(writeStream);
        readStream.on("end", () => {
          fs.unlinkSync(path);
          resolve();
        });
      });
    }
    
    // 合并切片
    const mergeFileChunk = async (filePath, filename, size) => {
      const chunkDir = path.join(outputPath, filename);
      
      const chunkPaths = fs.readdirSync(chunkDir);
    
      if (!chunkPaths.length) return;
    
      // 根据切片下标进行排序,否则直接读取目录的获得的顺序可能会错乱
      chunkPaths.sort((a, b) => a.split("-")[1] - b.split("-")[1]);
      console.log("chunkPaths = ", chunkPaths);
    
      await Promise.all(
        chunkPaths.map((chunkPath, index) =>
          pipeStream(
            path.resolve(chunkDir, chunkPath),
            // 指定位置创建可写流
            fs.createWriteStream(filePath, {
              start: index * size,
              end: (index + 1) * size
            })
          )
        )
      );
    
      // 合并后删除保存切片的目录
      fs.rmdirSync(chunkDir);
    };
    
    module.exports = router
    

      

  • 相关阅读:
    完全背包笔记
    渗透测试之信息收集常用网站
    结对项目-四则运算"软件"之升级版
    第三次作业:个人项目-小学四则运算“软件”之初版
    分布式版本控制系统Git的安装与使用
    第一次作业:准备
    爬虫综合大作业
    爬取全部校园新闻
    理解爬虫原理
    中文词频统计与词云生成
  • 原文地址:https://www.cnblogs.com/chenlw/p/16133553.html
Copyright © 2020-2023  润新知