找回密码
 立即注册
快捷导航

[Vue/React] 大文件上传(Vue)

[复制链接]
NSFW 2023-8-14 08:37:00 | 显示全部楼层

读取文件并切片

<template>
    <div>
        <input type="file" @change="handleFileChange" />
        <button @click="handleUpload">上传</button>
    </div>
</template>

<script setup>
import axios from 'axios'

const SIZE = 10 * 1024 * 1024 // 10M
let files = null
let chunkList = null

const handleFileChange = (e) => {
    const filesList = e.target.files
    if (filesList && filesList.length) {
        files = filesList[0]
        chunkList = createChunk(filesList[0])
    }
}

const handleUpload = () => {
    const uploadList = chunkList.map(({file}, index) => ({
        file,
        size: file.size,
        percent: 0,
        chunkName: `${files.name}-${index}`,
        fileName: files.name,
        index,
        type: files.type
    }))
    uploadFile(uploadList)
}

/**
 *
 * @param file 大文件
 * @param size 切片的大小  10 * 1024 * 1024 = 10M
 */
function createChunk(file, size = SIZE) {
    //两个形参:file是大文件,size是切片的大小
    const _chunkList = []
    let cur = 0
    while (cur < file.size) {
        _chunkList.push({
            file: file.slice(cur, cur + size) //使用slice()进行切片
        })
        cur += size
    }
    return _chunkList
}

async function uploadFile(list) {
    const requestList = list.map(({file,fileName,index,chunkName}) => {
        const formData = new FormData() // 创建表单类型数据
        formData.append('file', file)//该文件
        formData.append('fileName', fileName)//文件名
        formData.append('chunkName', chunkName)//切片名
        return {formData,index}
    })
    console.log(requestList)
    requestList.map(({formData,index}) => axiosRequest({
        method: 'post',
        url: 'http://localhost:3001/upload',//请求接口,要与后端一一一对应
        data: formData
    })
           .then(res => {
               console.log(res);
               //显示每个切片上传进度
               let p = document.createElement('p')
            p.innerHTML = `${list[index].chunkName}--${res.data.message}`
            document.getElementById('progress').appendChild(p)
           })
    )
    await Promise.all(requestList)//保证所有的切片都已经传输完毕
    merge(files.size, files.name)
}

// 通知后端去做切片合并
function merge(size, fileName) {
    axiosRequest({
        url: 'http://localhost:3001/merge',//后端合并请求
        data: JSON.stringify({
            size,
            fileName
        }),
    })
}

function axiosRequest({url,data}) {
    return new Promise((resolve, reject) => {
        const config = {//设置请求头
            headers: {
                'Content-Type': 'multipart/form-data'
            }
        }
        //默认是post请求,可更改
        axios.post(url, data, config).then((res) => {
            resolve(res)
        })
    })
}
</script>

Node

const http = require('http')
const multiparty = require('multiparty')
const path = require('path')
const fse = require('fs-extra')
const server = http.createServer()
const UPLOAD_DIR = path.resolve(__dirname, '.', 'fileCache')// 读取根目录,创建一个文件夹qiepian存放切片
server.on('request', async (req, res) => {
    // 处理跨域问题,允许所有的请求头和请求源
    res.setHeader('Access-Control-Allow-Origin', '*')
    res.setHeader('Access-Control-Allow-Headers', '*')
    if (req.url === '/upload') { //前端访问的地址正确
        const multipart = new multiparty.Form() // 解析FormData对象
        try {
            multipart.parse(req, async (err, fields, files) => {
                if (err) { //解析失败
                    return res.end({
                        code: 1
                    })
                }
                // console.log('fields=', fields);
                // console.log('files=', files);
                
                const [file] = files.file
                const [fileName] = fields.fileName
                const [chunkName] = fields.chunkName
                const chunkDir = path.resolve(UPLOAD_DIR, `${fileName}-chunks`)//在qiepian文件夹创建一个新的文件夹,存放接收到的所有切片
                if (!fse.existsSync(chunkDir)) { //文件夹不存在,新建该文件夹
                    await fse.mkdirs(chunkDir)
                }
                // 把切片移动进chunkDir
                await fse.move(file.path, `${chunkDir}/${chunkName}`)
                res.end(JSON.stringify({ //向前端输出
                    code: 0,
                    message: '切片上传成功'
                }))
            })
        } catch (error) {
            console.log(error)
        }
    }

    if (req.url === '/merge') { // 该去合并切片了
        const data = await resolvePost(req)
        const {
            fileName,
            size
        } = data
        const filePath = path.resolve(UPLOAD_DIR, fileName)//获取切片路径
        const chunkDir = path.resolve(UPLOAD_DIR, `${fileName}-chunks`) 
        await mergeFileChunk(filePath, fileName, size)
        if (fse.existsSync(chunkDir)) {
            fse.removeSync(chunkDir)
        }
        res.end(JSON.stringify({
            code: 0,
            message: 'ok'
        }))
    }

})
server.listen(3001, () => {
    console.log('服务已启动');
})
// 合并
async function mergeFileChunk(filePath, fileName, size) {
    const chunkDir = path.resolve(UPLOAD_DIR, `${fileName}-chunks`)
    let chunkPaths = await fse.readdir(chunkDir)
    chunkPaths.sort((a, b) => a.split('-')[1] - b.split('-')[1])
    const arr = chunkPaths.map((chunkPath, index) => {
        return pipeStream(
            path.resolve(chunkDir, chunkPath),
            // 在指定的位置创建可写流
            fse.createWriteStream(filePath, {
                start: index * size,
                end: (index + 1) * size
            })
        )
    })
    await Promise.all(arr)//保证所有的切片都被读取
}
// 将切片转换成流进行合并
function pipeStream(path, writeStream) {
    return new Promise(resolve => {
        // 创建可读流,读取所有切片
        const readStream = fse.createReadStream(path)
        readStream.on('end', () => {
            fse.unlinkSync(path)// 读取完毕后,删除已经读取过的切片路径
            resolve()
        })
        readStream.pipe(writeStream)//将可读流流入可写流
    })
}
// 解析POST请求传递的参数
function resolvePost(req) {
    // 解析参数
    return new Promise(resolve => {
        let chunk = ''
        req.on('data', data => { //req接收到了前端的数据
            chunk += data //将接收到的所有参数进行拼接
        })
        req.on('end', () => {
            resolve(JSON.parse(chunk))//将字符串转为JSON对象
        })
    })
}
回复

使用道具 举报

主题

0

回帖

283

积分

自成一派

 楼主| NSFW 2023-8-14 08:37:21 | 显示全部楼层
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼
温馨提示

关于 注册码 问题

      由于近期经常大量注册机器人注册发送大量广告,本站开启免费入群领取注册码注册网站账号,注册码在群公告上贴着...

关于 注册码 问题

      由于近期经常大量注册机器人注册发送大量广告,本站开启免费入群领取注册码注册网站账号,注册码在群公告上贴着...

Archiver|手机版|小黑屋|DLSite

GMT+8, 2024-11-23 00:31

Powered by Discuz! X3.5 and PHP8

快速回复 返回顶部 返回列表