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

如何仅用Node.js实现图片上传?(不依赖Express及其他npm包)

纯Node.js实现图片上传(无第三方依赖)

修复Base64上传后图片无法打开的问题

你遇到的PNG无法打开的问题,90%的概率是没有剥离Base64字符串的前缀(即data:image/png;base64,)。直接将包含前缀的字符串转Buffer写入文件,会导致文件开头不是PNG标准的文件头,因此图片查看器无法识别。

修复后的服务端代码

const http = require('http');
const fs = require('fs');
const path = require('path');

const server = http.createServer((req, res) => {
  if (req.method === 'POST' && req.url === '/upload-base64') {
    let body = '';
    // 接收请求体
    req.on('data', chunk => body += chunk.toString());

    req.on('end', () => {
      try {
        // 解析前端发送的JSON数据
        const { base64Data } = JSON.parse(body);
        // 剥离Base64前缀,仅保留编码后的内容
        const cleanBase64 = base64Data.replace(/^data:image\/png;base64,/, '');
        // 将Base64字符串转为Buffer(指定编码为base64)
        const imgBuffer = Buffer.from(cleanBase64, 'base64');
        // 写入文件
        const savePath = path.join(__dirname, 'uploaded.png');
        fs.writeFile(savePath, imgBuffer, err => {
          if (err) {
            res.writeHead(500, { 'Content-Type': 'application/json' });
            return res.end(JSON.stringify({ error: '文件写入失败' }));
          }
          res.writeHead(200, { 'Content-Type': 'application/json' });
          res.end(JSON.stringify({ message: '图片上传成功' }));
        });
      } catch (err) {
        res.writeHead(400, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ error: '请求格式错误,请发送包含base64Data字段的JSON' }));
      }
    });
  } else {
    res.writeHead(404, { 'Content-Type': 'text/plain' });
    res.end('Not Found');
  }
});

server.listen(3000, () => console.log('服务运行在 http://localhost:3000'));

前端配套代码(发送Base64)

// 监听文件选择事件
document.querySelector('input[type="file"]').addEventListener('change', e => {
  const file = e.target.files[0];
  const reader = new FileReader();
  
  reader.onload = event => {
    fetch('http://localhost:3000/upload-base64', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ base64Data: event.target.result })
    })
    .then(res => res.json())
    .then(console.log)
    .catch(console.error);
  };
  
  reader.readAsDataURL(file);
});

更高效的Multipart表单上传(推荐)

Base64会让文件体积增大30%左右,更标准的方式是直接处理multipart/form-data格式的表单上传,无需编码转换:

服务端代码

const http = require('http');
const fs = require('fs');
const path = require('path');

const server = http.createServer((req, res) => {
  if (req.method === 'POST' && req.url === '/upload') {
    // 从请求头获取分隔符(boundary)
    const contentType = req.headers['content-type'];
    const boundary = contentType.split('; ')[1].split('=')[1];
    const endBoundary = `--${boundary}--\r\n`;
    
    let fileBuffer = Buffer.from('');
    let isFileContent = false;

    req.on('data', chunk => {
      if (!isFileContent) {
        // 匹配文件名,定位文件内容起始位置
        const fileNameMatch = chunk.toString().match(/filename="(.+?)"/);
        if (fileNameMatch) {
          const headerEndIndex = chunk.indexOf('\r\n\r\n');
          if (headerEndIndex !== -1) {
            isFileContent = true;
            // 截取文件内容部分(跳过头部)
            fileBuffer = Buffer.concat([fileBuffer, chunk.slice(headerEndIndex + 4)]);
          }
        }
      } else {
        // 检查是否到达上传结束标记
        const endIndex = chunk.indexOf(endBoundary);
        if (endIndex !== -1) {
          // 截取到结束标记前的有效内容(去除末尾的换行)
          fileBuffer = Buffer.concat([fileBuffer, chunk.slice(0, endIndex - 2)]);
          // 写入文件
          const savePath = path.join(__dirname, 'uploaded-form.png');
          fs.writeFile(savePath, fileBuffer, err => {
            if (err) {
              res.writeHead(500, { 'Content-Type': 'application/json' });
              return res.end(JSON.stringify({ error: '文件写入失败' }));
            }
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ message: '图片上传成功' }));
          });
        } else {
          // 继续拼接文件内容
          fileBuffer = Buffer.concat([fileBuffer, chunk]);
        }
      }
    });

    req.on('error', () => {
      res.writeHead(500, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ error: '请求处理失败' }));
    });
  } else {
    // 返回一个简单的上传表单页面
    res.writeHead(200, { 'Content-Type': 'text/html' });
    res.end(`
      <form action="/upload" method="post" enctype="multipart/form-data">
        <input type="file" name="image" accept="image/png">
        <button type="submit">上传PNG图片</button>
      </form>
    `);
  }
});

server.listen(3000, () => console.log('服务运行在 http://localhost:3000'));

注意事项

  • Linux系统下要确保Node进程有目标目录的写入权限,可通过chmod或chown调整目录权限。
  • 上述Multipart代码仅支持单文件上传,如需处理多文件或其他表单字段,需扩展边界分割逻辑。
  • 验证PNG文件是否有效:可通过hexdump -n 8 uploaded.png查看文件头,正常PNG的前8字节应为89 50 4E 47 0D 0A 1A 0A。

内容的提问来源于stack exchange,提问作者Harshit Goyal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 11:43:28