house-data-collect/server.js
2026-03-20 21:53:19 +08:00

169 lines
5.0 KiB
JavaScript

/**
* Web服务器脚本
* 提供静态文件服务和API接口
*/
const http = require('http');
const fs = require('fs');
const path = require('path');
const url = require('url');
const BASE_DIR = '/app/houseDream';
// const BASE_DIR = '/Users/liyanyan/vps/tencet-ecs/app/houseDream';
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) {
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('\n正在关闭服务器...');
server.close(() => {
console.log('服务器已关闭');
process.exit(0);
});
});
process.on('SIGINT', () => {
console.log('\n正在关闭服务器...');
server.close(() => {
console.log('服务器已关闭');
process.exit(0);
});
});