import * as http from 'http'; import * as https from 'https'; import * as fs from 'fs'; import * as path from 'path'; // 配置文件路径 const CONFIG_FILE = path.join(__dirname, 'config.json'); // 定义类型 interface RouteConfig { [route: string]: string; } interface AppConfig { cacheConfig: boolean; reloadOnChange: boolean; defaultContentType: string; proxyPort: number; targetHost: string; } interface ConfigFile { routes: RouteConfig; config: AppConfig; } // 存储当前的路由配置 let MOCK_ROUTES: RouteConfig = {}; let CONFIG: AppConfig = { cacheConfig: true, reloadOnChange: true, defaultContentType: "application/json", proxyPort: 9443, targetHost: "devrmtapp.resmart.cn" }; // 过滤掉以 # 开头的路由(视为注释,不参与 mock) function filterActiveRoutes(routes: RouteConfig): RouteConfig { const filtered: RouteConfig = {}; for (const [route, filePath] of Object.entries(routes)) { if (route.startsWith('#')) continue; filtered[route] = filePath; } return filtered; } // 加载配置文件 function loadConfig() { try { if (!fs.existsSync(CONFIG_FILE)) { console.warn(`[CONFIG] 配置文件不存在: ${CONFIG_FILE}`); console.warn(`[CONFIG] 正在创建默认配置文件...`); // 创建默认配置 const defaultConfig: ConfigFile = { routes: { "/api2/test": "mock/test.txt" }, config: { cacheConfig: true, reloadOnChange: true, defaultContentType: "application/json", proxyPort: 9443, targetHost: "devrmtapp.resmart.cn" } }; // 确保mock目录存在 const mockDir = path.join(__dirname, 'mock'); if (!fs.existsSync(mockDir)) { fs.mkdirSync(mockDir, { recursive: true }); } // 保存配置文件 fs.writeFileSync(CONFIG_FILE, JSON.stringify(defaultConfig, null, 2), 'utf-8'); console.log(`[CONFIG] 已创建默认配置文件: ${CONFIG_FILE}`); // 加载配置(# 开头的路由视为注释,不参与 mock) const configData: ConfigFile = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8')); MOCK_ROUTES = filterActiveRoutes(configData.routes || {}); CONFIG = configData.config || CONFIG; } else { const configData: ConfigFile = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8')); MOCK_ROUTES = filterActiveRoutes(configData.routes || {}); CONFIG = configData.config || CONFIG; console.log(`[CONFIG] 配置文件已加载,共 ${Object.keys(MOCK_ROUTES).length} 个路由`); } // 验证mock文件是否存在 validateMockFiles(); } catch (error) { console.error(`[CONFIG] 加载配置文件失败:`, error); // 使用默认配置 MOCK_ROUTES = { "/api2/user/list": "mock/user_list.txt" }; CONFIG = { cacheConfig: true, reloadOnChange: true, defaultContentType: "application/json", proxyPort: 9443, targetHost: "devrmtapp.resmart.cn" }; } } // 验证mock文件是否存在 function validateMockFiles() { console.log(`[CONFIG] 验证mock文件...`); let missingFiles = []; for (const [route, filePath] of Object.entries(MOCK_ROUTES)) { const fullPath = path.join(__dirname, filePath); if (!fs.existsSync(fullPath)) { missingFiles.push({ route, filePath, fullPath }); console.warn(`[CONFIG] 警告: mock文件不存在 - ${filePath} (用于路由: ${route})`); } } if (missingFiles.length > 0) { console.log(`[CONFIG] 缺少 ${missingFiles.length} 个mock文件,请创建这些文件`); } else { console.log(`[CONFIG] 所有mock文件验证通过`); } } // 检查是否为mock路由的函数 function isMockRoute(requestPath: string): boolean { return MOCK_ROUTES.hasOwnProperty(requestPath); } // 获取mock文件路径 function getMockFilePath(requestPath: string): string { return MOCK_ROUTES[requestPath]; } // 代理服务器 const proxyServer = http.createServer((clientReq, clientRes) => { // 解析客户端请求的 URL const parsedUrl = new URL(`http://localhost${clientReq.url!}`); const requestPath = parsedUrl.pathname; // 检查是否为需要mock的路由 if (isMockRoute(requestPath)) { const mockFile = getMockFilePath(requestPath); console.log(`[MOCK] 拦截路由: ${requestPath} -> 使用文件: ${mockFile}`); try { // 构建完整的文件路径 const mockFilePath = path.join(__dirname, mockFile); // 检查文件是否存在 if (!fs.existsSync(mockFilePath)) { console.error(`[MOCK] Mock文件不存在: ${mockFilePath}`); clientRes.writeHead(404, { 'Content-Type': 'application/json' }); clientRes.end(JSON.stringify({ error: 'Mock file not found', route: requestPath, file: mockFile, timestamp: new Date().toISOString() })); return; } // 读取本地mock文件 const mockData = fs.readFileSync(mockFilePath, 'utf-8'); // 设置响应头 const contentType = CONFIG.defaultContentType || 'application/json'; clientRes.writeHead(200, { 'Content-Type': contentType, 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type', 'X-Mock-Source': mockFile, 'X-Mock-Timestamp': new Date().toISOString() }); // 返回mock数据 clientRes.end(mockData); console.log(`[MOCK] 成功返回mock数据: ${mockFile}`); } catch (error) { console.error(`[MOCK] 读取mock文件失败: ${mockFile}`, error); clientRes.writeHead(500, { 'Content-Type': 'application/json' }); clientRes.end(JSON.stringify({ error: 'Failed to read mock data', route: requestPath, file: mockFile, message: (error as Error).message, timestamp: new Date().toISOString() })); } return; // 直接返回,不转发到目标服务器 } // 如果不是mock路由,正常代理转发 console.log(`[PROXY] 转发请求: ${requestPath}`); // 目标服务器的选项 const options: http.RequestOptions = { hostname: CONFIG.targetHost, port: 443, // HTTPS 默认端口 method: clientReq.method, path: parsedUrl.pathname + parsedUrl.search, headers: { ...clientReq.headers, host: CONFIG.targetHost } }; // 创建到目标服务器的 HTTPS 请求 const proxyReq = https.request(options, (proxyRes) => { // 将目标服务器的响应头复制到客户端响应 clientRes.writeHead(proxyRes.statusCode!, proxyRes.headers); // 将目标服务器的响应数据管道传输到客户端 proxyRes.pipe(clientRes); }); // 错误处理 proxyReq.on('error', (err) => { console.error('Proxy request error:', err); clientRes.writeHead(500, { 'Content-Type': 'application/json' }); clientRes.end(JSON.stringify({ error: 'Proxy error', message: err.message, timestamp: new Date().toISOString() })); }); // 将客户端请求体管道传输到代理请求 clientReq.pipe(proxyReq); }); // 添加管理接口用于重新加载配置 proxyServer.on('request', (req, res) => { const parsedUrl = new URL(`http://localhost${req.url!}`); const pathname = parsedUrl.pathname; // 管理接口:重新加载配置 if (pathname === '/__reload-config' && req.method === 'POST') { try { const oldCount = Object.keys(MOCK_ROUTES).length; loadConfig(); const newCount = Object.keys(MOCK_ROUTES).length; res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: true, message: 'Configuration reloaded successfully', routesCount: newCount, routesChanged: newCount - oldCount })); console.log(`[ADMIN] 通过API重新加载配置 (路由数: ${oldCount} -> ${newCount})`); } catch (error) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: false, error: (error as Error).message })); } } // 管理接口:查看当前配置 if (pathname === '/__config' && req.method === 'GET') { try { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ routes: MOCK_ROUTES, config: CONFIG, timestamp: new Date().toISOString(), totalRoutes: Object.keys(MOCK_ROUTES).length }, null, 2)); } catch (error) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: false, error: (error as Error).message })); } } }); // 初始化:第一次加载配置 loadConfig(); // 如果配置了文件监视,则监听配置文件变化 if (CONFIG.reloadOnChange) { fs.watchFile(CONFIG_FILE, (curr, prev) => { console.log(`[CONFIG] 配置文件已修改,重新加载...`); try { const oldRoutesCount = Object.keys(MOCK_ROUTES).length; loadConfig(); const newRoutesCount = Object.keys(MOCK_ROUTES).length; console.log(`[CONFIG] 配置重载完成 (路由数: ${oldRoutesCount} -> ${newRoutesCount})`); } catch (error) { console.error(`[CONFIG] 重新加载配置文件失败:`, error); } }); console.log(`[CONFIG] 已启用配置文件监视: ${CONFIG_FILE}`); } proxyServer.listen(CONFIG.proxyPort, "0.0.0.0", () => { console.log(`========================================`); console.log(`代理服务器运行在: http://localhost:${CONFIG.proxyPort}`); console.log(`目标服务器: https://${CONFIG.targetHost}`); console.log(`配置文件: ${CONFIG_FILE}`); console.log(`已配置Mock路由: ${Object.keys(MOCK_ROUTES).length} 个`); console.log(`========================================`); console.log(`管理接口:`); console.log(` GET http://localhost:${CONFIG.proxyPort}/__config 查看当前配置`); console.log(` POST http://localhost:${CONFIG.proxyPort}/__reload-config 重新加载配置`); console.log(`========================================`); console.log(`Mock路由列表:`); for (const [route, file] of Object.entries(MOCK_ROUTES)) { const filePath = path.join(__dirname, file); const exists = fs.existsSync(filePath) ? '✓' : '✗'; console.log(` ${exists} ${route} -> ${file}`); } console.log(`========================================`); });