require('dotenv').config(); const http = require('http'); const url = require('url'); const path = require('path'); const fs = require('fs'); const {sendResponse, isSafePath, processImage} = require("./utils"); const PORT = process.env.PORT || 3000; const IMAGE_BASE_DIR = process.env.IMAGE_BASE_DIR || '/app/images'; // 定义支持的图片扩展名 const IMAGE_EXTENSIONS = new Set(['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg', 'tiff', 'tif']); // MIME 类型映射 const MIME_TYPES = { 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'png': 'image/png', 'gif': 'image/gif', 'webp': 'image/webp', 'bmp': 'image/bmp', 'svg': 'image/svg+xml', 'tiff': 'image/tiff', 'tif': 'image/tiff', 'mp4': 'video/mp4', 'webm': 'video/webm', 'ogg': 'video/ogg', 'avi': 'video/x-msvideo', 'mov': 'video/quicktime', 'mp3': 'audio/mpeg', 'wav': 'audio/wav', 'pdf': 'application/pdf', 'txt': 'text/plain', 'html': 'text/html', 'css': 'text/css', 'js': 'application/javascript', 'json': 'application/json' }; 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}`); } // 获取文件扩展名 const ext = path.extname(fullImagePath).toLowerCase().slice(1); const isImage = IMAGE_EXTENSIONS.has(ext); const hasImageParams = query.q || query.w || query.width || query.h || query.height; // 如果不是图片文件,或者图片文件但没有处理参数,直接返回原文件 if (!isImage || !hasImageParams) { const fileStream = fs.createReadStream(fullImagePath); // 设置正确的 Content-Type const contentType = MIME_TYPES[ext] || 'application/octet-stream'; res.writeHead(200, {'Content-Type': contentType}); // 流式传输文件 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}`); } }); // ===================== 启动服务器 ===================== 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); });