开源透明,所有处理逻辑在此,你也可以自己部署
const http = require('http');
const QRCode = require('qrcode');
const { PDFDocument } = require('pdf-lib');
const { IncomingForm } = require('formidable');
const sharp = require('sharp');
const fs = require('fs');
const path = require('path');
const os = require('os');
const PORT = 3001;
const TMP_DIR = path.join(os.tmpdir(), 'tools_server');
const MAX_TMP_AGE = 60 * 60 * 1000; // 1 hour
// Ensure temp directory exists and clean old files on startup
if (!fs.existsSync(TMP_DIR)) {
fs.mkdirSync(TMP_DIR, { recursive: true });
} else {
// Clean leftover files older than 1 hour
try {
const now = Date.now();
const files = fs.readdirSync(TMP_DIR);
for (const f of files) {
const fp = path.join(TMP_DIR, f);
try {
const stat = fs.statSync(fp);
if (now - stat.mtimeMs > MAX_TMP_AGE) {
fs.unlinkSync(fp);
console.log('Cleaned old temp file:', f);
}
} catch(e) {}
}
} catch(e) { console.error('Startup cleanup error:', e.message); }
}
// Periodic cleanup every 30 minutes
setInterval(() => {
try {
const now = Date.now();
const files = fs.readdirSync(TMP_DIR);
for (const f of files) {
const fp = path.join(TMP_DIR, f);
try {
const stat = fs.statSync(fp);
if (now - stat.mtimeMs > MAX_TMP_AGE) {
fs.unlinkSync(fp);
}
} catch(e) {}
}
} catch(e) { console.error('Periodic cleanup error:', e.message); }
}, 30 * 60 * 1000);
// Safe file cleanup
function safeCleanup(...paths) {
for (const p of paths) {
try { if (p && fs.existsSync(p)) fs.unlinkSync(p); } catch(e) {}
}
}
function parseForm(req) {
return new Promise((resolve, reject) => {
const form = new IncomingForm({ uploadDir: TMP_DIR, keepExtensions: true });
form.parse(req, (err, fields, files) => {
if (err) return reject(err);
resolve({ fields, files });
});
});
}
function readBody(req) {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
try { resolve(JSON.parse(body)); }
catch (e) { reject(new Error('Invalid JSON')); }
});
});
}
const server = http.createServer(async (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
try {
const url = new URL(req.url, `http://localhost:${PORT}`);
const route = url.pathname;
if (route === '/qr' && req.method === 'POST') {
// QR code: pure in-memory, no temp files
const body = await readBody(req);
const content = (body.content || '').trim();
if (!content) throw new Error('Content is required');
const size = parseInt(body.size) || 5;
const level = (body.level || 'M').toUpperCase();
const validLevels = ['L', 'M', 'Q', 'H'];
const ecLevel = validLevels.includes(level) ? level : 'M';
const width = [200, 350, 500, 800][[3, 5, 7, 10].indexOf(size)] || 350;
const qrBuffer = await QRCode.toBuffer(content, {
type: 'png', width, margin: 2, errorCorrectionLevel: ecLevel
});
res.writeHead(200, {
'Content-Type': 'image/png',
'Content-Length': qrBuffer.length,
'Cache-Control': 'no-cache'
});
res.end(qrBuffer);
} else if (route === '/pdf-merge' && req.method === 'POST') {
const { files } = await parseForm(req);
const pdfFiles = [];
try {
for (const key of Object.keys(files)) {
const file = files[key];
const arr = Array.isArray(file) ? file : [file];
for (const f of arr) {
if (f.mimetype === 'application/pdf' || f.originalFilename.toLowerCase().endsWith('.pdf')) {
pdfFiles.push(f.filepath);
}
}
}
if (pdfFiles.length < 2) throw new Error('需要至少2个PDF文件');
const mergedPdf = await PDFDocument.create();
for (const pdfPath of pdfFiles) {
const pdfBytes = fs.readFileSync(pdfPath);
const pdf = await PDFDocument.load(pdfBytes, { ignoreEncryption: true });
const indices = pdf.getPageIndices();
const pages = await mergedPdf.copyPages(pdf, indices);
pages.forEach(page => mergedPdf.addPage(page));
}
const mergedBytes = await mergedPdf.save();
res.writeHead(200, {
'Content-Type': 'application/pdf',
'Content-Disposition': 'attachment; filename="merged.pdf"',
'Content-Length': mergedBytes.length
});
res.end(mergedBytes);
} finally {
safeCleanup(...pdfFiles);
}
} else if (route === '/photo-restore' && req.method === 'POST') {
const { files } = await parseForm(req);
let inputPath = null;
let outputPath = null;
try {
let imgFile = null;
for (const key of Object.keys(files)) {
const file = files[key];
const arr = Array.isArray(file) ? file : [file];
for (const f of arr) {
if (f.mimetype && f.mimetype.startsWith('image/')) { imgFile = f; break; }
}
if (imgFile) break;
}
if (!imgFile) throw new Error('请上传一张图片');
inputPath = imgFile.filepath;
outputPath = path.join(TMP_DIR, 'restored_' + Date.now() + '.jpg');
const metadata = await sharp(inputPath).metadata();
let pipeline = sharp(inputPath);
if (metadata.channels && metadata.channels >= 3) {
pipeline = pipeline.normalize()
.modulate({ brightness: 1.05, saturation: 1.15 })
.sharpen({ sigma: 1.2, m1: 0, m2: 3, x1: 3, y2: 15, y3: 15 });
} else {
pipeline = pipeline.normalize()
.sharpen({ sigma: 1.5, m1: 0, m2: 3, x1: 3, y2: 15, y3: 15 });
}
await pipeline.jpeg({ quality: 92, mozjpeg: true }).toFile(outputPath);
const resultBuffer = fs.readFileSync(outputPath);
res.writeHead(200, {
'Content-Type': 'image/jpeg',
'Content-Length': resultBuffer.length,
'Cache-Control': 'no-cache'
});
res.end(resultBuffer);
} finally {
safeCleanup(inputPath, outputPath);
}
} else if (route === '/stats') {
var counterFile = '/www/wwwroot/273059_cn/data/counter.json';
var count = 0, updated = null;
try {
var data = fs.readFileSync(counterFile, 'utf-8');
var parsed = JSON.parse(data);
count = parsed.count || 0;
updated = parsed.updated || null;
} catch(e) {}
var stats = {
uptime: process.uptime(),
memory: process.memoryUsage(),
count: count,
updated: updated,
node: process.version,
platform: process.platform,
arch: process.arch,
pid: process.pid,
pages: [
{ name: '首页', url: 'https://273059.cn/' },
{ name: '不务正业', url: 'https://273059.cn/games/' },
{ name: '瞎捣鼓', url: 'https://273059.cn/tools/' },
{ name: '交作业', url: 'https://273059.cn/download/' },
{ name: '留言', url: 'https://273059.cn/liuyan.php' },
{ name: '关于', url: 'https://273059.cn/blog/' },
{ name: '毒鸡汤', url: 'https://273059.cn/tools/soup.php' },
{ name: '极客工具箱', url: 'https://273059.cn/tools/geek-tools.php' },
{ name: '图片压缩', url: 'https://273059.cn/tools/image-compress.php' },
{ name: '二维码生成', url: 'https://273059.cn/tools/qr-generator.php' },
{ name: 'PDF合并', url: 'https://273059.cn/tools/pdf-merge.php' },
{ name: '网络测速', url: 'https://273059.cn/tools/speed-test.php' },
{ name: '天气卡片', url: 'https://273059.cn/tools/weather-card.php' },
{ name: '老照片修复', url: 'https://273059.cn/tools/photo-restore.php' },
{ name: '雷电战机', url: 'https://273059.cn/games/thunder-tank/' },
{ name: '贪吃蛇', url: 'https://273059.cn/games/snake/' },
{ name: '打字练习', url: 'https://273059.cn/python-typing-game.html' },
{ name: '仪表盘', url: 'https://273059.cn/tools/dashboard.php' }
],
timestamp: new Date().toISOString()
};
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(stats));
} else if (route === '/counter') {
const counterFile = '/www/wwwroot/273059_cn/data/counter.json';
let count = 0;
try {
const data = fs.readFileSync(counterFile, 'utf-8');
count = JSON.parse(data).count || 0;
} catch(e) {}
if (req.method === 'POST') {
count++;
try {
const dirName = path.dirname(counterFile);
if (!fs.existsSync(dirName)) fs.mkdirSync(dirName, { recursive: true });
fs.writeFileSync(counterFile, JSON.stringify({ count, updated: new Date().toISOString() }));
} catch(e) { console.error('Counter write error:', e.message); }
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ count }));
} else {
res.writeHead(404);
res.end('Not found');
}
} catch (err) {
console.error('Error:', err.message);
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end(err.message || 'Internal error');
}
});
server.listen(PORT, '127.0.0.1', () => {
console.log(`Tools server running on http://127.0.0.1:${PORT}`);
});