Files
blog-press/docs/Web/Others/ImageProcessor.md

155 lines
5.4 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 图片处理服务
date: 2026-06-04
---
# 一、简介
  基于 Node.js + Sharp 实现的轻量级图片处理服务。
  [sharp](https://sharp.pixelplumbing.com/)是 Node.js 下非常常用的高性能图像处理库,主要用来缩放、裁剪、旋转、格式转换、压缩图片等,速度快、内存占用低。
  支持路径+参数的形式实时压缩图片,例如`ip:port/a.png?q=80&w=1024&h=768`
# 二、核心方法
## 2.1 根据参数调整图片
```JavaScript
const sharp = require('sharp');
// 压缩配置
const compressOptions = {
quality: 0.7,
maxHeight: 800,
maxWidth: 600
};
/**
* 处理图片压缩和调整大小
* @param {string} imagePath 图片路径
* @param {Object} query 查询参数
* @returns {Promise<Buffer>} 处理后的图片Buffer
*/
async function processImage(imagePath, query) {
let sharpInstance = sharp(imagePath).rotate();
// 处理宽高参数
let targetWidth = parseInt(query.w) || parseInt(query.width);
let targetHeight = parseInt(query.h) || parseInt(query.height);
// 如果没有指定宽高,使用配置的默认最大宽高
if (!targetWidth && !targetHeight) {
targetWidth = compressOptions.maxWidth;
targetHeight = compressOptions.maxHeight;
}
// 如果指定了宽高,进行调整大小处理
if (targetWidth || targetHeight) {
const resizeOptions = {
width: targetWidth,
height: targetHeight,
fit: sharp.fit.inside, // 保持宽高比,图片会完整显示在指定区域内
withoutEnlargement: true // 不放大比原始尺寸小的图片
};
sharpInstance = sharpInstance.resize(resizeOptions);
}
// 处理质量参数(优先使用查询参数,否则使用配置的默认值)
const quality = query.q ? Math.max(1, Math.min(100, parseInt(query.q))) :
Math.round(compressOptions.quality * 100);
const finalQuality = Math.max(1, Math.min(100, quality));
// 转换为JPEG格式并压缩
return sharpInstance.jpeg({quality: finalQuality, mozjpeg: true}).toBuffer();
}
```
&emsp;&emsp;核心方法:[resize](https://sharp.nodejs.cn/api-resize/)、[jpeg](https://sharp.nodejs.cn/api-output/#jpeg)、[toBuffer](https://sharp.nodejs.cn/api-output/#tobuffer)。
## 2.2 返回图片数据
```JavaScript
/**
* 发送响应
* @param {ServerResponse} res 响应对象
* @param {number} statusCode 状态码
* @param {string} message 信息
*/
function sendResponse(res, statusCode, message) {
res.writeHead(statusCode, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end(message);
}
const server = http.createServer(async (req, res) => {
if (req.method !== 'GET') {
return sendResponse(res, 405, '仅支持GET请求');
}
try {
const parsedUrl = url.parse(req.url, true);
const requestPath = parsedUrl.pathname;
const query = parsedUrl.query;
// 过滤根路径请求(避免直接访问服务根目录)
if (requestPath === '/') {
return sendResponse(res, 200, '图片处理服务已启动!');
}
const requestedFilePath = requestPath.slice(1);
const fullImagePath = path.join(IMAGE_BASE_DIR, requestedFilePath);
// 安全检查:防止路径遍历
if (!isSafePath(fullImagePath, IMAGE_BASE_DIR)) {
return sendResponse(res, 403, '访问拒绝:非法路径(路径遍历攻击)');
}
if (!fs.existsSync(fullImagePath)) {
return sendResponse(res, 404, `图片不存在:${requestedFilePath}`);
}
// 如果没有压缩参数,直接返回原图
if (!query.q && !query.w && !query.width && !query.h && !query.height) {
const fileStream = fs.createReadStream(fullImagePath);
const ext = path.extname(fullImagePath).slice(1);
res.writeHead(200, {'Content-Type': `image/${ext}`});
// 流式传输文件(适合大文件)
fileStream.pipe(res);
fileStream.on('error', (err) => {
sendResponse(res, 500, `读取文件失败:${err.message}`);
});
return;
}
// 处理图片压缩和调整大小
const imageBuffer = await processImage(fullImagePath, query);
res.writeHead(200, {
'Content-Type': 'image/jpeg',
'Content-Length': imageBuffer.length,
'Cache-Control': 'public, max-age=86400' // 缓存1天
});
res.end(imageBuffer);
} catch (error) {
console.error('服务器异常:', error);
sendResponse(res, 500, `服务器内部错误:${error.message}`);
}
});
```
&emsp;&emsp;原生Node.js中res用法
```js
res.writeHead(statusCode, [statusMessage], [headers]);
res.end(message)
```
::: tip
Express中req请求对象常用用法
1. 获取请求路径参数:`/user/:id`->`req.params.id`
2. 获取查询参数(?key=value`/search?name=tom&age=18`->`req.query.name`
3. 获取请求体:`req.body.username`
4. 获取请求头:`req.headers['user-agent']`
Express中res响应对象常用用法
1. 返回普通文本:`res.send('Hello Express');`
2. 返回JSON`res.json({ success: true, data: {} });`
3. 设置状态码:`res.status(404).json({ msg: 'Not Found' });`
4. 设置响应头:`res.set('Content-Type', 'text/plain');`
5. 结束响应:`res.end()`
:::