'use strict'; // 通用小工具:响应、请求体、文件名/扩展名处理(无状态,可被任意层引用) const path = require('path'); const MAX_BODY_BYTES = 32 * 1024 * 1024; const IMAGE_EXT = new Set(['.jpg', '.jpeg', '.png', '.webp', '.gif']); const MIME_BY_EXT = { '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.webp': 'image/webp', '.gif': 'image/gif', }; // 采集输出根目录下永远跳过的目录名 const SKIP_DIR_NAMES = new Set(['膳绘']); function sendJson(res, statusCode, obj) { const data = JSON.stringify(obj); res.writeHead(statusCode, { 'Content-Type': 'application/json; charset=utf-8' }); res.end(data); } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function sanitizeFileName(name) { return String(name).replace(/[\\/:*?"<>|]/g, '_').trim() || 'file'; } function readBody(req) { return new Promise((resolve, reject) => { const chunks = []; let size = 0; req.on('data', (c) => chunks.push(c)); req.on('data', (c) => { size += c.length; if (size > MAX_BODY_BYTES) { req.destroy(new Error('请求体过大')); reject(new Error('请求体过大')); } }); req.on('end', () => { if (!chunks.length) return resolve({}); try { resolve(JSON.parse(Buffer.concat(chunks).toString('utf8'))); } catch (e) { reject(e); } }); req.on('error', reject); }); } function guessExtFromUrl(urlStr) { try { const u = new URL(urlStr); let ext = path.extname(u.pathname).replace('.', '').toLowerCase(); if (ext === 'jpeg') ext = 'jpg'; if (['jpg', 'png', 'gif', 'webp'].includes(ext)) return ext; } catch { // fall through } return 'jpg'; } module.exports = { MAX_BODY_BYTES, IMAGE_EXT, MIME_BY_EXT, SKIP_DIR_NAMES, sendJson, sleep, sanitizeFileName, readBody, guessExtFromUrl, };