nodejs接收前端传入的文件

比如前端有一个文件上传的功能

<div>
    <input type="file">
    <button>开始上传</button>
</div>
<script>
    const btn = document.querySelector('button');
    btn.onclick = async()=>{
        const fileInput = document.querySelector('input');
        
        // 拼接参数
        const formData = new FormData();
        formData.append('file', fileInput.files[0]);
        
        // 开始上传
        const res = await fetch('/upload',{method: 'POST', body: formData}).then(res=>res.json());
        console.log(res);
    }
</script>

如果后端是nodejs原生写的一个服务,改如何处理接收呢?

简单处理

我们可以这样做

import { createServer } from "http";
import path from 'path';
import { __dirname } from './utils/index.js'
import fs from 'fs'

const httpServer = createServer((req, res) => {
    const { url } = req;
    if (url === '/upload') {
        console.log('开始上传');

        let body = '';
        req.on("data", (chunk) => {
            console.log("上传中");
            body += chunk;
        })

        req.on("end", () => {
            const recFile = path.join(__dirname, '../a.png');
            fs.writeFile(recFile, body, 'binary', (err) => {
                console.log("上传完成");
                if (!err) {
                    res.end("sucess")
                }
            })
        })
    }

}); // 创建一个http服务
httpServer.listen(3000, () => console.log('服务已经启动: http://127.0.0.1:3000')); // 启动http服务

那一个图片传入举例,我们会发现图片确实被存储到项目中。
但是紧接着我们就会发现,图片打不开!

强制使用记事本或者开发工具以编码形式打开看看 会发现内部文件类似于这样的

------WebKitFormBoundarye8NwBtLb3tKGD502
Content-Disposition: form-data; name="file"; filename="jd.jpg"
Content-Type: image/jpeg

ÿØÿàJFIFÿÛCÿÛCÿM"ÿÄ     
ÿÄ.     "#$%!&'45ÿÿÄ>!1"A2Q#aq
����
ÿØÿàJFIFÿÛCÿÛCÿM"ÿÄ     
ÿÄ.     "#$%!&'45ÿÿÄ>!1"A2Q#aq
------WebKitFormBoundarye8NwBtLb3tKGD502--

我们来解析一下这里边的结构

其中数据边界描述和文件的其他信息 在请求头里 浏览器也帮我们加上了这些属性。和传入后端的body内部保持一致

boundary即为数据边界描述的意思,文件上传时内容是分段传输的,每一boundary表示一个fild(form表单控值)边界,用于可能存在的分段上传。

分析原因

上一步我们已经分析了接收到的数据,
通过分析body我们得知:body数据多了一些和文件二进制本身无关的其他信息。
那么处理起来就很简单了,我们只需要把这些无用信息去除即可。

最后再将蓝色部分的纯文件二进制写入到文件即可。

处理办法

if (url === '/upload') {
    req.setEncoding('binary'); // 将传入的文件设为二进制编码(若本身为二进制,此步可省)

    // 边界字符串
    var boundary = req.headers['content-type'].split('; ')[1].replace('boundary=', '');

    let body = '';  // 请求体(前端post传入的所有参数内容)
    req.on('data', function (chunk) { body += chunk; });

    let fileName = ''; // 文件名
    req.on('end', function () {
        // 处理前端传入的二进制数据,格式化成json
        const file = querystring.parse(body, '\r\n', ':');

        //通过Content-Disposition来获取文件相关信息
        const fileCtd = file['Content-Disposition'].split('; ');
        for (const value in fileCtd) {
            if (fileCtd[value].indexOf("filename=") != -1) {
                fileName = fileCtd[value].substring(10, fileCtd[value].length - 1);
                if (fileName.indexOf('\\') != -1) {
                    fileName = fileName.substring(fileName.lastIndexOf('\\') + 1);
                }
            }
        }

        // 获取文件类型
        const fileType = file['Content-Type'];

        var entireData = body.toString();

        // var contentTypeRegex = /Content-Type: image\/.*/;
        const contentType = file['Content-Type'].substring(1);

        // --===提取只包含文件本身二进制数据,去除前后的其它内容 start===--
        // 1. 移除前置多余字符:根据contentType字段来定位
        var upperBoundary = entireData.indexOf(contentType) + contentType.length;
        var shorterData = entireData.substring(upperBoundary);
        console.log(upperBoundary);
        console.log(shorterData);
        // 2. 替换开始位置的空格
        var binaryDataAlmost = shorterData.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
        // 3. 移除后置多余字符:根据 "--"+ boundary + "--" 来定位
        var binaryData = binaryDataAlmost.substring(0, binaryDataAlmost.indexOf('--' + boundary + '--'));
        // --===提取只包含文件本身二进制数据,去除前后的其它内容 end===--

        // 保存文件
        fs.writeFile(fileName, binaryData, 'binary', (err) => {
            res.end('图片上传完成');
        });
    });
}

参考这篇文章:我的另一篇文章

posted @ 2024-05-15 18:28  丁少华  阅读(61)  评论(0编辑  收藏  举报