JavaScript & Node 分片上传图片保存至服务器后无法预览
问题排查结论
首先直接回答核心疑问:将buffer写入文件前不需要裁剪剔除任何内容,保存的图片无法预览是前后端分片上传逻辑存在多处错误导致的,和原始文件buffer的内容无关。
具体错误点如下:
- 后端写入逻辑错误:每次收到分片都调用
fs.writeFileSync,该方法默认是覆盖写入模式,最终服务器上的文件只会保留最后一个收到的分片内容,不可能是完整图片。 - 写入编码配置错误:调用写入方法时传入了
'base64'编码参数,但前端发送的是原始二进制ArrayBuffer数据,并非base64编码格式,编码不匹配会直接导致写入的二进制内容被转码损坏。 - 中间件冲突:全局挂载的
express.json()中间件会尝试把所有请求体解析为JSON格式,会破坏发送的原始二进制流数据。 - 前端分片逻辑有缺陷:分片数量计算没有做向上取整,循环条件多写了
+1,会额外生成空分片发送;且请求头没有携带分片序号,就算改成追加写入,一旦网络请求乱序到达,拼接出来的文件内容顺序也是错的。 - 端口配置不匹配:前端请求写的是8000端口,后端默认监听5000端口,联调时需要统一配置。
修复方案
前端代码调整
- 修正分片计算逻辑,用
Math.ceil取整计算总分片数,去掉循环条件多余的+1,跳过空分片 - 请求头新增分片序号、总分片数字段,供后端校验拼接顺序
调整后的核心代码:
const getImageInput = document.getElementById('imageInput'); getImageInput.addEventListener('change',(event)=>{ uploadImage(event.target.files) }) function uploadImage(files){ Array.from(files).forEach((file)=>{ imageUploadApi(file) }) } async function imageUploadApi(file){ const reader = new FileReader() reader.readAsArrayBuffer(file) reader.addEventListener('load',async(event)=>{ const CHUNK_SIZE = 1000 const totalLength = event.target.result.byteLength // 向上取整计算总分片数 const chunkCount = Math.ceil(totalLength / CHUNK_SIZE) const fileName = file.name for(let i = 0; i < chunkCount; i++){ const start = i * CHUNK_SIZE const end = Math.min(start + CHUNK_SIZE, totalLength) const chunk = event.target.result.slice(start, end) // 空分片直接跳过 if(chunk.byteLength === 0) continue const response = await fetch('http://localhost:8000/image-upload',{ method:'POST', body: chunk, headers:{ 'Content-Type':'application/octet-stream', 'Content-Length':chunk.length, 'file-name': fileName, 'chunk-index': i, // 新增当前分片序号 'total-chunks': chunkCount // 新增总分片数 } }) console.log(await response.json()) } }) }
后端代码调整
- 调整中间件匹配规则,避免
express.json()处理二进制上传请求,用express.raw()专门解析octet-stream类型的请求体,配置合适的请求体大小上限 - 写入文件改用对应模式:第一个分片写入时清空创建新文件,后续分片用追加模式写入,去掉错误的base64编码参数
- 补全缺失的
path模块引用,统一监听端口和前端请求端口一致
调整后的核心代码:
const express = require('express'); const app = express(); const cors = require('cors'); const fs = require('fs'); const path = require('path'); app.use(cors()); // 仅对json类型请求使用json解析器 app.use(express.json({ type: ['application/json'] })); // 单独给二进制上传接口配置raw解析器 app.post('/image-upload', express.raw({type: 'application/octet-stream', limit: '100mb'}), (req,res)=>{ const fileName = req.headers['file-name'] const chunkIndex = Number(req.headers['chunk-index']) const savePath = path.join(__dirname, './upload_pictures/', fileName) // 第一个分片先清空创建文件,后续分片追加写入 if(chunkIndex === 0){ fs.writeFileSync(savePath, req.body) }else{ fs.appendFileSync(savePath, req.body) } res.status(200).json({ message:'Chunk Uploaded Successfully' }) }) if(process.env.NODE_ENV === 'production'){ app.use(express.static('client/build')); app.get('*', (req, res) =>{ res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html')) }); } const port = process.env.PORT || 8000; app.listen(port, () => console.log(`Server started on port ${port}`));
注意:运行代码前先确认
upload_pictures文件夹已经创建,否则写入会报路径不存在的错误。
内容的提问来源于stack exchange,提问作者night_programmer
相关产品推荐
相关产品推荐

