node 文件上传

后端

router.js

const express = require('express');
const ceshi = express.Router() //创建路由
const multer = require('multer');

// 不要将 这个作为全局中间件 而是只在需要的路由上使用
// 单名称单文件上传   single     
// 单名城多文件    array
// 多名称多文件       fields   
// 注意事项 这儿的 file 必须和前端 的 input name保持一直
ceshi.use("/", multer({ dest: '/tmp/' }).array('file'));

// ceshi.use("/", multer({ dest: '/tmp/' }).fields([{ name: "file1" }, { name: "file2" }]));

// 文件上传
ceshi.post("/uploadImg", require("./uploadImg"))

module.exports = ceshi

ceshi.js

const path = require("path")
const { uploadImg, uploadBogFile } = require("../../../tool/upload")

module.exports = async (req, res, next) => {
    let file = req.files[0]
    let filename = file.originalname
    let path1 = path.resolve(__dirname, "../../../public/ceshi/" + filename)
    let data = await uploadImg(file.path, path1)
    if (data.code !== 200) {
        next(new Error(data.msg))
    }
    res.send(data.msg)
}

upload.js

const fs = require("fs")

// 简单的图片上传
const uploadImg = (path0, path1) => {
    return new Promise((resolve, reject) => {
        fs.readFile(path0, (err, data1) => {
            if (err) {
                return reject({
                    code: 500,
                    msg: err
                })
            }
            fs.writeFile(path1, data1, (err, data) => {
                if (!err) {
                    return resolve({
                        code: 200,
                        msg: "上传成功"
                    })
                }
                return reject({
                    code: 500,
                    msg: err
                })
            })
        })
    })
};

// 大文件的上传
const uploadBogFile = (file, path1) => {
    return new Promise((res, rej) => {
        let rl = fs.createReadStream(file)  //读
        let wl = fs.createWriteStream(path1) //写,没有目录会自动生成
        rl.on("close", function () {
            return res("文件上传成功")
        })

        rl.pipe(wl)
    })

}

module.exports = {
    uploadImg,
    uploadBogFile
}

前端

直接使用 from表单提交

  <form action="http://127.0.0.1:5000/ceshi/uploadImg" method="post" enctype="multipart/form-data">
            <input name="file" type="file">
            <input name="file1" type="file">
            <button type="submit">submit</button>
        </form>

使用Ajax 模拟表单提交

链接

大文件分片上传

前端

<html>

<head>
    <meta charset="UTF-8">
    <!-- import CSS -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>

<body>
    <div id="app">
        <!-- 上传组件 -->
        <el-upload action drag :auto-upload="false" :show-file-list="false" :on-change="handleChange">
            <i class="el-icon-upload"></i>
            <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
            <div class="el-upload__tip" slot="tip">大小不超过 200M 的视频</div>
        </el-upload>

        <!-- 进度显示 -->
        <div class="progress-box">
            <span>上传进度:{{ percent.toFixed() }}%</span>
            <el-button type="primary" size="mini" @click="handleClickBtn">{{ upload | btnTextFilter}}</el-button>
        </div>

        <!-- 展示上传成功的视频 -->
        <div v-if="videoUrl">
            <video :src="videoUrl" controls />
        </div>
    </div>
</body>
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/spark-md5/3.0.0/spark-md5.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script>
    new Vue({
        el: '#app',
        filters: {
            btnTextFilter(val) {
                return val ? '暂停' : '继续'
            }
        },
        data() {
            return {
                percent: 0,
                videoUrl: '',
                upload: true,
                percentCount: 0
            }
        },
        methods: {
            async handleChange(file) {
                if (!file) return
                this.percent = 0
                this.videoUrl = ''
                // 获取文件并转成 ArrayBuffer 对象
                const fileObj = file.raw
                let buffer
                try {
                    buffer = await this.fileToBuffer(fileObj)
                } catch (e) {
                    console.log(e)
                }

                // 将文件按固定大小(2M)进行切片,注意此处同时声明了多个常量
                const chunkSize = 2097152,
                    chunkList = [], // 保存所有切片的数组
                    chunkListLength = Math.ceil(fileObj.size / chunkSize), // 计算总共多个切片
                    suffix = /\.([0-9A-z]+)$/.exec(fileObj.name)[1] // 文件后缀名

                // 根据文件内容生成 hash 值
                const spark = new SparkMD5.ArrayBuffer()
                spark.append(buffer)
                const hash = spark.end()

                // 生成切片,这里后端要求传递的参数为字节数据块(chunk)和每个数据块的文件名(fileName)
                let curChunk = 0 // 切片时的初始位置
                for (let i = 0; i < chunkListLength; i++) {
                    console.log(hash, suffix);
                    const item = {
                        chunk: fileObj.slice(curChunk, curChunk + chunkSize),
                        fileName: `${hash}_${i}.${suffix}`, // 文件名规则按照 hash_1.jpg 命名
                    }
                    curChunk += chunkSize
                    chunkList.push(item)
                }
                this.chunkList = chunkList // sendRequest 要用到
                this.hash = hash // sendRequest 要用到
                this.sendRequest()
            },

            // 发送请求
            sendRequest() {
                const requestList = [] // 请求集合
                this.chunkList.forEach((item, index) => {
                    const fn = () => {
                        const formData = new FormData()
                        formData.append('file', item.chunk)
                        formData.append('filename', item.fileName)
                        return axios({
                            url: 'http://127.0.0.1:5000/ceshi/uploadImg',
                            method: 'post',
                            headers: {
                                'Content-Type': 'multipart/form-data'
                            },
                            data: formData
                        }).then(res => {
                            if (res.data.code === 0) { // 成功
                                if (this.percentCount ===
                                    0) { // 避免上传成功后会删除切片改变 chunkList 的长度影响到 percentCount 的值
                                    this.percentCount = 100 / this.chunkList.length
                                }
                                this.percent += this.percentCount // 改变进度
                                this.chunkList.splice(index, 1) // 一旦上传成功就删除这一个 chunk,方便断点续传
                            }
                        })
                    }
                    requestList.push(fn)
                })

                let i = 0 // 记录发送的请求个数
                // 文件切片全部发送完毕后,需要请求 '/merge' 接口,把文件的 hash 传递给服务器
                const complete = () => {
                    axios({
                        url: '/merge',
                        method: 'get',
                        params: {
                            hash: this.hash
                        }
                    }).then(res => {
                        if (res.data.code === 0) { // 请求发送成功
                            this.videoUrl = res.data.path
                        }
                    })
                }
                const send = async () => {
                    if (!this.upload) return
                    if (i >= requestList.length) {
                        // 发送完毕
                        complete()
                        return
                    }
                    await requestList[i]()
                    i++
                    send()
                }
                send() // 发送请求
            },

            // 按下暂停按钮
            handleClickBtn() {
                this.upload = !this.upload
                // 如果不暂停则继续上传
                if (this.upload) this.sendRequest()
            },

            // 将 File 对象转为 ArrayBuffer 
            fileToBuffer(file) {
                return new Promise((resolve, reject) => {
                    const fr = new FileReader()
                    fr.onload = e => {
                        resolve(e.target.result)
                    }
                    fr.readAsArrayBuffer(file)
                    fr.onerror = () => {
                        reject(new Error('转换文件格式发生错误'))
                    }
                })
            }
        }
    })
</script>

</html>

express

  • 文件名要在body中获取
  • 小图片数据的获取没啥改变,大文件不详
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值