大文件怎么实现切片上传?「硬核」

大家好我是刷子哥,今天学了下大文件上传马上把笔记分享出来大家一起学习;

夫学须志也,才须学也,非学无以广才,非志无以成学(要知真知必须使身心在宁静中研究探讨,人们的才能是从不断学习中积累起来的,不学习就难以增长才干,不立志就难以学有所成)。——诸葛亮 ,废话不说直接干货。

首先这个大文件上传的思路就是前端把 file用slice进行切割将文件切割成指定的等份,while 循环调用上传的接口向后端发送数据,直到切割文件的大小和文件的大小一致就终止循环。后端有文件就进行叠加文件,没有就新建一个文件。

这次我们在根目录新建两个文件夹一个是后端一个是前端的代码。

1、初始化前端的项目npm i init -y 然后这是前端的依赖

{
    
    
  "name": "uploadr",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    
    
    "test": "vite"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    
    
    "axios": "^1.1.3",
    "vite": "^3.2.2"
  }
}

2、html结构代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>上传文件</title>
</head>
<body>
 <ul>
     <li>
         <progress value="0" id="uploadProgress"> </progress>
     </li>
     <li>
         <input type="file" id="fileUpload" value="请选则视频"/>
     </li>
     <li>
         <span id="uploadInfo">你都上传了那些文件</span>
     </li>
     <li>
         <button id="uploadBtn">点击上传</button>
     </li>
 </ul>
<!-- SyntaxError: Cannot use import statement outside a module (at  加上type='module'-->
<script type="module" src="./index.js"></script>
</body>
</html>

3、定义一些公用的配置 config.js

// 字典
export const UPLOADTYPE = {
    
    
    'video/mp4':'mp4',
    'video/mp3':'mp3'
}
export const UPLOADINFO = {
    
    
    'NO_INFO':'请你您选择文件进行上传',
    'TYPE_INFO':'您上传的文件格式不正确,仅支持mp4,mp3',
    'UPLOAD_LOADING':'上传中',
    'UPLOAD_ERROR':'上传失败了请您检查网络',
    'UPLOAD_SUCCESS':'上传成功'
}
// 路径
export const BASEURL = 'http://localhost:8000/file/upload'
// 每次切多少
export const CHUNKSIZE =  64 * 1024

4、前端核心的js代码 内有注释

import {
    
    UPLOADINFO, UPLOADTYPE, BASEURL, CHUNKSIZE} from './cofig'
import axios from "axios";

;((dcu) => {
    
    
    let uploadBtn = dcu.querySelector('#uploadBtn');
    let uploadProgress = dcu.querySelector('#uploadProgress');
    let fileUpload = dcu.querySelector('#fileUpload');
    let uploadInfo = dcu.querySelector('#uploadInfo');


    let uploadSize = 0 //用于保存当前上传了多少

    const init = () => {
    
    
        bindEvent()
    }

    function bindEvent() {
    
    
        uploadBtn.addEventListener('click', uploadHandler, false)
    }

    async function uploadHandler() {
    
    
        // let file = fileUpload.files[0]
        const {
    
    files: [file]} = fileUpload
        console.log(file)
        if (!file) {
    
     // 没有文件
            uploadInfo.innerText = UPLOADINFO['NO_INFO']
            return;
        }
        if (!UPLOADTYPE[file.type]) {
    
     // 规定上传的类型
            uploadInfo.innerText = UPLOADINFO['TYPE_INFO']
            return
        }

        const {
    
    type, name, size} = file
        let fileName = new Date().getTime() + '_' + name
        uploadProgress.max = size
        uploadInfo.innerText = uploadInfo.innerText = UPLOADINFO['UPLOAD_LOADING']
        let res; // 返回成功的结果
        while (uploadSize < size) {
    
     // 规定的文件小于要上传的文件就切片处理
            let chunk = file.slice(uploadSize, uploadSize + CHUNKSIZE)
            const formData = createFormData({
    
    
                name: name,
                type: type,
                fileName: fileName,
                size: size,
                uploadSize: uploadSize,
                file: chunk
            })
            console.log('idwa', formData) // 上传的fromData对象

            try {
    
    
                res = await axios.post(BASEURL, formData) // 上传的接口
                console.log('dwa', res)
            } catch (err) {
    
    
                uploadInfo.innerText = uploadInfo.innerText = `${
      
      UPLOADINFO['UPLOAD_ERROR']} +  ${
      
      err}`
                throw err
            }
            // 拼接上传的数据
            uploadSize += chunk.size // 拼接切割的size
            uploadProgress.value = uploadSize // 进度条的zize

        }

        uploadInfo.innerText = uploadInfo.innerText = UPLOADINFO['UPLOAD_SUCCESS']
        // 上传完成创建视频标签
        if(res.data.fileUrl) await createVideo(res.data.fileUrl) // 上传成功创建视频标签
    }

    function createFormData({
     
     name, type, fileName, size, uploadSize, file}) {
    
      // 创建formData对象
        let fd = new FormData()
        fd.append('name', name)
        fd.append('type', type)
        fd.append('fileName', fileName)
        fd.append('size', size)
        fd.append('uploadSize', uploadSize)
        fd.append('file', file)
        return fd
    }

    function createVideo (url) {
    
     //创建一个视频的标签插入到页面
        console.log(url)
        const eleVideo = document.createElement('video')
        eleVideo.controls = true
        eleVideo.width = '500'
        eleVideo.src = url
        document.body.appendChild(eleVideo)
    }
    // 还有判断 size 大小的逻辑 以及上传中按钮隐藏... 大家自己补充吧

    init()
})(document);

后端模块

后端主要用到了nodejs、express、express-fileupload、 fs、 path、

初始化后端的项目npm i init -y 然后这是前端的依赖

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

1、后端核心模块

  • const filePath = resolve(__dirname, './uploadTemp/' + nameFile) 这个就是文件的操作,去找到同级别的 uploadTemp文件,上传的视频都会到 uploadTemp 文件夹。

    Fs

  • fs.existsSync() 方法用于同步检查给定路径中是否已存在文件。它返回一个布尔值,该值指示文件的存在。

  • fs.appendFileSync() 方法的用途是通过异步的方法将文本内容或数据添加到文件里,如果文件不存在则会自动创建

  • fs.writeFileSync() 方法用于将数据同步写入文件。如果该文件已经存在,则替换该文件。 “ options”参数可用于修改方法的函数。

    Path

  • path.extname() 方法返回 path 的扩展名,即 path 的最后一部分中从最后一次出现的 .(句点)字符到字符串的结尾。 如果 path 的最后一部分中没有 .,或者除了 path 的基本名称(参见 path.basename())的第一个字符之外没有 . 个字符,则返回空字符串。

  • path.resolve() 方法会把一个路径或路径片段的序列解析为一个绝对路径。
    给定的路径的序列是从右往左被处理的,后面每个 path 被依次解析,直到构造完成一个绝对路径。 例如,给定的路径片段的序列为:/foo、/bar、baz,则调用 path.resolve(‘/foo’, ‘/bar’, ‘baz’) 会返回 /bar/baz。

  • app.use('/',express.static('uploadTemp')) 当前端访问路径http://localhost:8000/1111.mp4 的时候就像当于uploadTemp文件夹下面的 1111.mp4 视频。

  • 在node.js 中 let { file } = req.files 通过 req.files 才能拿到前端传入的 file 对象。

  • app.use(bodyParser.urlencoded({extended:true})) 这是常用的方法,常见的前端请求解决方案如表单post提交、axios、fetch等库的post请求都需要这个中间件进行解析,返回json的格式数据。当请求的数据类型是application/x-www-form-urlencoded时才会进入这个中间件进行处理。

  • 解析URL-encode数据的方法,true的话使用qs库来解析,false的话使用querystring库去解决, "extended": true,

  • app.use(bodyParser.json()) 解析并返回 json格式的数据,这是常用的方法。内部会查看content-type,只有是正确的content-type默认是application/json才进入这个中间件解析处理。

const express  = require('express')
const bodyParser = require('body-parser')
const fileupload = require('express-fileupload')
const app = express()
const {
    
    extname,resolve} =  require('path')
const {
    
    existsSync,appendFileSync,writeFileSync} = require('fs')

app.all('*',(req,res,next) => {
    
    
    res.header('Access-Control-Allow-origin','*')
    res.header('Access-Control-Allow-Methods','POST,GET')
    next() // 执行中间件
}) // 解决跨域问题

app.use(bodyParser.urlencoded({
    
    extended:true})) 
app.use(fileupload()) // 使用插件
app.use(bodyParser.json()) 
app.use('/',express.static('uploadTemp')) 

app.post('/file/upload',(req,res) => {
    
    
    const UPLOADTYPE = {
    
    
        'video/mp4':'mp4',
        'video/mp3':'mp3'
    }
    console.log(req)
    let  {
    
    name,type,fileName,size,uploadSize} =  req.body
    let {
    
     file } = req.files
    if(!file) {
    
    
        res.send({
    
    
            code:101,
            msg:'NO file upload'
        })
        return
    }
    if(!UPLOADTYPE[type]) {
    
    
        res.send({
    
    
            code:102,
            msg:'the type is not allowed for uploading.'
        })
        return
    }

    let  nameFile = fileName + extname(name) // path 模块取文件的后缀名
    const  filePath = resolve(__dirname,  './uploadTemp/' + nameFile)
    console.log(name,file,nameFile)

    if(uploadSize !== '0' ) {
    
     // 有文件大小进行文件追加操作
         if(!existsSync(filePath)) {
    
    
             res.send({
    
    
                 msg:"no file exists",
                 code:1003
             })
             return;
         }
         appendFileSync(filePath,file.data)
         res.send({
    
    
            msg:"appended",
            code:0,
            fileUrl:'http://localhost:8000/' + nameFile
        }) // 返回前端可以访问视频的地址
        return;
    }

    writeFileSync(filePath,file.data) // 写入文件


    res.send({
    
    
        msg:"file is create",
        code:0
    })
})

const PORT  = 8000
app.listen(PORT , () => {
    
    
    console.log('启动了',PORT)
}) // 监听端口号

项目代码地址

地址gitee

猜你喜欢

转载自blog.csdn.net/qq_54753561/article/details/127736164
今日推荐