欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

React實(shí)現(xiàn)文件上傳和斷點(diǎn)續(xù)傳功能的示例代碼

 更新時(shí)間:2024年02月04日 08:21:37   作者:一咻  
這篇文章主要為大家詳細(xì)介紹了React實(shí)現(xiàn)文件上傳和斷點(diǎn)續(xù)傳功能的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

實(shí)現(xiàn)思路

分片上傳的思路:

  • 我們先拿到文件,在前端進(jìn)行分片,將分片之后的小的文件傳遞給服務(wù)端。
  • 當(dāng)在客戶端傳送完成的時(shí)候,發(fā)送最后一個(gè)請求告訴服務(wù)端,文件已經(jīng)傳送完成了,然后服務(wù)端再將之前接收到文件進(jìn)行合并成一個(gè)大的文件。最終再告訴客戶端合并好的這個(gè)大文件。

斷點(diǎn)續(xù)傳,兩種方案:

  • 在上傳之前先拉一下已經(jīng)上傳了那些切片在服務(wù)端了,然后客戶端就可以跳過已經(jīng)上傳的切片了。
  • 客戶端不處理,在服務(wù)端進(jìn)行處理,客戶端上傳所有的切片,然后服務(wù)端發(fā)現(xiàn)如果已經(jīng)上傳過了,則迅速返回成功,告訴客戶端再繼續(xù)傳送下一個(gè)切片。

前端實(shí)現(xiàn)

1. axios 封裝

import axios from "axios";
import Qs from "qs";

let instance = axios.create();
instance.defaults.baseURL = "http://127.0.0.1:8888";
instance.defaults.headers["Content-Type"] = "multipart/form-data";

instance.defaults.transformRequest = (data, headers) => {
  const contentType = headers["Content-Type"];
  if (contentType === "application/x-www-form-urlencoded")
    return Qs.stringify(data);
  return data;
};

instance.interceptors.response.use((response) => {
  return response.data;
});

export default instance;

2. 分片上傳邏輯

import { useRef, useState } from "react";
import SparkMD5 from "spark-md5";

import "./large-file-upload.less";
import instance from "../utils/instance";

function LargeFileUpload() {
  const [loading, setLoading] = useState(false);
  const inputFileRef = useRef();

  const handleUploadClick = () => {
    inputFileRef.current.click();
  };

  // 根據(jù)文件生成 一個(gè)hash 值
  function changeBuffer(file) {
    return new Promise((resolve) => {
      let fileReader = new FileReader();
      // 調(diào)用讀取 file 內(nèi)容的函數(shù),當(dāng)讀取完成的時(shí)候, readyState 變成 DONE (已完成), 并觸發(fā) loadend 時(shí)間,同時(shí) result 屬性中包含一個(gè)
      // arrayBuffer 對(duì)象表示所讀取文件的數(shù)據(jù)。
      fileReader.readAsArrayBuffer(file);

      // 當(dāng)讀取操作完成的時(shí)候,觸發(fā) loaded 事件
      fileReader.onload = (ev) => {
        // result 表示所讀取的文件數(shù)據(jù)
        let buffer = ev.target.result;
        let spark = new SparkMD5.ArrayBuffer();

        let HASH, suffix;
        spark.append(buffer);
        // 根據(jù)文件內(nèi)容生成一個(gè) hash 值
        HASH = spark.end();
        // 在文件名里面匹配 . 后面的字母
        // 第一個(gè)是匹配的 所有字符, 第二個(gè)是 匹配的第一個(gè)組
        suffix = /\.([a-zA-z0-9]+)$/.exec(file.name)[1];

        resolve({
          buffer,
          HASH,
          suffix,
          filename: `${HASH}.${suffix}`,
        });
      };
    });
  }

  const onInputFileChange = async () => {
    let file = inputFileRef.current.files[0];
    // 獲取文件的 hash 值
    let already = [],
      data = null;

    // 根據(jù)文件內(nèi)容生成  hash 值的時(shí)候,也是必要消耗時(shí)間的
    setLoading(true);
    // 根據(jù)文件內(nèi)容生成  hash 值,和獲取文件后綴
    let { HASH, suffix } = await changeBuffer(file);

    // 獲取已經(jīng)上傳的切片信息
    try {
      data = await instance.get("/upload_already", {
        params: {
          HASH,
        },
      });
      // 拿到已經(jīng)上傳好的 切片列表
      if (+data.code === 0) {
        already = data.fileList;
      }
    } catch (err) {
      console.log(err);
    }

    // 實(shí)現(xiàn)文件的切片處理
    // 有兩種策略  【固定大小  或者 固定數(shù)量】
    let max = 1024 * 100; // 每次傳輸?shù)淖畲笞止?jié)數(shù)
    let count = Math.ceil(file.size / max); // 計(jì)算一共要分多少個(gè)切片
    let index = 0;
    let chunks = [];
    // 如果計(jì)算的 切片個(gè)數(shù)大于 100 個(gè)則,就固定切片個(gè)數(shù)
    if (count > 100) {
      count = 100;
      // 重新計(jì)算切片的大小
      max = file.size / 100;
    }
    // 生成切片信息
    while (index < count) {
      chunks.push({
        file: file.slice(index * max, (index + 1) * max),
        filename: `${HASH}_${index + 1}.${suffix}`,
      });
      index++;
    }
    index = 0;

    const complate = async () => {
      index++;
      // 當(dāng)如果沒有達(dá)到最大的個(gè)數(shù)

      if (index < count) return;
      try {
        data = await instance.post(
          "/upload_merge",
          {
            HASH,
            count,
          },
          {
            headers: {
              "Content-Type": "application/x-www-form-urlencoded",
            },
          }
        );
        if (+data.code === 0) {
          setLoading(false);
          // 上傳完成之后清除 form 的值
          inputFileRef.current.value = "";
          alert(
            `恭喜你,文件上傳成功,你可以訪問 ${data.servicePath} 訪問該文件~~`
          );
        }
      } catch (err) {
        alert("切片合并失敗,請稍后再試");
      }
    };
    // 遍歷收集好的 切片信息
    chunks.forEach((chunk) => {
      // 看看是否有已經(jīng)上傳的切片信息
      if (already.length > 0 && already.includes(chunk.filename)) {
        // 這里的 return 表示跳過的意思
        complate();
        return;
      }
      let fm = new FormData();
      fm.append("file", chunk.file);
      fm.append("filename", chunk.filename);
      instance.post("/upload_chunk", fm).then((data) => {
        if (+data.code === 0) {
          complate();
        }
        // 如果 code 不是 0
        return Promise.reject(data.codeText);
      });
    });
  };

  return (
    <div className="large-file-upload" onClick={handleUploadClick}>
      {loading ? (
        <div className="loading">loading...</div>
      ) : (
        <span className="add">+</span>
      )}

      <input type="file" ref={inputFileRef} onChange={onInputFileChange} />
    </div>
  );
}

export default LargeFileUpload;

實(shí)現(xiàn)文件上傳切片處理的方式

  • 固定數(shù)量。
  • 固定大小。(設(shè)置一個(gè)每次傳輸?shù)淖畲笞止?jié)數(shù),如果根據(jù)最大字節(jié)數(shù)計(jì)算出來的切片數(shù)量超過最大切片數(shù)量的話,則按照最大的切片數(shù)量重新計(jì)算每次傳輸?shù)淖畲笞止?jié)數(shù))

后端實(shí)現(xiàn)(nodejs)

1.引用包

body-parser:bodyParser用于解析客戶端請求的body中的內(nèi)容,內(nèi)部使用JSON編碼處理,url編碼,處理以及對(duì)于文件的上傳處理。

express: 創(chuàng)建 api服務(wù)

multiparty: 解析Content-Type multipart/form-data的HTTP請求,也被稱為文件上傳。

spark-md5:

  • MD5計(jì)算將整個(gè)文件或者字符串,通過其不可逆的字符串變換計(jì)算,產(chǎn)生文件或字符串的MD5散列值。任意兩個(gè)文件、字符串不會(huì)有相同的散列值(即“很大可能”是不一樣的,理論上要?jiǎng)?chuàng)造出兩個(gè)散列值相同的字符串是很困難的)。
  • 因此MD5常用于校驗(yàn)字符串或者文件,以防止文件、字符串被“篡改”。因?yàn)槿绻募⒆址腗D5散列值不一樣,說明文件內(nèi)容也是不一樣的,即經(jīng)過修改的,如果發(fā)現(xiàn)下載的文件和給的MD5值不一樣,需要慎重使用。

2. 代碼實(shí)現(xiàn)

// 使用 express 編寫 api 程序

const express = require("express");
const fs = require("fs");
const bodyParser = require("body-parser");
const multipart = require("multiparty");
const sparkMd5 = require("spark-md5");

// 創(chuàng)建服務(wù)

const app = express();
const PORT = 8888;
const HOST = "http://127.0.0.1";
const HOSTNAME = `${HOST}:${PORT}`;

app.listen(PORT, () => {
  console.log(`上傳服務(wù)啟動(dòng),請?jiān)L問${HOSTNAME}`);
});

// 中間件

app.use((req, res, next) => {
  res.header("Access-Control-allow-origin", "*");
  // 如果是 options 請求則放行
  req.method === "OPTIONS"
    ? res.send("current services support cross domain requests!")
    : next();
});

app.use(
  bodyParser.urlencoded({
    extended: false,
    limit: "1024mb",
  })
);

// API

// 延時(shí)函數(shù)

function delay(interval) {
  typeof interval !== "number" ? (interval = 1000) : null;
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, interval);
  });
}

// 檢測文件是否存在
const exists = function exists(path) {
  return new Promise((resolve) => {
    fs.access(path, fs.constants.F_OK, (err) => {
      if (err) {
        resolve(false);
        return;
      }
      resolve(true);
    });
  });
};

//  大文件上傳 & 合并切片
const merge = (HASH, count) => {
  return new Promise(async (resolve, reject) => {
    let path = `${uploadDir}/${HASH}`;
    let fileList = [];
    let suffix = "";
    let isExists;
    // 看當(dāng)前路徑是否存在
    isExists = await exists(path);
    // 根據(jù) hash 值沒有找到
    if (!isExists) {
      reject("HASH path is not found!");
      return;
    }
    fileList = fs.readdirSync(path);
    if (fileList.length < count) {
      reject("ths slice has not been uploaded!");
      return;
    }
    fileList
      .sort((a, b) => {
        let reg = /_(\d+)/;
        return reg.exec(a)[1] - reg.exec(b)[1];
      })
      .forEach((item) => {
        !suffix ? (suffix = /\.([0-9a-zA-Z]+)$/.exec(item)[1]) : null;
        // 把切片合并成一個(gè)文件
        fs.appendFileSync(
          `${uploadDir}/${HASH}.${suffix}`,
          fs.readFileSync(`${path}/${item}`)
        );
        // 刪除切片
        fs.unlinkSync(`${path}/${item}`);
      });
    // 移除臨時(shí)空的文件夾
    fs.rmdirSync(path);
    resolve({
      path: `${uploadDir}/${HASH}.${suffix}`,
      filename: `${HASH}.${suffix}`,
    });
  });
};

// 創(chuàng)建文件 并寫入到指定的目錄 并且返回給客戶端結(jié)果
const writeFile = (res, path, file, filename, stream) => {
  return new Promise((resolve, reject) => {
    if (stream) {
      try {
        // 創(chuàng)建可讀,可寫 流
        let readStream = fs.createReadStream(file.path);
        let writeStream = fs.createWriteStream(path);
        // 將可寫流交給可讀流管道
        // 上面三行代碼的作用是,將文件從 file.path 復(fù)制到 path
        readStream.pipe(writeStream);
        readStream.on("end", () => {
          resolve();
          // 然后刪除掉 file.path 下面的文件
          fs.unlinkSync(file.path);
          res.send({
            code: 0,
            codeText: "upload success",
            originalFilename: filename,
            servicePath: path.replace(__dirname, HOSTNAME),
          });
        });
      } catch (err) {
        reject(err);
        res.send({
          code: 1,
          codeText: err,
        });
      }
      return;
    }
    fs.writeFile(path, file, (err) => {
      if (err) {
        reject(err);
        res.send({
          code: 1,
          codeText: err,
        });
        return;
      }
      resolve();
      res.send({
        code: 0,
        codeText: "upload success",
        originalFilename: filename,
        servicePath: path.replace(__dirname, HOSTNAME),
      });
    });
  });
};

// 基于 multiparty 插件實(shí)現(xiàn)文件上傳處理 & form-data 解析

const uploadDir = `${__dirname}/upload`;

const multiparty_upload = (req, auto) => {
  typeof auto !== "boolean" ? (auto = false) : null;
  let config = {
    maxFieldsSize: 200 * 1024 * 1024,
  };
  if (auto) config.uploadDir = uploadDir;

  return new Promise(async (resolve, reject) => {
    await delay();
    new multipart.Form(config).parse(req, (err, fields, files) => {
      if (err) {
        reject(err);
        return;
      }
      // 解析出文件,和文件名
      resolve({
        fields,
        files,
      });
    });
  });
};

// 合并文件
app.post("/upload_merge", async (req, res) => {
  let { HASH, count } = req.body;

  // 嘗試合并文件
  try {
    let { filename, path } = await merge(HASH, count);
    res.send({
      code: 0,
      codeText: "merge success",
      originalFilename: filename,
      servicePath: path.replace(__dirname, HOSTNAME),
    });
  } catch (err) {
    res.send({
      code: 1,
      codeText: err,
    });
  }
});

// 請求已經(jīng)上傳好的分片
app.get("/upload_already", async (req, res) => {
  let { HASH } = req.query;

  let path = `${uploadDir}/${HASH}`;
  let fileList = [];

  try {
    // 讀取文件目錄
    fileList = fs.readdirSync(path);
    // 對(duì)文件 進(jìn)行一個(gè)排序
    fileList = fileList.sort((a, b) => {
      // 匹配數(shù)字
      let reg = /_(\d)+/;
      return reg.exec(a)[1] - reg.exec(b)[1];
    });

    // 發(fā)送給前端
    res.send({
      code: 0,
      codeText: "",
      fileList,
    });
  } catch (err) {
    res.send({
      code: 0,
      codeText: "",
      fileList: fileList,
    });
  }
});

// 上傳分片的接口
app.post("/upload_chunk", async (req, res) => {
  try {
    let { fields, files } = await multiparty_upload(req);
    let file = (files.file && files.file[0]) || {};
    let filename = (fields.filename && fields.filename[0]) || "";
    let path = "";
    let isExists = false;
    // 創(chuàng)建存放 切片的臨時(shí)目錄
    let [, HASH] = /^([^_]+)_(\d+)/.exec(filename);
    path = `${uploadDir}/${HASH}`;
    !fs.existsSync(path) ? fs.mkdirSync(path) : null;
    // 把切片存儲(chǔ)發(fā)哦臨時(shí)目錄中
    path = `${uploadDir}/${HASH}/${filename}`;
    isExists = await exists(path);
    if (isExists) {
      res.send({
        // 0 表示成功
        code: 0,
        codeText: "file is exists",
        originalFilename: filename,
        servicePath: path.replace(__dirname, HOSTNAME),
      });
      return;
    }
    writeFile(res, path, file, filename, true);
  } catch (err) {
    res.send({
      code: 1,
      codeText: err,
    });
  }
});

app.use(express.static("./"));
app.use((req, res) => {
  res.status(404);
  res.send("not found!");
});

以上就是React實(shí)現(xiàn)文件上傳和斷點(diǎn)續(xù)傳功能的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于React文件上傳和斷點(diǎn)續(xù)傳的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • React-View-UI組件庫封裝Loading加載中源碼

    React-View-UI組件庫封裝Loading加載中源碼

    這篇文章主要介紹了React-View-UI組件庫封裝Loading加載樣式,主要包括組件介紹,組件源碼及組件測試源碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-06-06
  • React組件間通信的三種方法(簡單易用)

    React組件間通信的三種方法(簡單易用)

    React知識(shí)中一個(gè)主要內(nèi)容便是組件之間的通信,以下列舉幾種常用的組件通信方式,本文就詳細(xì)的介紹一下,感興趣的可以了解一下
    2021-10-10
  • React修改數(shù)組對(duì)象的注意事項(xiàng)及說明

    React修改數(shù)組對(duì)象的注意事項(xiàng)及說明

    這篇文章主要介紹了React修改數(shù)組對(duì)象的注意事項(xiàng)及說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • React組件中的this的具體使用

    React組件中的this的具體使用

    這篇文章主要介紹了React組件中的this的具體使用,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-02-02
  • 提高React界面性能的十個(gè)技巧

    提高React界面性能的十個(gè)技巧

    眾所周知,性能是Web應(yīng)用界面的關(guān)鍵方面,它直接影響到用戶的使用體驗(yàn)。本文將向您展示十種提高React UI性能的特定技術(shù)和一般方法。
    2021-05-05
  • 淺談從React渲染流程分析Diff算法

    淺談從React渲染流程分析Diff算法

    這篇文章主要介紹了淺談從React渲染流程分析Diff算法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-09-09
  • Redux中間件的使用方法教程

    Redux中間件的使用方法教程

    中間件就是一個(gè)函數(shù),對(duì)store.dispatch方法進(jìn)行了改造,在發(fā)出 Action 和執(zhí)行 Reducer 這兩步之間,添加了其他功能,要理解中間件,關(guān)鍵點(diǎn)是要知道,這個(gè)中間件是連接哪些部分的軟件,它在中間做了什么事,提供了什么服務(wù)
    2023-01-01
  • jenkins分環(huán)境部署vue/react項(xiàng)目的方法步驟

    jenkins分環(huán)境部署vue/react項(xiàng)目的方法步驟

    這篇文章主要介紹了jenkins分環(huán)境部署vue/react項(xiàng)目的方法,本文分步驟給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-02-02
  • React封裝高階組件實(shí)現(xiàn)路由權(quán)限的控制詳解

    React封裝高階組件實(shí)現(xiàn)路由權(quán)限的控制詳解

    這篇文章主要介紹了React封裝高階組件實(shí)現(xiàn)路由權(quán)限的控制,在React中,為了實(shí)現(xiàn)安全可靠的路由權(quán)限控制,可以通過多種方式來確保只有經(jīng)過授權(quán)的用戶才能訪問特定路徑下的資源,下面來介紹封裝高階組件控制的方法,需要的朋友可以參考下
    2025-02-02
  • 簡單談?wù)凴eact中的路由系統(tǒng)

    簡單談?wù)凴eact中的路由系統(tǒng)

    下面小編就為大家?guī)硪黄唵握務(wù)凴eact中的路由系統(tǒng)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-07-07

最新評(píng)論