• 大文件分片上传


    大文件分片上传需要前后端一起配合的,后端部分用node展示

    背景:

    在开发中往往有的时候会存在大文件的情况下,可能有几百M;要是直接传,可能接口因时间太长,直接失败了,这个时候就需要大文件分片上传了,接下来就通过一个小demo,给大家演示一下吧!!!

    前端:

    1.上传文件会获取一个blob对象,利用slice方法进行切片上传

            async handleUpload() {
                const fileObj = this.fileObj
                if (!fileObj.file) return
                const { shouldUpload } = await this.verifyUpload(fileObj.file.name)
                if (!shouldUpload) {
                    alert('秒传:上传成功')
                    return
                }
                const chunkList = this.createChunk(fileObj.file)
                console.log(chunkList) // 看看chunkList长什么样子
                this.fileObj.chunkList = chunkList.map(({ file }, index) => ({
                    file,
                    size: file.size,
                    percent: 0,
                    chunkName: `${fileObj.file.name}-${index}`,
                    fileName: fileObj.file.name,
                    index,
                }))
                console.log(this.fileObj)
                this.uploadChunks() // 执行上传切片的操作
            },
            createChunk(file, size = 2 * 1024 * 1024) {
                const chunkList = []
                let cur = 0
                while (cur < file.size) {
                    // 使用slice方法切片
                    chunkList.push({ file: file.slice(cur, cur + size) })
                    cur += size
                }
                return chunkList
            },

    这个切片前会有一个判断文件是否存在的接口,这个后续再说

    2.上传切片已经展示进度条

    通过axiosd的进度条事件onUploadProgress获取上传文件的进度,显示进度信息

    通过async,await把uploadChunks变成同步顺序 循环上传所有切片

            import axios from 'axios'
            axiosRequest({
                url,
                method = 'post',
                data,
                headers = {},
                onUploadProgress = (e) => e, // 进度回调
            }) {
                return new Promise((resolve, reject) => {
                    axios[method](url, data, {
                        headers,
                        onUploadProgress, // 传入监听进度回调
                    })
                        .then((res) => {
                            resolve(res)
                        })
                        .catch((err) => {
                            reject(err)
                        })
                })
            },
            async uploadChunks() {
                const requestList = this.fileObj.chunkList
                    .map(({ file, fileName, index, chunkName }) => {
                        const formData = new FormData()
                        formData.append('file', file)
                        formData.append('fileName', fileName)
                        formData.append('chunkName', chunkName)
                        console.log({ formData, index })
                        return { formData, index }
                    })
                    .map(({ formData, index }) =>
                        this.axiosRequest({
                            url: 'http://localhost:3000/upload',
                            data: formData,
                            onUploadProgress: this.createProgressHandler(
                                this.fileObj.chunkList[index]
                            ), // 传入监听上传进度回调
                        })
                    )
                const result = await Promise.all(requestList) // 使用Promise.all进行请求
                console.log(result)
                this.mergeChunks()
            },
            createProgressHandler(item) {
                return (e) => {
                    // 设置每一个切片的进度百分比
                    item.percent = parseInt(String((e.loaded / e.total) * 100))
                }
            },

    3.由第二步的promise.al结果后通知后端上传完毕,合成切片

            mergeChunks(size = 2 * 1024 * 1024) {
                this.axiosRequest({
                    url: 'http://localhost:3000/merge',
                    headers: {
                        'content-type': 'application/json',
                    },
                    data: JSON.stringify({
                        size,
                        fileName: this.fileObj.file.name,
                    }),
                })
            },

    4.秒传功能,实际就是后端通过检索有无该文件,在第一步中开头执行

            async verifyUpload(fileName) {
                const { data } = await this.axiosRequest({
                    url: 'http://localhost:3000/verify',
                    headers: {
                        'content-type': 'application/json',
                    },
                    data: JSON.stringify({
                        fileName,
                    }),
                })
                return data
            },

    前端完整代码:

    <template>
        <div>
            <input type="file" @change="handleFileChange" />
            <el-button @click="handleUpload"> 上传 </el-button>
            <div style=" 300px">
                总进度:
                <el-progress :percentage="totalPercent"></el-progress>
                切片进度:
                <div v-for="item in fileObj.chunkList" :key="item">
                    <span>{{ item.chunkName }}:</span>
                    <el-progress :percentage="item.percent"></el-progress>
                </div>
            </div>
        </div>
    </template>
    
    <script>
    import axios from 'axios'
    export default {
        name: '',
        data() {
            return {
                fileObj: {
                    file: null,
                    chunkList: [],
                },
            }
        },
        computed: {
            totalPercent() {
                const fileObj = this.fileObj
                if (fileObj.chunkList.length === 0) return 0
                const loaded = fileObj.chunkList
                    .map(({ size, percent }) => size * percent)
                    .reduce((pre, next) => pre + next)
                return parseInt((loaded / fileObj.file.size).toFixed(2))
            },
        },
        methods: {
            axiosRequest({
                url,
                method = 'post',
                data,
                headers = {},
                onUploadProgress = (e) => e, // 进度回调
            }) {
                return new Promise((resolve, reject) => {
                    axios[method](url, data, {
                        headers,
                        onUploadProgress, // 传入监听进度回调
                    })
                        .then((res) => {
                            resolve(res)
                        })
                        .catch((err) => {
                            reject(err)
                        })
                })
            },
            handleFileChange(e) {
                const [file] = e.target.files
                if (!file) return
                this.fileObj.file = file
            },
            async handleUpload() {
                const fileObj = this.fileObj
                if (!fileObj.file) return
                const { shouldUpload } = await this.verifyUpload(fileObj.file.name)
                if (!shouldUpload) {
                    alert('秒传:上传成功')
                    return
                }
                const chunkList = this.createChunk(fileObj.file)
                console.log(chunkList) // 看看chunkList长什么样子
                this.fileObj.chunkList = chunkList.map(({ file }, index) => ({
                    file,
                    size: file.size,
                    percent: 0,
                    chunkName: `${fileObj.file.name}-${index}`,
                    fileName: fileObj.file.name,
                    index,
                }))
                console.log(this.fileObj)
                this.uploadChunks() // 执行上传切片的操作
            },
            createChunk(file, size = 2 * 1024 * 1024) {
                const chunkList = []
                let cur = 0
                while (cur < file.size) {
                    // 使用slice方法切片
                    chunkList.push({ file: file.slice(cur, cur + size) })
                    cur += size
                }
                return chunkList
            },
            async uploadChunks() {
                const requestList = this.fileObj.chunkList
                    .map(({ file, fileName, index, chunkName }) => {
                        const formData = new FormData()
                        formData.append('file', file)
                        formData.append('fileName', fileName)
                        formData.append('chunkName', chunkName)
                        console.log({ formData, index })
                        return { formData, index }
                    })
                    .map(({ formData, index }) =>
                        this.axiosRequest({
                            url: 'http://localhost:3000/upload',
                            data: formData,
                            onUploadProgress: this.createProgressHandler(
                                this.fileObj.chunkList[index]
                            ), // 传入监听上传进度回调
                        })
                    )
                const result = await Promise.all(requestList) // 使用Promise.all进行请求
                console.log(result)
                this.mergeChunks()
            },
            async verifyUpload(fileName) {
                const { data } = await this.axiosRequest({
                    url: 'http://localhost:3000/verify',
                    headers: {
                        'content-type': 'application/json',
                    },
                    data: JSON.stringify({
                        fileName,
                    }),
                })
                return data
            },
            createProgressHandler(item) {
                return (e) => {
                    // 设置每一个切片的进度百分比
                    item.percent = parseInt(String((e.loaded / e.total) * 100))
                }
            },
            mergeChunks(size = 2 * 1024 * 1024) {
                this.axiosRequest({
                    url: 'http://localhost:3000/merge',
                    headers: {
                        'content-type': 'application/json',
                    },
                    data: JSON.stringify({
                        size,
                        fileName: this.fileObj.file.name,
                    }),
                })
            },
        },
    }
    </script>
    
    <style scoped></style>

    后端node实现,就不多做解释

    const http = require('http')
    const path = require('path')
    const fse = require('fs-extra')
    const multiparty = require('multiparty')
    
    const server = http.createServer()
    const UPLOAD_DIR = path.resolve(__dirname, '.', `qiepian`) // 切片存储目录
    
    server.on('request', async (req, res) => {
        res.setHeader('Access-Control-Allow-Origin', '*')
        res.setHeader('Access-Control-Allow-Headers', '*')
        if (req.method === 'OPTIONS') {
            res.status = 200
            res.end()
            return
        }
        console.log(req.url)
    
        if (req.url === '/upload') {
            const multipart = new multiparty.Form()
    
            multipart.parse(req, async (err, fields, files) => {
                if (err) {
                    console.log('errrrr', err)
                    return
                }
                const [file] = files.file
                const [fileName] = fields.fileName
                const [chunkName] = fields.chunkName
                // 保存切片的文件夹的路径,比如  张远-嘉宾.flac-chunks
                const chunkDir = path.resolve(UPLOAD_DIR, `${fileName}-chunks`)
                // // 切片目录不存在,创建切片目录
                if (!fse.existsSync(chunkDir)) {
                    await fse.mkdirs(chunkDir)
                }
                // 把切片移动到切片文件夹
                await fse.move(file.path, `${chunkDir}/${chunkName}`)
                res.end(
                    JSON.stringify({
                        code: 0,
                        message: '切片上传成功',
                    })
                )
            })
        }
        const resolvePost = (req) =>
            new Promise((res) => {
                let chunk = ''
                req.on('data', (data) => {
                    chunk += data
                })
                req.on('end', () => {
                    res(JSON.parse(chunk))
                })
            })
        const pipeStream = (path, writeStream) => {
            console.log('path', path)
            return new Promise((resolve) => {
                const readStream = fse.createReadStream(path)
                readStream.on('end', () => {
                    fse.unlinkSync(path)
                    resolve()
                })
                readStream.pipe(writeStream)
            })
        }
    
        // 合并切片
        const mergeFileChunk = async (filePath, fileName, size) => {
            // filePath:你将切片合并到哪里,的路径
            const chunkDir = path.resolve(UPLOAD_DIR, `${fileName}-chunks`)
            let chunkPaths = null
            // 获取切片文件夹里所有切片,返回一个数组
            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)
            fse.rmdirSync(chunkDir) // 合并后删除保存切片的目录
        }
        if (req.url === '/merge') {
            const data = await resolvePost(req)
            const { fileName, size } = data
            const filePath = path.resolve(UPLOAD_DIR, fileName)
            await mergeFileChunk(filePath, fileName, size)
            res.end(
                JSON.stringify({
                    code: 0,
                    message: '文件合并成功',
                })
            )
        }
    
        if (req.url === '/verify') {
            const data = await resolvePost(req)
            const { fileName } = data
            const filePath = path.resolve(UPLOAD_DIR, fileName)
            console.log(filePath)
            if (fse.existsSync(filePath)) {
                res.end(
                    JSON.stringify({
                        shouldUpload: false,
                    })
                )
            } else {
                res.end(
                    JSON.stringify({
                        shouldUpload: true,
                    })
                )
            }
        }
    })
    
    server.listen(3000, () => console.log('正在监听 3000 端口'))
  • 相关阅读:
    cocos3.2触摸事件接收顺序
    触摸点是否在按钮矩形内
    scrollview里container拖动显示问题
    cocos2dx 显示对象尺寸
    allocating an object of abstract class
    学习scorllview
    cocos2dx引用计数
    addchild 报错不能添加nil
    有用的宏
    一段SQL
  • 原文地址:https://www.cnblogs.com/ssszjh/p/15891717.html
Copyright © 2020-2023  润新知