• [网络] 前端大文件上传


    思路

    前端切片文件, 后端组合成完整文件

    流程

    创建 client 和 server 目录

    client

    1. npm init -y
    2. npm i vite -D
    3. 修改 package.json 配置
    "scripts": {
        "dev": "vite"
    },
    
    • 1
    • 2
    • 3
    1. client 目录下创建 index.html 文件
    2. 创建 client/src 目录, 新建 app.js 文件
    3. 在 index.html 中使用 script 标签引入 app.js, 引入时设置 type=module
    <script src="./src/app.js" type="module"></script>
    
    • 1
    // index.html
    <!DOCTYPE html>
    <html lang="en">
      <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>Document</title>
      </head>
      <body>
        <div>
          <p>
            <progress id="uploadProgess" value="0"></progress>
          </p>
          <p>
            <input type="file" id="videoUploader" value="选择视频" />
          </p>
          <p>
            <span id="uploadInfo"></span>
          </p>
          <p>
            <button id="uploadBtn">上传视频</button>
          </p>
        </div>
    
        <video
          src=""
          id="video-play"
          autoplay
          controls
          style="display: none"
        ></video>
    
        <script src="./src/app.js" type="module"></script>
      </body>
    </html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    // app.js
    import { UPLOAD_INFO, ALLOWED_TYPE, CHUNK_SIZE, API } from "./config.js";
    ((doc) => {
      const doms = {
        uploadProgess: doc.getElementById("uploadProgess"),
        videoUploader: doc.getElementById("videoUploader"),
        uploadInfo: doc.getElementById("uploadInfo"),
        uploadBtn: doc.getElementById("uploadBtn"),
        videoPlay: doc.getElementById("video-play"),
      };
    
      let uploadSize = 0;
      let videoUrlPlay = "";
    
      const init = () => {
        bindEvent();
      };
    
      function bindEvent() {
        doms.uploadBtn.addEventListener("click", uploadVideo, false);
      }
    
      async function uploadVideo() {
        // const file = doms.videoUploader.files[0];
        const {
          files: [file],
        } = doms.videoUploader;
    
        // 检测是否选择文件
        if (!file) {
          doms.uploadInfo.innerText = UPLOAD_INFO["NO_FILE"];
          return;
        }
    
        // 检测文件类型
        if (!ALLOWED_TYPE[file.type]) {
          doms.uploadInfo.innerHTML = UPLOAD_INFO["FILE_TYPE_ERROR"];
          return;
        }
    
        const { name, type, size } = file;
        const fileName = new Date().getTime() + "_" + name;
        doms.uploadProgess.max = size;
        doms.uploadInfo.innerHTML = "";
    
        while (uploadSize < size) {
          //   console.log(uploadSize);
          // 文件切分
          const fileChunk = file.slice(uploadSize, uploadSize + CHUNK_SIZE);
    
          // 创建 FormData
          const formData = createFormData({
            name,
            type,
            size,
            fileName,
            uploadSize,
            file: fileChunk,
          });
    
          try {
            let res = await fetch(API.UPLOAD_VIDEO, {
              method: "POST",
              body: formData,
            });
            const { videoUrl } = await res.json();
            videoUrlPlay = videoUrl;
          } catch (e) {
            doms.uploadInfo.innerText = `${UPLOAD_INFO["UPLOAD_FAILED"]}: ${e.message}`;
            return;
          }
          uploadSize += fileChunk.size;
          doms.uploadProgess.value = uploadSize;
          doms.uploadInfo.innerText = UPLOAD_INFO["UPLOAD_SUCCESS"];
          doms.videoUploader.value = null;
        }
        // console.log(videoUrlPlay);
        doms.videoPlay.src = videoUrlPlay;
        doms.videoPlay.style.display = "block";
      }
    
      function createFormData({ name, type, size, fileName, uploadSize, file }) {
        const fd = new FormData();
        fd.append("name", name);
        fd.append("type", type);
        fd.append("size", size);
        fd.append("fileName", fileName);
        fd.append("uploadSize", uploadSize);
        fd.append("file", file);
    
        return fd;
      }
    
      init();
    })(document);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    // config.js
    export const UPLOAD_INFO = {
      NO_FILE: "请先选择文件",
      FILE_TYPE_ERROR: "不支持该类型文件",
      UPLOAD_FAILED: "上传失败",
      UPLOAD_SUCCESS: "上传成功",
    };
    
    export const ALLOWED_TYPE = {
      "video/mp4": "mp4",
    };
    
    export const CHUNK_SIZE = 64 * 1024;
    
    const BASE_URL = "http://localhost:8000/";
    export const API = {
      UPLOAD_VIDEO: BASE_URL + "upload_video",
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    server

    1. npm init -y
    2. npm i express express-fileupload
    3. 修改 package.json 配置
      "scripts": {
        "dev": "nodemon ./app.js"
      },
    
    • 1
    • 2
    • 3
    1. 在 server 目录下创建 app.js 文件
    // app.js
    const express = require("express");
    const bodyParser = require("body-parser");
    const uploader = require("express-fileupload");
    const { extname, resolve } = require("path");
    const { existsSync, appendFileSync, writeFileSync } = require("fs");
    
    const app = express();
    const PORT = 8000;
    const ALLOWED_TYPE = {
      "video/mp4": "mp4",
    };
    
    app.use(bodyParser.urlencoded({ extended: true }));
    app.use(bodyParser.json());
    app.use(uploader());
    app.use("/", express.static("upload_temp"));
    
    // 跨域处理
    app.all("*", (req, res, next) => {
      res.header("Access-Control-Allow-origin", "*");
      res.header("Access-Control-Allow-Methods", "POST,GET");
      //   next();
      next();
    });
    
    app.post("/upload_video", (req, res) => {
      const { name, type, size, fileName, uploadSize } = req.body;
      const { file } = req.files;
    
      // 检测是否发送文件
      if (!file) {
        res.send({
          code: 1001,
          msg: "no file uploaded",
        });
        return;
      }
      // 检测文件类型
      if (!ALLOWED_TYPE[type]) {
        res.send({
          code: 1002,
          msg: "file type error",
        });
        return;
      }
      const filename = fileName + extname(name);
      const filePath = resolve(__dirname, "./upload_temp/", filename);
    
      if (uploadSize !== "0") {
        if (!existsSync(filePath)) {
          res.send({
            code: 1003,
            msg: "no file exist",
          });
          return;
        }
        appendFileSync(filePath, file.data);
        res.send({
          code: 0,
          msg: "append",
          videoUrl: "http://localhost:8000/" + filename,
        });
        return;
      }
      writeFileSync(filePath, file.data);
      res.send({
        code: 0,
        msg: "file is created",
      });
    });
    
    // 启动服务器
    app.listen(PORT, () => {
      console.log("server is running on " + PORT);
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
  • 相关阅读:
    JAVA动漫周边产品销售管理系统计算机毕业设计Mybatis+系统+数据库+调试部署
    2.6W字系统总结,带你实现 Linux 自由!
    C++ Reference: Standard C++ Library reference: C Library: cwchar: wcsrtombs
    『Java安全』Struts 2.3.14.2 action占位符OGNL注入漏洞S2-015复现与浅析
    AVL树的插入--旋转笔记
    计及调频成本和荷电状态恢复的多储能系统调频功率双层优化【蓄电池经济最优目标下充放电】(基于matlab+yalmip+cplex的蓄电池出力优化)
    Flink TaskManager的Memory Model内存模型
    KUKA机器人后台控制程序(SPS)介绍
    P1918 保龄球
    python中的NaN在质量控制中怎么处理?
  • 原文地址:https://blog.csdn.net/aixintianshideshouhu/article/details/125480901