mirror of
https://github.com/woodchen-ink/Random-Api.git
synced 2025-07-18 14:02:03 +08:00
180 lines
4.7 KiB
JavaScript
180 lines
4.7 KiB
JavaScript
const express = require('express');
|
|
const fetch = require('node-fetch');
|
|
const LRU = require('lru-cache');
|
|
const compression = require('compression');
|
|
const winston = require('winston');
|
|
require('winston-daily-rotate-file');
|
|
const cluster = require('cluster');
|
|
const numCPUs = require('os').cpus().length;
|
|
|
|
const app = express();
|
|
const port = 5003;
|
|
|
|
// 外部 JSON 文件的 URL
|
|
const CSV_PATHS_URL = 'https://random-api-file.czl.net/url.json';
|
|
|
|
// 设置缓存
|
|
let csvPathsCache = null;
|
|
let lastFetchTime = 0;
|
|
const CACHE_DURATION = 60 * 1000 *60 * 24; // 24小时
|
|
|
|
const csvCache = new LRU({
|
|
max: 100, // 最多缓存100个CSV文件
|
|
maxAge: 1000 * 60 * 60 * 24 // 缓存24小时
|
|
});
|
|
|
|
//日志格式
|
|
const consoleFormat = winston.format.printf(({ level, message, timestamp }) => {
|
|
return `${timestamp} ${level}: ${message}`;
|
|
});
|
|
// 设置日志
|
|
const logger = winston.createLogger({
|
|
level: 'info',
|
|
format: winston.format.combine(
|
|
winston.format.timestamp(),
|
|
winston.format.json()
|
|
),
|
|
transports: [
|
|
new winston.transports.Console({
|
|
format: winston.format.combine(
|
|
winston.format.timestamp(),
|
|
consoleFormat
|
|
),
|
|
}),
|
|
new winston.transports.DailyRotateFile({
|
|
filename: 'logs/application-%DATE%.log',
|
|
datePattern: 'YYYY-MM-DD-HH',
|
|
zippedArchive: true,
|
|
maxSize: '20m',
|
|
maxFiles: '14d'
|
|
})
|
|
]
|
|
});
|
|
|
|
|
|
// 使用压缩中间件
|
|
app.use(compression());
|
|
|
|
// 日志缓冲
|
|
let logBuffer = [];
|
|
|
|
// 增强的日志中间件
|
|
app.use((req, res, next) => {
|
|
const logEntry = {
|
|
timestamp: new Date().toISOString(),
|
|
ip: req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress,
|
|
method: req.method,
|
|
path: req.originalUrl,
|
|
protocol: req.protocol,
|
|
host: req.get('Host'),
|
|
userAgent: req.get('User-Agent'),
|
|
referer: req.get('Referer') || 'N/A',
|
|
accept: req.get('Accept'),
|
|
acceptEncoding: req.get('Accept-Encoding'),
|
|
acceptLanguage: req.get('Accept-Language')
|
|
};
|
|
|
|
// 立即输出到控制台
|
|
console.log(JSON.stringify(logEntry));
|
|
|
|
// 添加到缓冲区,用于每小时写入文件
|
|
logBuffer.push(logEntry);
|
|
|
|
next();
|
|
});
|
|
|
|
// 每小时输出一次日志
|
|
setInterval(() => {
|
|
if (logBuffer.length > 0) {
|
|
logger.info('Hourly log', { logs: logBuffer });
|
|
logBuffer = [];
|
|
}
|
|
}, 60 * 60 * 1000); // 每小时
|
|
|
|
async function getCsvPaths() {
|
|
const now = Date.now();
|
|
if (!csvPathsCache || now - lastFetchTime > CACHE_DURATION) {
|
|
const response = await fetch(CSV_PATHS_URL);
|
|
if (response.ok) {
|
|
csvPathsCache = await response.json();
|
|
lastFetchTime = now;
|
|
}
|
|
}
|
|
return csvPathsCache;
|
|
}
|
|
|
|
async function getCsvContent(url) {
|
|
if (csvCache.has(url)) {
|
|
return csvCache.get(url);
|
|
}
|
|
const response = await fetch(url);
|
|
if (response.ok) {
|
|
const content = await response.text();
|
|
csvCache.set(url, content);
|
|
return content;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async function handleRequest(req, res) {
|
|
try {
|
|
const csvPaths = await getCsvPaths();
|
|
if (!csvPaths) {
|
|
return res.status(500).send('CSV paths configuration could not be fetched.');
|
|
}
|
|
|
|
let path = req.path.slice(1);
|
|
path = path.split('?')[0];
|
|
if (path.endsWith('/')) {
|
|
path = path.slice(0, -1);
|
|
}
|
|
|
|
const pathSegments = path.split('/');
|
|
const prefix = pathSegments[0];
|
|
const suffix = pathSegments.slice(1).join('/');
|
|
|
|
if (prefix in csvPaths && suffix in csvPaths[prefix]) {
|
|
const csvUrl = csvPaths[prefix][suffix];
|
|
const fileArrayText = await getCsvContent(csvUrl);
|
|
if (fileArrayText) {
|
|
const fileArray = fileArrayText.split('\n').filter(line => Boolean(line) && !line.trim().startsWith('#'));
|
|
const randomUrl = fileArray[Math.floor(Math.random() * fileArray.length)];
|
|
return res.redirect(302, randomUrl);
|
|
} else {
|
|
return res.status(500).send('CSV file could not be fetched.');
|
|
}
|
|
} else {
|
|
const indexHtmlResponse = await fetch('https://random-api-file.czl.net');
|
|
const indexHtml = await indexHtmlResponse.text();
|
|
return res.type('html').send(indexHtml);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
logger.error('Error:', error);
|
|
res.status(500).send('Internal Server Error');
|
|
}
|
|
}
|
|
|
|
// 处理所有路由
|
|
app.get('*', handleRequest);
|
|
|
|
// 使用 cluster 模块
|
|
if (cluster.isMaster) {
|
|
console.log(`Master ${process.pid} is running`);
|
|
|
|
// Fork workers.
|
|
for (let i = 0; i < numCPUs; i++) {
|
|
cluster.fork();
|
|
}
|
|
|
|
cluster.on('exit', (worker, code, signal) => {
|
|
console.log(`worker ${worker.process.pid} died`);
|
|
});
|
|
} else {
|
|
// Workers can share any TCP connection
|
|
// In this case it is an HTTP server
|
|
app.listen(port, () => {
|
|
console.log(`Worker ${process.pid} started on port ${port}`);
|
|
});
|
|
}
|