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

Node.js+Express+Sharp图片缩放API问题:无法即时读取新图

问题分析与修复方案

核心问题

你的代码存在两个关键问题:

  1. then调用逻辑错误:你直接把fs.readFile()的执行结果传给了then,但fs.readFile是异步函数,会在resizeImage还没完成缩放时就开始读取文件,导致读到的是旧的缩放结果。
  2. 文件覆盖与浏览器缓存:每次缩放都写入同一个fjord-resized.jpg文件,多请求时会出现冲突,同时浏览器会缓存旧图片,必须刷新或重启才能看到新结果。

修复步骤

1. 修正异步调用逻辑

把fs.readFile包装成函数传给then,确保它在缩放完成后才执行,同时加入参数验证和错误处理:

import express from "express";
import sharp from "sharp";
import fs from "fs"; // 改用ES模块导入,保持TypeScript风格

const img = express.Router();

const resizeImage = async function (width: number, height: number) {
  try {
    await sharp("./imgs/fjord.jpg")
      .resize({ width, height })
      .toFile("./imgs/thumbs/fjord-resized.jpg");
  } catch (err) {
    console.error(err);
    throw err; // 抛出错误让上层处理,避免静默失败
  }
};

img.get("/", (req, res) => {
  const { width, height } = req.query;
  const numWidth = Number(width);
  const numHeight = Number(height);

  // 验证参数有效性
  if (isNaN(numWidth) || isNaN(numHeight) || numWidth <= 0 || numHeight <= 0) {
    return res.status(400).send("width和height必须是正整数");
  }

  resizeImage(numWidth, numHeight)
    .then(() => {
      // 缩放完成后再读取文件
      fs.readFile("./imgs/thumbs/fjord-resized.jpg", (err: NodeJS.ErrnoException | null, data: Buffer) => {
        if (err) {
          console.error(err);
          return res.status(500).send("读取缩放图片失败");
        }
        res.setHeader("Content-Type", "image/jpeg");
        // 禁用缓存,确保每次请求都获取新图片
        res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        res.end(data);
      });
    })
    .catch(err => {
      res.status(500).send("图片缩放失败");
    });
});

export default img;

2. 更高效的方案:直接输出Sharp流到响应

没必要先写入文件再读取,Sharp可以直接把处理后的图片流发给Express响应,既减少IO操作,又避免文件冲突:

import express from "express";
import sharp from "sharp";

const img = express.Router();

img.get("/", (req, res) => {
  const { width, height } = req.query;
  const numWidth = Number(width);
  const numHeight = Number(height);

  // 参数验证
  if (isNaN(numWidth) || isNaN(numHeight) || numWidth <= 0 || numHeight <= 0) {
    return res.status(400).send("width和height必须是正整数");
  }

  // 直接将Sharp处理后的图片流输出到响应
  sharp("./imgs/fjord.jpg")
    .resize({ width: numWidth, height: numHeight })
    .on("error", (err) => {
      console.error(err);
      res.status(500).send("图片处理失败");
    })
    .pipe(res); // 流直接对接响应,无需中间文件
});

export default img;

3. 进阶优化:保留缩放文件并避免覆盖

如果需要保留不同尺寸的缩放图片,可以根据参数生成唯一文件名,避免互相覆盖,同时支持浏览器缓存:

import express from "express";
import sharp from "sharp";
import fs from "fs";
import path from "path";

const img = express.Router();

const resizeImage = async function (width: number, height: number) {
  const outputFileName = `fjord-${width}x${height}.jpg`;
  const outputPath = path.join(__dirname, "../imgs/thumbs", outputFileName);
  
  // 检查文件是否已存在,避免重复处理
  if (fs.existsSync(outputPath)) {
    return outputPath;
  }

  try {
    await sharp("./imgs/fjord.jpg")
      .resize({ width, height })
      .toFile(outputPath);
    return outputPath;
  } catch (err) {
    console.error(err);
    throw err;
  }
};

img.get("/", async (req, res) => {
  const { width, height } = req.query;
  const numWidth = Number(width);
  const numHeight = Number(height);

  if (isNaN(numWidth) || isNaN(numHeight) || numWidth <= 0 || numHeight <= 0) {
    return res.status(400).send("width和height必须是正整数");
  }

  try {
    const outputPath = await resizeImage(numWidth, numHeight);
    res.setHeader("Content-Type", "image/jpeg");
    // 设置缓存,相同尺寸的图片可以缓存一天
    res.setHeader("Cache-Control", "public, max-age=86400");
    res.sendFile(outputPath);
  } catch (err) {
    res.status(500).send("图片处理失败");
  }
});

export default img;

关键注意事项

  • 必须验证输入参数,避免无效值导致的运行时错误。
  • 在Express中不要直接throw err,要用res.status(500).send()处理错误,防止服务器崩溃。
  • 若无需保留缩放文件,直接用Sharp流输出是最优解,性能更高。

内容的提问来源于stack exchange,提问作者Ahmed Saeed

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 08:50:45