const http = require('http'); const url = require('url'); const path = require('path'); const fs = require('fs'); const sharp = require('sharp'); const PORT = process.env.PORT || 3000; const IMAGE_BASE_DIR = process.env.IMAGE_BASE_DIR || '/app/images'; /** * 安全检查:防止路径遍历攻击(如 ../../etc/passwd) * @param {string} targetPath 目标文件路径 * @param {string} baseDir 基础目录 * @returns {boolean} 是否合法 */ function isSafePath(targetPath, baseDir) { const resolvedTarget = path.resolve(targetPath).toLowerCase(); const resolvedBase = path.resolve(baseDir).toLowerCase(); return resolvedTarget.startsWith(resolvedBase); } /** * 发送错误响应 * @param {http.ServerResponse} res 响应对象 * @param {number} statusCode 状态码 * @param {string} message 错误信息 */ function sendError(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 sendError(res, 405, '仅支持GET请求'); } try { const parsedUrl = url.parse(req.url, true); const requestPath = parsedUrl.pathname; const query = parsedUrl.query; // 过滤根路径请求(避免直接访问服务根目录) if (requestPath === '/') { return sendError(res, 200, ` 图片处理服务已启动! 使用示例: 1. 访问原图片:http://localhost:${PORT}/a/b/c.png 2. 调整质量:http://localhost:${PORT}/a/b/c.png?q=80 图片基础目录:${IMAGE_BASE_DIR} `); } const requestedFilePath = requestPath.slice(1); const fullImagePath = path.join(IMAGE_BASE_DIR, requestedFilePath); // 安全检查:防止路径遍历 if (!isSafePath(fullImagePath, IMAGE_BASE_DIR)) { return sendError(res, 403, '访问拒绝:非法路径(路径遍历攻击)'); } if (!fs.existsSync(fullImagePath)) { return sendError(res, 404, `图片不存在:${requestedFilePath}`); } if (!query.q) { const fileStream = fs.createReadStream(fullImagePath); const ext = path.extname(fullImagePath).slice(1); // 如 png/jpg res.writeHead(200, { 'Content-Type': `image/${ext}` }); // 流式传输文件(适合大文件) fileStream.pipe(res); fileStream.on('error', (err) => { sendError(res, 500, `读取文件失败:${err.message}`); }); return; } const quality = parseInt(query.q); if (isNaN(quality)) { return sendError(res, 400, '质量参数q必须是数字(1-100)'); } const finalQuality = Math.max(1, Math.min(100, quality)); const imageBuffer = await sharp(fullImagePath).rotate().jpeg({ quality: finalQuality }).toBuffer(); res.writeHead(200, { 'Content-Type': 'image/jpeg', 'Content-Length': imageBuffer.length }); res.end(imageBuffer); } catch (error) { console.error('服务器异常:', error); sendError(res, 500, `服务器内部错误:${error.message}`); } }); // ===================== 启动服务器 ===================== server.listen(PORT, '0.0.0.0', () => { console.log(`服务地址:http://0.0.0.0:${PORT}`); }); // 捕获全局未处理异常 process.on('uncaughtException', (err) => { console.error('全局未捕获异常:', err); }); process.on('unhandledRejection', (reason) => { console.error('全局未处理Promise拒绝:', reason); });