如何实现点击按钮下载Sharp处理后Buffer中的图片
实现Buffer图片前端下载功能方案
问题背景
项目中已通过Multer的multer.memoryStorage()将用户上传的图片读取到Buffer,使用Sharp完成图片格式转换后,通过.toBuffer()得到了处理完成的图片Buffer,现有上传处理逻辑调试正常,需要实现点击页面按钮触发处理后图片下载的效果。
现有代码
EJS页面代码
<form method="post" action="upload" enctype="multipart/form-data"> <input name="image" type="file"> <div> <label for="format">Select format</label> <select id="format" name="format"> <option value = "png">PNG</option> <option value = "jpeg">JPEG</option> </select> <button id="submit" type="submit">Submit</button> <button id="download">Download</button> </div> </form>
服务端server.js代码
const storage = multer.memoryStorage(); const upload = multer({storage: storage}); app.post("/upload", upload.single('image'), (req, res) => { (async ()=> { try { const {data, info} = await sharp(req.file.buffer) .withMetadata() .toFormat(req.body.format) .toBuffer({resolveWithObject: true}); console.log(data, info); } catch(error) { console.log(error); } })() });
注意:当前代码中Sharp处理生成的图片Buffer仅在
/upload接口的请求生命周期内存在,请求返回后Buffer会被垃圾回收,无法跨请求留存,不能直接供后续的下载按钮调用。
实现方案
方案1:最简实现(改动量最小,无需额外依赖/前端JS)
直接在上传处理完成后返回带下载头的响应,浏览器收到响应后会自动弹出文件保存框,不需要额外写前端交互逻辑。
- 修改服务端
/upload接口逻辑,替换原来的console.log部分,设置下载响应头后返回处理好的图片Buffer:
const storage = multer.memoryStorage(); const upload = multer({storage: storage}); app.post("/upload", upload.single('image'), (req, res) => { (async ()=> { try { const {data, info} = await sharp(req.file.buffer) .withMetadata() .toFormat(req.body.format) .toBuffer({resolveWithObject: true}); // 设置下载响应头 res.set({ 'Content-Type': `image/${req.body.format}`, 'Content-Disposition': `attachment; filename="processed-image.${req.body.format}"`, 'Content-Length': info.size }); // 返回图片Buffer触发下载 res.send(data); } catch(error) { console.log(error); res.status(500).send('图片处理失败'); } })() });
- 原有EJS代码不需要额外修改,表单内按钮默认type就是submit,点击按钮提交表单后,服务端处理完成会自动触发下载。
方案2:预览后下载(体验更好,无重复上传)
如果需要先提交处理、展示预览图,再点击下载按钮触发下载(不需要重复上传文件),可以通过前端JS缓存处理后的图片数据,实现无刷新交互。
- 修改服务端接口,处理完成后返回图片Base64数据给前端,而不是直接返回文件流:
const storage = multer.memoryStorage(); const upload = multer({storage: storage}); app.post("/upload", upload.single('image'), (req, res) => { (async ()=> { try { const {data, info} = await sharp(req.file.buffer) .withMetadata() .toFormat(req.body.format) .toBuffer({resolveWithObject: true}); // 转Base64返回前端 const base64Img = data.toString('base64'); res.json({ code: 0, imgData: `data:image/${req.body.format};base64,${base64Img}`, format: req.body.format }); } catch(error) { console.log(error); res.status(500).json({code: -1, msg: '图片处理失败'}); } })() });
- 修改EJS代码,增加前端交互逻辑,拦截表单提交、缓存处理后的图片、实现下载触发:
<form id="uploadForm" method="post" action="upload" enctype="multipart/form-data"> <input name="image" type="file"> <div> <label for="format">Select format</label> <select id="format" name="format"> <option value = "png">PNG</option> <option value = "jpeg">JPEG</option> </select> <button id="submit" type="submit">Submit</button> <button id="download" type="button" disabled>Download</button> </div> <!-- 预览区域 --> <div id="preview" style="margin-top: 16px;"></div> </form> <script> let cachedImg = null; let cachedFormat = null; const form = document.getElementById('uploadForm'); const downloadBtn = document.getElementById('download'); const previewArea = document.getElementById('preview'); // 拦截表单提交 form.addEventListener('submit', async (e) => { e.preventDefault(); const formData = new FormData(form); const resp = await fetch('/upload', {method: 'POST', body: formData}); const result = await resp.json(); if (result.code === 0) { cachedImg = result.imgData; cachedFormat = result.format; // 展示预览 previewArea.innerHTML = `<img src="${cachedImg}" style="max-width: 400px" />`; // 启用下载按钮 downloadBtn.disabled = false; } else { alert(result.msg); } }); // 绑定下载事件 downloadBtn.addEventListener('click', () => { if (!cachedImg) return; const link = document.createElement('a'); link.href = cachedImg; link.download = `processed-image.${cachedFormat}`; document.body.appendChild(link); link.click(); document.body.removeChild(link); }); </script>
该方案下,用户点击Submit后页面无刷新,处理完成直接展示预览图,Download按钮解锁后点击即可直接下载,不需要再次向服务端发起请求。
内容的提问来源于stack exchange,提问作者foobar
相关产品推荐
相关产品推荐

