文件分段上传案列(参考b站小野森森老师的课)

文件分段上传案列

1.什么是分片上传
分片上传,就是将所要上传的文件,按照一定的大小,分割成多个块,来进行分别上传,上传完成之后再由后端进行合并,也就是不断往一个文件里append,直到合并成一个完整的文件。

2.分片上传的场景

  • 大文件上传
  • 网络环境环境不好,存在需要重传风险的场景

3.具体案例
首先创建两个文件夹,一个前端,一个后端
进入前端文件夹后分别执行

npm init -y(生成package文件)    
yarn add  vite -D
再更改package中的配置为  "dev": "vite"

然后进入后端文件夹执行

npm init -y    
yarn add express express-fileupload 
yarn global add nodemon
更改package中的配置
"dev": "nodemon ./app.js"

具体代码如下:
在这里插入图片描述

前端

app.js

import {
    UPLOAD_INFO,
    ALLOWED_TYPE,
    API
} from './config';
//;立即执行函数
;
((doc) => { //因为是通过原生dom来写东西,所以要先获取到元素
    const oProgress = doc.querySelector('#uploadProgress');
    //选择一个视频 
    const oUploader = doc.querySelector('#videoUploader');
    const oInfo = doc.querySelector('#uploadInfo');
    const oBtn = doc.querySelector('#uploadBtn');
    let uploadedSize = 0; //当前一共上传了多少
    const init = () => {
            bindEvent();
    }
    //事件处理函数
    function bindEvent() {
        //监听点击时,把选择好的视频进行上传
        oBtn.addEventListener('click', uploadVideo, false);
    }
    async function uploadVideo() { //开始做上传任务
    //选择好要上传的视频后,可以拿到一个file,这个file包含当前文件的名称、大小、类型以及创建时间等,
        console.log(oUploader.files, 11)
        const file = oUploader.files[0];
        console.log(file)
        if (!file) { //没选择文件时提示选择文件
            oInfo.innerText = '请先选择文件'
            return;
        }
        if (!ALLOWED_TYPE[file.type]) {
            oInfo.innerText = '不支持该类型文件上传';
            return;
        }
        const { name, type, size } = file; //解构
        const fileName = new Date().getTime() + '_' + name; //上传文件的名称
        const CHUNK_SIZE = 64 * 1024; //单个切片的大小
        let uploadedResult = null;
        oProgress.max = size; //一共需要完成多少工作
        oInfo.innerText = ''; //清空
        while (uploadedSize < size) { //当前以上传文件的大小<文件总的大小
            const fileChunk = file.slice(uploadedSize, uploadedSize + CHUNK_SIZE);
            const formData = createFormData({
                name,
                type,
                size,
                fileName,
                uploadedSize,
                file: fileChunk
            });
            try {
                //给后端穿的参数
                uploadedResult = await axios.post(API.UPLOAD_VIDEO, formData);
                console.log(uploadedResult)
            } catch (e) {
                //提示上传失败
                oInfo.innerText = `${ UPLOAD_INFO['UPLOAD_FAILED'] }${ e.message }`;
                return;
            }
            uploadedSize += fileChunk.size;
            oProgress.value = uploadedSize; //进度条已经完成的工作量
        }
        oInfo.innerText = '上传成功';
        oUploader.value = null;
        createVideo(uploadedResult.data.video_url);
    }

    function createFormData({
        name,
        type,
        size,
        fileName,
        uploadedSize,
        file
    }) {
        const fd = new FormData();

        fd.append('name', name);
        fd.append('type', type);
        fd.append('size', size);
        fd.append('fileName', fileName);
        fd.append('uploadedSize', uploadedSize);
        fd.append('file', file);

        return fd;
    }

    function createVideo(src) {
        const oVideo = document.createElement('video');
        oVideo.controls = true;
        oVideo.width = '500';
        oVideo.src = src;
        document.body.appendChild(oVideo);
    }

    init();
})(document);

要注意的是每一个模块都是有自己的开关的,这里面的开关就是init ,用执行init,来决定这个模块工不工作,需要有一个事件处理函数,也就是bindEvent,用它来监听上传视频这个按钮,当按钮被点击时,触发uploadVideo,开始做上传的任务,可以先打印一下选择的文件都包含哪些信息,也就是打印oUploader.files,打开浏览器选择视频进行上传,发现打印出来一个对象,他的第0项是file,也就是文件的基本 信息,所以声明一个file=oUploader.files[0],此时再打印file,发现他包括文件的名字大小类型以及创建的时间等,然后接下来需要对它进行判断,如果没有选择文件,则提示请先选择文件,如果选择文件类型不正确,则提示不支持该类型文件上传,接下来对file进行解构,获得文件的名字大小以及类型,然后声明一个fileName=时间戳加下划线以及文件名称,用于代表单个切片的名字,
相当于uploadedSize < size让他切割,从uploadedSize,切割到uploadedSize + CHUNK_SIZE,
然后还需要有formData,因为不仅要传fileChunk,还要传type、name,fileChunk等
config.js

const BASE_URL = 'http://localhost:8000/';
export const UPLOAD_INFO = {
    'NO_FILE': '请先选择文件',
    'INVALID_TYPE': '不支持该类型文件上传',
    'UPLOAD_FAILED': '上传失败',
    'UPLOAD_SUCCESS': '上传成功'
}
export const ALLOWED_TYPE = {
    'video/mp4': 'mp4',
    'video/ogg': 'ogg'
}

export const API = {
    UPLOAD_VIDEO: BASE_URL + 'upload_video'
}

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="uploadProgress" 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>

    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <!--运行es module    所以type="module"-->
    <script src="./src/app.js" type="module"></script>
</body>

</html>

package.json

{
  "name": "client",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "vite"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "vite": "^3.1.7"
  }
}

后端

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;

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(uploader());
app.use('/', express.static('upload_temp'));

const ALLOWED_TYPE = {
    'video/mp4': 'mp4',
    'video/ogg': 'ogg'
}
app.all('*', (req, res, next) => {
    res.header('Access-Control-Allow-origin', '*');
    res.header('Access-Control-Allow-Methods', 'POST,GET');
    next();
});
//api地址     请求    响应
app.post('/upload_video', (req, res) => {
    const {
        name,
        type,
        size,
        fileName,
        uploadedSize
    } = 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: 'The type is not allowed for uploading.'
        });
        return;
    }
    const filename = fileName + extname(name);
    const filePath = resolve(__dirname, './upload_temp/' + filename);
    if (uploadedSize !== '0') {
        if (!existsSync(filePath)) {
            res.send({
                code: 1003,
                msg: 'No file exists'
            });
            return;
        }
        appendFileSync(filePath, file.data);
        res.send({
            code: 0,
            msg: 'Appended',
            video_url: '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);
});

package.json

{
  "name": "server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "nodemon ./app.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.18.1",
    "express-fileupload": "^1.4.0",
    "nodemon": "^2.0.20"
  }
}

上传完成的文件会被存储在upload_temp文件夹里

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值