# 文件上传相关

# 文件上传思路

# 客户端

  • 🍠1、input标签选择文件/拖拽方式选择文件/复制到剪切板获取文件
  • 🍿2、File Api获取文件信息
  • 🍪3、XMLHttpRequest/fetch 上传
  • 🍵4、上传数据 FormaData/Blob等,服务端使用mutipart/form-data

# 服务端

  • 🍗1、设置文件存储目录
  • 🥘2、是否更改文件名称 (客户端是否传名称)
  • 🍙3、上传成功,通知客户端可以访问的url
  • 😱4、url的产生,需要启动静态资源服务

# 单文件上传









 























































<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>上传单个文件</title>
  </head>

  <body>
    <input id="uploadFile" type="file" accept="image/*" />
    <!-- 
      type属性file,用户选择文件
      accept属性 规定选择文件类型
      multiple  是否可以上传多个文件
     -->

    <button type="button" id="uploadBtn" onClick="startUpload()">
      开始上传
    </button>
    <div class="progress">上传进度:<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];
          //创建上传数据
          const formData = new FormData();
          formData.append("file", file);
          //上传文件
          this.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);
              uploadResultEle.innerText = xhr.responseText;
            }
          };

          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>
</html>

# 多文件上传

<input id="uploadFile" type="file" multiple accept="image/*" />

# 大文件上传

大文件上传

# 切片

/**
 * const DefaultChunkSize = 5 * 1024 * 1024;
 * 文件分片
 * @param {*} file 文件
 * @param {*} chunkSize 分片大小
 * @returns 
 */
const handleFileChunk = function (file, chunkSize) {
    const fileChunkList = [];
    // 索引值
    let curIndex = 0;
    while (curIndex < file.size) {
        //最后一个切片以实际结束大小为准。
        const endIndex = curIndex + chunkSize < file.size ? curIndex + chunkSize : file.size;
        const curFileChunkFile = file.slice(curIndex, endIndex);
        curIndex += chunkSize;
        fileChunkList.push({ file: curFileChunkFile })
    }
    return fileChunkList;
};

# 获取全部文件hash

/**
 * 
 * 获取文件内容
 * @param {any} file 
 * @returns 
 */
function getFileContent(file) {
    return new Promise((resolve, reject) => {
        const fileReader = new FileReader();
        //读取文件内容
        fileReader.readAsArrayBuffer(file);
        fileReader.onload = (e) => {
            //返回读取到的文件内容
            resolve(e.target.result);
        };
        fileReader.onerror = (e) => {
            reject(fileReader.error);
            fileReader.abort();
        };
    });
}

/**
 * spark-md5.min.js
 * 获取全部文件内容hash
 * @param {any} fileList 
 */
async function getFileHash2(file) {

    console.time("filehash")
    const spark = new SparkMD5.ArrayBuffer();
    //获取全部内容
    const content = await getFileContent(file)
    try {
        spark.append(content);
        //生成指纹
        const result =  spark.end();
        console.timeEnd("filehash")
        return result;
    } catch (e) {
        console.log(e);
    }
}

# 上传所有切片


/**
 * 
 * 单个文件上传
 * @param {any} {
 *     url,
 *     method="post",
 *     data,
 *     headers={},
 * } 
 * @returns 
 */
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
            });
        };
    });
}



/**
 * 
 * 上传所有的分片
 * @param {any} chunks 
 * @param {any} fileName 
 */
async function uploadChunks(chunks, fileName) {
    const requestList = chunks
        .map(({ chunk, hash, fileHash, index, fileCount, size, totalSize }) => {
            //生成每个切片上传的信息
            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);
}

# 全部逻辑

//设置默认切片大小为5M
const DefaultChunkSize = 5 * 1024 * 1024;

const start = async function (bigFile) {
    //生成多个切片
    const fileList = handleFileChunk(bigFile, DefaultChunkSize);
    //获取整个大文件的内容hash,方便实现秒传
    // const containerHash = await getFileHash(fileList);
    const containerHash = await getFileHash2(bigFile);
    //给每个切片添加辅助内容信息
    const chunksInfo = fileList.map(({ file }, index) => ({
        //整个文件hash
        fileHash: containerHash,
        //当前是第几个切片
        index,
        //文件个数
        fileCount: fileList.length,
        //当前切片的hash
        hash: containerHash + "-" + index,
        //切片内容
        chunk: file,
        //文件总体大小
        totalSize: bigFile.size,
        //单个文件大小
        size: file.size
    }));

    //上传所有文件
    uploadChunks(chunksInfo, bigFile.name);
};
Last Updated: 10/29/2022, 2:14:54 PM