You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

纯JavaScript无第三方库上传存储图片后文件损坏的原因及解决方案

问题根因

保存的图片无法打开、十六进制内容和原文件不一致,核心原因是服务端未对multipart/form-data格式的请求体做解析,直接将包含表单协议内容的整个请求体写入了图片文件。
multipart/form-data是文件上传的标准表单协议格式,请求体除了实际文件二进制内容外,还固定包含表单分隔符、表单项描述头、结束标记等协议内容,这些内容不属于图片本身,混入文件后会直接破坏图片结构。你当前的服务端代码虽然提取了boundary值但完全没有用于内容切分,相当于把HTTP请求的完整原始body直接存为jpeg文件,文件开头不是JPEG标准的FF D8文件头,自然无法被图片查看器识别。
另外客户端的FileReader读ArrayBuffer再转Blob的逻辑属于无意义冗余:File对象本身就是Blob的子类,可以直接append到FormData中,不需要额外转码,这部分逻辑虽然没有直接造成文件损坏,但可以删除简化。

修复方案

简化客户端代码

去掉多余的FileReader转码逻辑,直接将选中的文件对象传入FormData即可:

function myfunction() {
    const photo = document.getElementById("image-file").files[0];
    if (!photo) return;
    const formData = new FormData();
    formData.append("uploadFile", photo, photo.name);
    fetch('http://127.0.0.1:3000/upload', { method: "POST", body: formData });
}

修正服务端multipart解析逻辑

按照multipart协议规则从请求体中切出纯文件二进制内容再写入磁盘,解析逻辑遵循以下步骤:

  • 从Content-Type头中提取boundary值,注意请求体中实际的分隔符格式为--<boundary值>
  • 定位第一个分隔符的位置,向后查找头信息和文件内容的分隔标记\r\n\r\n,标记之后就是文件内容的起始位置
  • 从文件起始位置向后查找下一个分隔符的位置,两个位置之间的内容去掉末尾的换行符就是纯文件二进制
  • 将切分得到的纯文件Buffer写入磁盘

修复后的服务端代码如下:

const fs = require('fs');

function store_file(req, res) {
    let body = Buffer.from([]);
    req.on('data', (data) => {
        body = Buffer.concat([body, data]);
    });
    req.on('end', () => {
        const contentType = req.headers['content-type'];
        if (!contentType || !contentType.includes('boundary=')) {
            res.writeHead(400, { "Content-Type": "application/json" });
            res.end(JSON.stringify({ msg: "missing boundary in content-type" }));
            return;
        }
        // 提取boundary,拼接成实际分隔符Buffer
        const boundary = contentType.split('boundary=')[1];
        const boundaryBuf = Buffer.from(`--${boundary}`);
        // 定位第一个分隔符位置
        const firstBoundaryPos = body.indexOf(boundaryBuf);
        if (firstBoundaryPos === -1) {
            res.writeHead(400, { "Content-Type": "application/json" });
            res.end(JSON.stringify({ msg: "invalid form data format" }));
            return;
        }
        // 定位表单头和文件内容的分隔标记
        const headerEndMarker = Buffer.from('\r\n\r\n');
        const headerEndPos = body.indexOf(headerEndMarker, firstBoundaryPos);
        if (headerEndPos === -1) {
            res.writeHead(400, { "Content-Type": "application/json" });
            res.end(JSON.stringify({ msg: "invalid form header" }));
            return;
        }
        // 计算文件内容起始位置
        const fileStartPos = headerEndPos + headerEndMarker.length;
        // 定位文件内容后的下一个分隔符,确定文件结束位置
        const nextBoundaryPos = body.indexOf(boundaryBuf, fileStartPos);
        if (nextBoundaryPos === -1) {
            res.writeHead(400, { "Content-Type": "application/json" });
            res.end(JSON.stringify({ msg: "invalid file content" }));
            return;
        }
        // 切出纯文件内容,去掉末尾的\r\n换行
        const fileBuffer = body.subarray(fileStartPos, nextBoundaryPos - 2);
        // 写入文件
        fs.writeFile('test.jpeg', fileBuffer, (err) => {
            if (err) {
                res.writeHead(500, { "Content-Type": "application/json" });
                res.end(JSON.stringify({ msg: "file write failed" }));
                return;
            }
            console.log('The file has been saved!');
            index_file(req, res);
        });
    });
}

修复后重新对比hexdump可以发现,保存的文件和原始文件二进制完全一致,JPEG文件开头为标准的FF D8 FF标记,可以正常打开。

内容的提问来源于stack exchange,提问作者Shaheer Ahmed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 17:27:32