如何仅用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
相关产品推荐
相关产品推荐

