最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時(shí)間:2024年02月04日 08:21:37   作者:一咻  
這篇文章主要為大家詳細(xì)介紹了React實(shí)現(xiàn)文件上傳和斷點(diǎn)續(xù)傳功能的相關(guān)知識,文中的示例代碼講解詳細(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 對象表示所讀取文件的數(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編碼,處理以及對于文件的上傳處理。

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)槿绻募?、字符串的MD5散列值不一樣,說明文件內(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);
    // 對文件 進(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 native es6語法

    深入淺析react native es6語法

    這篇文章主要介紹了深入淺析react native es6語法的相關(guān)資料,需要的朋友可以參考下
    2015-12-12
  • 淺談React Native 傳參的幾種方式(小結(jié))

    淺談React Native 傳參的幾種方式(小結(jié))

    這篇文章主要介紹了淺談React Native 傳參的幾種方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05
  • 使用React+ts實(shí)現(xiàn)無縫滾動(dòng)的走馬燈詳細(xì)過程

    使用React+ts實(shí)現(xiàn)無縫滾動(dòng)的走馬燈詳細(xì)過程

    這篇文章主要給大家介紹了關(guān)于使用React+ts實(shí)現(xiàn)無縫滾動(dòng)的走馬燈詳細(xì)過程,文中給出了詳細(xì)的代碼示例以及圖文教程,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2023-08-08
  • React運(yùn)行機(jī)制超詳細(xì)講解

    React運(yùn)行機(jī)制超詳細(xì)講解

    React 作為當(dāng)下最為流行的前端開發(fā)框架之一,使用它可以快速構(gòu)建大型 Web 應(yīng)用,加上其出色的性能表現(xiàn),使得眾多互聯(lián)網(wǎng)公司對它格外地青睞,這篇文章主要介紹了React運(yùn)行機(jī)制
    2022-11-11
  • webpack 2的react開發(fā)配置實(shí)例代碼

    webpack 2的react開發(fā)配置實(shí)例代碼

    本篇文章主要介紹了webpack 2的react開發(fā)配置實(shí)例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-07-07
  • react中如何對自己的組件使用setFieldsValue

    react中如何對自己的組件使用setFieldsValue

    react中如何對自己的組件使用setFieldsValue問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • react redux入門示例

    react redux入門示例

    本篇文章主要介紹了react redux入門示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-04-04
  • React+Spring實(shí)現(xiàn)跨域問題的完美解決方法

    React+Spring實(shí)現(xiàn)跨域問題的完美解決方法

    這篇文章主要介紹了React+Spring實(shí)現(xiàn)跨域問題的完美解決方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-08-08
  • React 子組件向父組件傳值的方法

    React 子組件向父組件傳值的方法

    本篇文章主要介紹了React 子組件向父組件傳值的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-07-07
  • React注冊倒計(jì)時(shí)功能的實(shí)現(xiàn)

    React注冊倒計(jì)時(shí)功能的實(shí)現(xiàn)

    這篇文章主要介紹了React注冊倒計(jì)時(shí)功能的實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-09-09

最新評論

奉节县| 岳阳市| 渑池县| 平陆县| 和静县| 象山县| 云龙县| 锦屏县| 静海县| 德清县| 江北区| 阿拉善右旗| 恭城| 嘉定区| 曲阜市| 四会市| 连州市| 安龙县| 南乐县| 内黄县| 海安县| 普安县| 仙游县| 西峡县| 江达县| 曲阜市| 汕头市| 开封县| 余庆县| 南岸区| 西安市| 鹿邑县| 绩溪县| 同江市| 南漳县| 隆安县| 东兰县| 和田县| 宁晋县| 曲周县| 黄浦区|