在 node.js 中需要图像处理方面的帮助

Need help in image handling in node.js

例如,有没有什么方法可以在需要时调整图像大小。 想要做的是这样 /image.jpg 给我原始图像,但 image_200x200.jpg 给我调整大小的图像,宽度和高度为 200 像素。 我想让它在渲染时调整图像大小,这样我就不必存储调整大小的图像。提前致谢

有什么办法吗?

是的,有。但是您必须知道,这样做会在每次请求时调整图像的大小。如果您预计您的网站流量很大,这不是一个可持续的解决方案,除非您将这些调整大小的图像实际存储在某种缓存中。

并且即使您确实缓存了它们,请注意如果没有任何 URL 签名(确保 URL 是由您的服务器发出的),恶意用户可能想要向您的服务器发送垃圾邮件每次都使用随机大小的请求,以强制对您的服务器进行调整大小操作并使其不堪重负。

既然已经解决了,让我们尝试找到解决方案。

我们如何在 Express 中路由请求?

Express 提供了一种方法 serve static files,我们可以在不需要调整大小时保留它。如果在此位置找不到文件,将检查下一个路由。所以在下一个中,我们将尝试检查是否需要调整大小,然后再尝试匹配其他路由。我们可以通过使用匹配特定模式的正则表达式来定位那些。

此示例将匹配 URL 以 _{n}x{n} 结尾和“jpeg”、“jpg”或“png”扩展名:

app.use(express.static(Path.join(__dirname, 'public'))); // If a file is found
app.use('/(*_\d+x\d+.(jpe?g|png))', resizingMiddleware); // If resizing is needed
// ... Other routes

中间件

A middleware 是 Express 在请求路由时调用的函数。在这种情况下,我们可以这样声明:

function resizingMiddleware(req, res, next)  {
  const data = parseResizingURI(req.baseUrl); // Extract data from the URI

  if (!data) { return next(); } // Could not parse the URI

  // Get full file path in public directory
  const path = Path.join(__dirname, 'public', data.path);

  resizeImage(path, data.width, data.height)
    .then(buffer => {
      // Success. Send the image
      res.set('Content-type', mime.lookup(path)); // using 'mime-types' package
      res.send(buffer);
    })
    .catch(next); // File not found or resizing failed
}

正在从 URI 中提取数据

正如您在上面看到的,我使用 parseResizingURI 来获取原始文件名以及请求的尺寸。让我们写这个函数:

function limitNumberToRange(num, min, max) {
  return Math.min(Math.max(num, min), max);
}

function parseResizingURI(uri) {
  // Attempt to extract some variables using Regex
  const matches = uri.match(
    /(?<path>.*\/)(?<name>[^\/]+)_(?<width>\d+)x(?<height>\d+)(?<extension>\.[a-z\d]+)$/i
  );

  if (matches) {
    const { path, name, width, height, extension } = matches.groups;
    return {
      path: path + name + extension, // Original file path
      width: limitNumberToRange(+width, 16, 2000),   // Ensure the size is in a range
      height: limitNumberToRange(+height, 16, 2000), // so people don't try 999999999
      extension: extension
    };
  }
  return false;
}

调整图像大小

在中间件中,你可能还会看到一个resizeImage函数。让我们用 sharp:

来写
function resizeImage(path, width, height) {
  return sharp(path).resize({
    width,
    height,
    // Preserve aspect ratio, while ensuring dimensions are <= to those specified
    fit: sharp.fit.inside,
  }).toBuffer();
}

把它们放在一起

最后,我们得到如下代码:

// Don't forget to install all used packages:
// $ npm install --save mime-types express sharp

const Path = require('path');
const mime = require('mime-types')
const sharp = require('sharp');
const express = require('express');
const app = express();

// Existing files are sent through as-is
app.use(express.static(Path.join(__dirname, 'public')));
// Requests for resizing
app.use('/(*_\d+x\d+.(jpe?g|png))', resizingMiddleware);
// Other routes...
app.get('/', (req, res) => { res.send('Hello World!'); });

app.listen(3000);

function resizingMiddleware(req, res, next)  {
  const data = parseResizingURI(req.baseUrl); // Extract data from the URI

  if (!data) { return next(); } // Could not parse the URI

  // Get full file path in public directory
  const path = Path.join(__dirname, 'public', data.path);

  resizeImage(path, data.width, data.height)
    .then(buffer => {
      // Success. Send the image
      res.set('Content-type', mime.lookup(path)); // using 'mime-types' package
      res.send(buffer);
    })
    .catch(next); // File not found or resizing failed
}

function resizeImage(path, width, height) {
  return sharp(path).resize({
    width,
    height,
    // Preserve aspect ratio, while ensuring dimensions are <= to those specified
    fit: sharp.fit.inside,
  }).toBuffer();
}

function limitNumberToRange(num, min, max) {
  return Math.min(Math.max(num, min), max);
}

function parseResizingURI(uri) {
  // Attempt to extract some variables using Regex
  const matches = uri.match(
    /(?<path>.*\/)(?<name>[^\/]+)_(?<width>\d+)x(?<height>\d+)(?<extension>\.[a-z\d]+)$/i
  );

  if (matches) {
    const { path, name, width, height, extension } = matches.groups;
    return {
      path: path + name + extension, // Original file path
      width: limitNumberToRange(+width, 16, 2000),   // Ensure the size is in a range
      height: limitNumberToRange(+height, 16, 2000), // so people don't try 999999999
      extension: extension
    };
  }
  return false;
}