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

JavaScript進(jìn)階之前端文件上傳和下載示例詳解

 更新時(shí)間:2022年09月30日 14:47:02   作者:云牧  
這篇文章主要為大家介紹了JavaScript進(jìn)階之前端文件上傳和下載示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

文件下載

1.通過a標(biāo)簽點(diǎn)擊直接下載

<a href="https:xxx.xlsx" rel="external nofollow"  download="test">下載文件</a>

download屬性標(biāo)識(shí)文件需要下載且下載名稱為test

如果有 Content-Disposition 響應(yīng)頭,則不需要設(shè)置download屬性就能下載,文件名在響應(yīng)頭里面由后端控制

此方法有同源和請(qǐng)求headers鑒權(quán)的問題

2.open或location.href

window.open('xxx.zip');
location.href = 'xxx.zip';

需要注意 url 長(zhǎng)度和編碼問題

不能直接下載瀏覽器默認(rèn)預(yù)覽的文件,如txt、圖片

3.Blob和Base64

function downloadFile(res, Filename) {
  // res為接口返回?cái)?shù)據(jù),在請(qǐng)求接口的時(shí)候可進(jìn)行鑒權(quán)
  if (!res) return;
  // IE及IE內(nèi)核瀏覽器
  if ("msSaveOrOpenBlob" in navigator) {
    navigator.msSaveOrOpenBlob(res, name);
    return;
  }
  const url = URL.createObjectURL(new Blob([res]));
  //  const fileReader = new FileReader();  使用 Base64 編碼生成
  // fileReader.readAsDataURL(res);
  // fileReader.onload = function() { ...此處邏輯和下面創(chuàng)建a標(biāo)簽并釋放代碼一致,可從fileReader.result獲取href值...}
  const a = document.createElement("a");
  a.style.display = "none";
  a.href = url;
  a.download = Filename;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url); // 釋放blob對(duì)象
}

注意 請(qǐng)求發(fā)送的時(shí)候注明 responseType = "blob",如無設(shè)置則需要 new Blob的時(shí)候傳入第二個(gè)參數(shù),如

new Blob([res], { type: xhr.getResponseHeader("Content-Type") });

此方法可以解決請(qǐng)求headers鑒權(quán)和下載瀏覽器默認(rèn)直接預(yù)覽的文件,并得知下載進(jìn)度

文件上傳

文件上傳思路

File文件

  • MDN描述

上傳單個(gè)文件-客戶端

<input id="uploadFile" type="file" accept="image/*" />
  • type屬性file:用戶選擇文件
  • accept屬性:規(guī)定選擇文件的類型

<body>
    <input id="uploadFile" type="file" accept="image/*" />
    <button type="button" id="uploadBtn" onClick="startUpload()">開始上傳</button>
    <div class="progress">上傳進(jìn)度:<span id="progressValue">0</span></div>
    <div id="uploadResult" class="result"></div>
    <script>
      const uploadFileEle = document.getElementById("uploadFile");
      const progressValueEle = document.getElementById("progressValue");
      const uploadResultEle = document.getElementById("uploadResult");
      try {
        function startUpload() {
          if (!uploadFileEle.files.length) return;
          // 獲取文件
          const file = uploadFileEle.files[0];
          // 創(chuàng)建上傳數(shù)據(jù)
          const formData = new FormData();
          formData.append("file", file);
          // 上傳文件
          upload(formData);
        }
        function upload(data) {
          const xhr = new XMLHttpRequest();
          xhr.onreadystatechange = function () {
            if (xhr.readyState === 4 && xhr.status === 200) {
              const result = JSON.parse(xhr.responseText);
              console.log("result:", result);
              uploadResultEle.innerText = xhr.responseText;
            }
          };
          // 上傳進(jìn)度
          xhr.upload.onprogress = function (event) {
            if (event.lengthComputable) {
              progressValueEle.innerText = Math.ceil((event.loaded * 100) / event.total) + "%";
            }
          };
          xhr.open("POST", "http://127.0.0.1:3000/upload", true);
          xhr.send(data);
        }
      } catch (e) {
        console.log("error:", e);
      }
    </script>
</body>

上傳文件-服務(wù)端

  • 客戶端使用form-data傳遞,服務(wù)端使用相同方式接受解析
  • 使用 multer 庫(kù)處理 multipart/form-data

const app = express();
// 上傳成功后返回URL地址
const resourceUrl = `http://127.0.0.1:${port}/`;
// 存儲(chǔ)文件目錄
const uploadDIr = path.join(__dirname, "/upload");
// destination 設(shè)置資源保存路徑,filename 設(shè)置資源名稱
const storage = multer.diskStorage({
  destination: async function (_req, _file, cb) {
    cb(null, uploadDIr);
  },
  filename: function (_req, file, cb) {
    // 設(shè)置文件名
    cb(null, `${file.originalname}`);
  },
});
const multerUpload = multer({ storage });
//設(shè)置靜態(tài)訪問目錄
app.use(express.static(uploadDIr));
app.post("/upload", multerUpload.any(), function (req, res, _next) {
  // req.file 是 `avatar` 文件的信息
  let urls = [];
  //獲取所有已上傳的文件
  const files = req.files;
  if (files && files.length > 0) {
    //遍歷生成url 集合返回給客戶端
    urls = files.map((item, _key) => {
      return resourceUrl + item.originalname;
    });
  }
  return res.json({
    REV: true,
    DATA: {
      url: urls,
    },
    MSG: "成功",
  });
});

多文件上傳-客戶端

  • input屬性:multiple是否允許多個(gè)值(相關(guān)類型email、file )
 <body>
    <input id="uploadFile" type="file" accept="image/*" multiple />
    <button id="uploadBtn" onClick="startUpload()">開始上傳</button>
    <div class="progress">上傳進(jìn)度:<span id="progressValue">0</span></div>
    <div id="uploadResult" class="result"></div>
    <script>
      const uploadFileEle = document.getElementById("uploadFile");
      const progressValueEle = document.getElementById("progressValue");
      const uploadResultEle = document.getElementById("uploadResult");
      try {
        function startUpload() {
          if (!uploadFileEle.files.length) return;
          //獲取文件
          const files = uploadFileEle.files;
          const formData = this.getUploadData(files);
          this.upload(formData);
        }
        //添加多個(gè)文件
        function getUploadData(files) {
          const formData = new FormData();
          for (let i = 0; i < files.length; i++) {
            const file = files[i];
            formData.append(file.name, file);
          }
          return formData;
        }
        function upload(data) {
          const xhr = new XMLHttpRequest();
          xhr.onreadystatechange = function () {
            if (xhr.readyState === 4 && xhr.status === 200) {
              const result = JSON.parse(xhr.responseText);
              console.log("result:", result);
              uploadResultEle.innerText = xhr.responseText;
            }
          };
          xhr.upload.addEventListener(
            "progress",
            function (event) {
              if (event.lengthComputable) {
                progressValueEle.innerText = Math.ceil((event.loaded * 100) / event.total) + "%";
              }
            },
            false
          );
          xhr.open("POST", "http://127.0.0.1:3000/upload", true);
          xhr.send(data);
        }
      } catch (e) {
        console.log("error:", e);
      }
    </script>
  </body>

大文件上傳-客戶端

<body>
    <input id="uploadFile" type="file" />
    <button type="button" id="uploadBtn" onClick="startUpload()">開始上傳</button>
    <div class="progress">上傳進(jìn)度:<span id="progressValue">0</span></div>
    <div id="uploadResult" class="result"></div>
    <script src="./fileUtils.js"></script>
    <script src="./spark-md5.min.js"></script>
    <script src="./index.js"></script>
    <script>
      const uploadFileEle = document.getElementById("uploadFile");
      const progressValueEle = document.getElementById("progressValue");
      const uploadResultEle = document.getElementById("uploadResult");
      try {
        function startUpload() {
          if (!uploadFileEle.files.length) return;
          //獲取文件
          const file = uploadFileEle.files[0];
          window.upload.start(file);
        }
      } catch (e) {
        console.log("error:", e);
      }
    </script>
  </body>

fileUtils

// 文件分片
function handleFileChunk(file, chunkSize) {
    const fileChunkList = [];
    // 索引值
    let curIndex = 0;
    while (curIndex < file.size) {
        // 最后一個(gè)切片以實(shí)際結(jié)束大小為準(zhǔn)。
        const endIndex = curIndex + chunkSize < file.size ? curIndex + chunkSize : file.size;
        // 截取當(dāng)前切片大小
        const curFileChunkFile = file.slice(curIndex, endIndex);
        // 更新當(dāng)前索引
        curIndex += chunkSize;
        fileChunkList.push({ file: curFileChunkFile });
    }
    return fileChunkList;
}
//設(shè)置默認(rèn)切片大小為5M
const DefaultChunkSize = 5 * 1024 * 1024;
const start = async function (bigFile) {
  // 生成多個(gè)切片
  const fileList = handleFileChunk(bigFile, DefaultChunkSize);
  // 獲取整個(gè)大文件的內(nèi)容hash,方便實(shí)現(xiàn)秒傳
  // const containerHash = await getFileHash(fileList);
  const containerHash = await getFileHash2(bigFile);
  // 給每個(gè)切片添加輔助內(nèi)容信息
  const chunksInfo = fileList.map(({ file }, index) => ({
    // 整個(gè)文件hash
    fileHash: containerHash,
    // 當(dāng)前切片的hash
    hash: containerHash + "-" + index,
    // 當(dāng)前是第幾個(gè)切片
    index,
    // 文件個(gè)數(shù)
    fileCount: fileList.length,
    // 切片內(nèi)容
    chunk: file,
    // 文件總體大小
    totalSize: bigFile.size,
    // 單個(gè)文件大小
    size: file.size,
  }));
  //上傳所有文件
  uploadChunks(chunksInfo, bigFile.name);
};
/**
 *
 * 獲取全部文件內(nèi)容hash
 * @param {any} fileList
 */
async function getFileHash(fileList) {
  console.time("filehash");
  const spark = new SparkMD5.ArrayBuffer();
  // 獲取全部?jī)?nèi)容
  const result = fileList.map((item, key) => {
    return getFileContent(item.file);
  });
  try {
    const contentList = await Promise.all(result);
    for (let i = 0; i < contentList.length; i++) {
      spark.append(contentList[i]);
    }
    // 生成指紋
    const res = spark.end();
    console.timeEnd("filehash");
    return res;
  } catch (e) {
    console.log(e);
  }
}
/**
 *
 * 獲取全部文件內(nèi)容hash
 * @param {any} fileList
 */
async function getFileHash2(fileList) {
  console.time("filehash");
  const spark = new SparkMD5.ArrayBuffer();
  // 獲取全部?jī)?nèi)容
  const content = await getFileContent(fileList);
  try {
    spark.append(content);
    // 生成指紋
    const result = spark.end();
    console.timeEnd("filehash");
    return result;
  } catch (e) {
    console.log(e);
  }
}
/**
 *
 * 獲取文件內(nèi)容
 * @param {any} file
 */
function getFileContent(file) {
  return new Promise((resolve, reject) => {
    const fileReader = new FileReader();
    // 讀取文件內(nèi)容
    fileReader.readAsArrayBuffer(file);
    fileReader.onload = (e) => {
      // 返回讀取到的文件內(nèi)容
      resolve(e.target.result);
    };
    fileReader.onerror = (e) => {
      reject(fileReader.error);
      fileReader.abort();
    };
  });
}
/**
 *
 * 上傳所有的分片
 * @param {any} chunks
 * @param {any} fileName
 */
async function uploadChunks(chunks, fileName) {
  const requestList = chunks
    .map(({ chunk, hash, fileHash, index, fileCount, size, totalSize }) => {
      //生成每個(gè)切片上傳的信息
      const formData = new FormData();
      formData.append("hash", hash);
      formData.append("index", index);
      formData.append("fileCount", fileCount);
      formData.append("size", size);
      formData.append("splitSize", DefaultChunkSize);
      formData.append("fileName", fileName);
      formData.append("fileHash", fileHash);
      formData.append("chunk", chunk);
      formData.append("totalSize", totalSize);
      return { formData, index };
    })
    .map(async ({ formData, index }) =>
      singleRequest({
        url: "http://127.0.0.1:3000/uploadBigFile",
        data: formData,
      })
    );
  //全部上傳
  await Promise.all(requestList);
}
/**
 * 單個(gè)文件上傳
 */
function singleRequest({ url, method = "post", data, headers = {} }) {
  return new Promise((resolve) => {
    const xhr = new XMLHttpRequest();
    xhr.open(method, url);
    Object.keys(headers).forEach((key) => xhr.setRequestHeader(key, headers[key]));
    xhr.send(data);
    xhr.onload = (e) => {
      resolve({
        data: e.target.response,
      });
    };
  });
}
window.upload = {
  start: start,
};

大文件上傳-服務(wù)端

...
import { checkFileIsMerge, chunkMerge } from "./upload";
const multiparty = require("multiparty");
const fse = require("fs-extra");
// 上傳成功后返回URL地址
const resourceUrl = `http://127.0.0.1:${port}/`;
// 存儲(chǔ)文件目錄
const uploadDIr = path.join(__dirname, "/upload");
//設(shè)置靜態(tài)訪問目錄
app.use(express.static(uploadDIr));
const extractExt = (filename) => filename.slice(filename.lastIndexOf("."), filename.length); // 提取后綴名
app.post("/uploadBigFile", function (req, res, _next) {
  const multipart = new multiparty.Form();
  multipart.parse(req, async (err, fields, files) => {
    if (err) {
      console.error(err);
      return res.json({
        code: 5000,
        data: null,
        msg: "上傳文件失敗",
      });
    }
    //取出文件內(nèi)容
    const [chunk] = files.chunk;
    //當(dāng)前chunk 文件hash
    const [hash] = fields.hash;
    //大文件的hash
    const [fileHash] = fields.fileHash;
    //大文件的名稱
    const [fileName] = fields.fileName;
    //切片索引
    const [index] = fields.index;
    //總共切片個(gè)數(shù)
    const [fileCount] = fields.fileCount;
    //當(dāng)前chunk 的大小
    // const [size] = fields.size;
    const [splitSize] = fields.splitSize;
    //整個(gè)文件大小
    const [totalSize] = fields.totalSize;
    const saveFileName = `${fileHash}${extractExt(fileName)}`;
    //獲取整個(gè)文件存儲(chǔ)路徑
    const filePath = path.resolve(uploadDIr, saveFileName);
    const chunkDir = path.resolve(uploadDIr, fileHash);
    // 大文件存在直接返回,根據(jù)內(nèi)容hash存儲(chǔ),可以實(shí)現(xiàn)后續(xù)秒傳
    if (fse.existsSync(filePath)) {
      return res.json({
        code: 1000,
        data: { url: `${resourceUrl}${saveFileName}` },
        msg: "上傳文件已存在",
      });
    }
    // 切片目錄不存在,創(chuàng)建切片目錄
    if (!fse.existsSync(chunkDir)) {
      await fse.mkdirs(chunkDir);
    }
    const chunkFile = path.resolve(chunkDir, hash);
    if (!fse.existsSync(chunkFile)) {
      await fse.move(chunk.path, path.resolve(chunkDir, hash));
    }
    const isMerge = checkFileIsMerge(chunkDir, Number(fileCount), fileHash);
    if (isMerge) {
      //合并
      await chunkMerge({
        filePath: filePath,
        fileHash: fileHash,
        chunkDir: chunkDir,
        splitSize: Number(splitSize),
        fileCount: Number(fileCount),
        totalSize: Number(totalSize),
      });
      return res.json({
        code: 1000,
        data: { url: `${resourceUrl}${saveFileName}` },
        msg: "文件上傳成功",
      });
    } else {
      return res.json({
        code: 200,
        data: { url: `${resourceUrl}${filePath}` },
        msg: "文件上傳成功",
      });
    }
  });
});

upload.ts

const fse = require("fs-extra");
const path = require("path");
/**
 * 讀流,寫流
 * @param path
 * @param writeStream
 * @returns
 */
const pipeStream = (path, writeStream) =>
    new Promise((resolve) => {
        const readStream = fse.createReadStream(path);
        readStream.on("end", () => {
            // fse.unlinkSync(path);
            resolve(null);
        });
        readStream.pipe(writeStream);
    });
/**
 * 
 * 合并所有切片
 * @export
 * @param {any} {
 *     filePath:文件路徑包含后綴名
 *     fileHash:文件hash
 *     chunkDir:切片存放的臨時(shí)目錄
 *     splitSize:每個(gè)切片的大小
 *     fileCount:文件總個(gè)數(shù)
 *     totalSize:文件總大小
 * } 
 * @returns 
 */
export async function chunkMerge({
    filePath,
    fileHash,
    chunkDir,
    splitSize,
    fileCount,
    totalSize,
}) {
    const chunkPaths = await fse.readdir(chunkDir);
    //帥選合適的切片
    const filterPath = chunkPaths.filter((item) => {
        return item.includes(fileHash);
    });
    //數(shù)量不對(duì),拋出錯(cuò)誤
    if (filterPath.length !== fileCount) {
        console.log("合并錯(cuò)誤");
        return;
    }
    // 根據(jù)切片下標(biāo)進(jìn)行排序,方便合并
    filterPath.sort((a, b) => a.split("-")[1] - b.split("-")[1]);
    await Promise.all(
        chunkPaths.map((chunkPath, index) => {
            //并發(fā)寫入,需要知道開始和結(jié)束位置
            let end = (index + 1) * splitSize;
            if (index === fileCount - 1) {
                end = totalSize + 1;
            }
            return pipeStream(
                path.resolve(chunkDir, chunkPath),
                // 指定位置創(chuàng)建可寫流
                fse.createWriteStream(filePath, {
                    start: index * splitSize,
                    end: end,
                })
            );
        })
    );
    //刪除所有切片
    // fse.rmdirSync(chunkDir); // 合并后刪除保存切片的目錄
    return filePath;
}

/**
 * 
 * 檢查切片是否可以合并
 * @export
 * @param {any} pathName 切片存儲(chǔ)目錄
 * @param {any} totalCount 大文件包含切片個(gè)數(shù)
 * @param {any} hash 大文件hash
 * @returns 
 */
export function checkFileIsMerge(pathName, totalCount, hash) {
    var dirs = [];
    //同步讀取切片存儲(chǔ)目錄
    const readDir = fse.readdirSync(pathName);
    //判斷目錄下切片數(shù)量 小于 總切片數(shù),不能合并
    if (readDir && readDir.length < totalCount) return false;
    //獲取目錄下所有真正屬于該文件的切片,以大文件hash為準(zhǔn)
    (function iterator(i) {
        if (i == readDir.length) {
            return;
        }
        const curFile = fse.statSync(path.join(pathName, readDir[i]));
        //提出目錄和文件名不包含大文件hash的文件
        if (curFile.isFile() && readDir[i].includes(hash + "")) {
            dirs.push(readDir[i]);
        }
        iterator(i + 1);
    })(0);
    //數(shù)量一直,可以合并
    if (dirs.length === totalCount) {
        return true;
    }
    return false;
}

這里的大文件上傳有幾處問題,我沒有解決,留給各位思考啦

  • 內(nèi)容hash計(jì)算速度如何提升(serviceworker)
  • 文件上傳進(jìn)度
  • 斷點(diǎn)續(xù)傳

以上就是JavaScript進(jìn)階之前端文件上傳和下載示例詳解的詳細(xì)內(nèi)容,更多關(guān)于JavaScript前端文件上傳下載的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論