JIMP生成图片截断 并发请求返回残缺旧图报ERR_CONTENT_LENGTH_MISMATCH
问题根因
- 所有请求生成图片时复用固定输出文件名:休闲模板统一写入
aCasualInvite.png,正式模板统一写入aFancyInvite.png,并发场景下多个请求会同时读写同一份物理文件。 - 当请求A正在往目标路径写图片内容、还没写完时,请求B可能已经开始读取这个半完成的文件返回给前端,或是直接覆盖请求A正在写入的文件,直接导致返回旧图片、残缺图片的问题。
- 你碰到的
Failed to load resource: net::ERR_CONTENT_LENGTH_MISMATCH报错也和这个问题直接相关:Express的sendFile会先读取文件元信息设置Content-Length响应头,如果读取文件过程中文件被其他请求改写、或是读到了只写入一半的文件,实际返回的内容长度和响应头标注的长度不匹配,就会触发这个浏览器报错。 - 你代码里JIMP相关的
await调用写法本身没有问题,和JIMP异步渲染逻辑无关,问题完全来自固定文件名导致的并发文件读写冲突。
修复方案
- 方案1(最小改动):给每个请求生成唯一的临时文件名,避免多请求操作同一路径文件。可以用时间戳+随机字符串生成唯一标识,文件发送完成后定时清理避免占用磁盘,核心修改示例:
const fs = require('fs/promises'); // makeImg函数内生成输出路径部分替换原有逻辑 const uniqueSuffix = `${Date.now()}_${Math.random().toString(36).slice(2,8)}`; const outputFileName = `invite_${uniqueSuffix}_${filename}.png`; const outputFullPath = __dirname + '/' + outputFileName; await image.writeAsync(outputFullPath); // 1分钟后自动删除临时文件,避免磁盘空间被占满 setTimeout(() => fs.unlink(outputFullPath).catch(()=>{}), 60000); return outputFileName;
- 方案2(推荐,彻底规避文件冲突):跳过本地磁盘写入逻辑,JIMP处理完图片后直接输出Buffer返回给前端、传给NodeMailer做附件,完全消除文件IO带来的并发问题,也不需要额外清理临时文件。核心修改示例:
// 路由部分修改 app.post('/invite/:email/:name', async (req,res, next) => { try { const email = atob(req.params.email); const name = atob(req.params.name); const data = req.body; // 直接拿到图片二进制Buffer const inviteImageBuffer = await makeImg(name, data); res.type('png'); res.send(inviteImageBuffer); // 发邮件时直接把这个Buffer传给nodemailer的attachments字段即可,不需要写本地文件 } catch (error) { return next(error); } }); // makeImg函数末尾替换原有writeAsync逻辑 // ...前面的模板读取、文字打印逻辑保持不变 // 直接返回PNG格式的Buffer,不写本地文件 return image.getBufferAsync(Jimp.MIME_PNG);
内容的提问来源于stack exchange,提问作者Loom
相关产品推荐
相关产品推荐

