174 lines
5.1 KiB
JavaScript
174 lines
5.1 KiB
JavaScript
/**
|
|
* Web服务器脚本
|
|
* 提供静态文件服务和API接口
|
|
*/
|
|
|
|
const http = require('http');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const url = require('url');
|
|
const dotenv = require('dotenv');
|
|
|
|
dotenv.config({path: ['.env.local', '.env'], quiet: true});
|
|
|
|
console.log('环境变量:', process.env.BASE_DIR);
|
|
|
|
const BASE_DIR = process.env.BASE_DIR;
|
|
const WEB_DIR = path.join(BASE_DIR, 'web');
|
|
const DATA_DIR = path.join(BASE_DIR, 'data');
|
|
const PIC_DIR = path.join(BASE_DIR, 'pic');
|
|
|
|
const PORT = process.env.PORT || 8080;
|
|
|
|
// MIME类型映射
|
|
const mimeTypes = {
|
|
'.html': 'text/html',
|
|
'.css': 'text/css',
|
|
'.js': 'application/javascript',
|
|
'.json': 'application/json',
|
|
'.png': 'image/png',
|
|
'.jpg': 'image/jpeg',
|
|
'.gif': 'image/gif',
|
|
'.ico': 'image/x-icon'
|
|
};
|
|
|
|
// 获取MIME类型
|
|
function getMimeType(filePath) {
|
|
const ext = path.extname(filePath).toLowerCase();
|
|
return mimeTypes[ext] || 'application/octet-stream';
|
|
}
|
|
|
|
// 读取文件
|
|
function readFile(filePath) {
|
|
return new Promise((resolve, reject) => {
|
|
fs.readFile(filePath, (err, data) => {
|
|
if (err) reject(err);
|
|
else resolve(data);
|
|
});
|
|
});
|
|
}
|
|
|
|
// 列出可用日期
|
|
function listAvailableDates() {
|
|
try {
|
|
const files = fs.readdirSync(DATA_DIR);
|
|
return files
|
|
.filter(f => f.endsWith('.json') && !f.includes('_raw') && !f.includes('test'))
|
|
.map(f => f.replace('.json', ''))
|
|
.sort()
|
|
.reverse();
|
|
} catch (err) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// 创建服务器
|
|
const server = http.createServer(async (req, res) => {
|
|
const parsedUrl = url.parse(req.url, true);
|
|
let pathname = parsedUrl.pathname;
|
|
|
|
// 设置CORS头
|
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
|
|
if (req.method === 'OPTIONS') {
|
|
res.writeHead(200);
|
|
res.end();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// API: 列出可用日期
|
|
if (pathname === '/api/dates') {
|
|
const dates = listAvailableDates();
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({ dates }));
|
|
return;
|
|
}
|
|
|
|
// API: 获取指定日期数据
|
|
if (pathname.startsWith('/api/data/')) {
|
|
const date = pathname.replace('/api/data/', '');
|
|
const filePath = path.join(DATA_DIR, `${date}.json`);
|
|
|
|
if (fs.existsSync(filePath)) {
|
|
const data = await readFile(filePath);
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.end(data);
|
|
} else {
|
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({ error: '数据不存在' }));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 静态文件服务
|
|
if (pathname === '/') {
|
|
pathname = '/index.html';
|
|
}
|
|
|
|
// 处理 data 和 pic 路径
|
|
let filePath;
|
|
if (pathname.startsWith('/data/')) {
|
|
filePath = path.join(DATA_DIR, pathname.replace('/data/', ''));
|
|
} else if (pathname.startsWith('/pic/')) {
|
|
filePath = path.join(PIC_DIR, pathname.replace('/pic/', ''));
|
|
} else if (pathname.startsWith('/public/')) {
|
|
filePath = path.join(BASE_DIR, pathname);
|
|
} else {
|
|
filePath = path.join(WEB_DIR, pathname);
|
|
}
|
|
|
|
// 安全检查:防止目录遍历
|
|
if (!filePath.startsWith(BASE_DIR)) {
|
|
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
res.end('Forbidden');
|
|
return;
|
|
}
|
|
|
|
const data = await readFile(filePath);
|
|
const mimeType = getMimeType(filePath);
|
|
|
|
res.writeHead(200, { 'Content-Type': mimeType });
|
|
res.end(data);
|
|
|
|
} catch (err) {
|
|
console.error('文件读取错误:', err);
|
|
if (err.code === 'ENOENT') {
|
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
res.end('Not Found');
|
|
} else {
|
|
console.error('服务器错误:', err);
|
|
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
res.end('Internal Server Error');
|
|
}
|
|
}
|
|
});
|
|
|
|
// 启动服务器
|
|
server.listen(PORT, 'localhost', () => {
|
|
console.log('==========================================');
|
|
console.log('北京市房地产数据监控服务器已启动');
|
|
console.log(`访问地址: http://localhost:${PORT}`);
|
|
console.log('按 Ctrl+C 停止服务器');
|
|
console.log('==========================================');
|
|
});
|
|
|
|
// 优雅关闭
|
|
process.on('SIGTERM', () => {
|
|
console.log('\nSIGTERM\n正在关闭服务器...');
|
|
server.close(() => {
|
|
console.log('服务器已关闭');
|
|
process.exit(0);
|
|
});
|
|
});
|
|
|
|
process.on('SIGINT', () => {
|
|
console.log('\nSIGINT\n正在关闭服务器...');
|
|
server.close(() => {
|
|
console.log('服务器已关闭');
|
|
process.exit(0);
|
|
});
|
|
});
|