[网络] 前端大文件上传

思路

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

流程

创建 client 和 server 目录

client

  1. npm init -y
  2. npm i vite -D
  3. 修改 package.json 配置
"scripts": {
    "dev": "vite"
},
  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>
// 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>
// 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);
// 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",
};

server

  1. npm init -y
  2. npm i express express-fileupload
  3. 修改 package.json 配置
  "scripts": {
    "dev": "nodemon ./app.js"
  },
  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);
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值