diff --git a/config.json b/config.json index 619e00c..085c8c4 100644 --- a/config.json +++ b/config.json @@ -11,6 +11,7 @@ "#/api2/device/one": "mock/device_detail.txt" }, "config": { + "mockEnabled": true, "cacheConfig": true, "reloadOnChange": true, "defaultContentType": "application/json", diff --git a/index.api.js b/index.api.js new file mode 100644 index 0000000..817c8d3 --- /dev/null +++ b/index.api.js @@ -0,0 +1,292 @@ +"use strict"; +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var http = require("http"); +var https = require("https"); +var fs = require("fs"); +var path = require("path"); +// 配置文件路径 +var CONFIG_FILE = path.join(__dirname, 'config.json'); +// 存储当前的路由配置 +var MOCK_ROUTES = {}; +var CONFIG = { + cacheConfig: true, + reloadOnChange: true, + defaultContentType: "application/json", + proxyPort: 9443, + targetHost: "devrmtapp.resmart.cn" +}; +// 过滤掉以 # 开头的路由(视为注释,不参与 mock) +function filterActiveRoutes(routes) { + var filtered = {}; + for (var _i = 0, _a = Object.entries(routes); _i < _a.length; _i++) { + var _b = _a[_i], route = _b[0], filePath = _b[1]; + if (route.startsWith('#')) + continue; + filtered[route] = filePath; + } + return filtered; +} +// 加载配置文件 +function loadConfig() { + try { + if (!fs.existsSync(CONFIG_FILE)) { + console.warn("[CONFIG] \u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ".concat(CONFIG_FILE)); + console.warn("[CONFIG] \u6B63\u5728\u521B\u5EFA\u9ED8\u8BA4\u914D\u7F6E\u6587\u4EF6..."); + // 创建默认配置 + var defaultConfig = { + routes: { + "/api2/test": "mock/test.txt" + }, + config: { + cacheConfig: true, + reloadOnChange: true, + defaultContentType: "application/json", + proxyPort: 9443, + targetHost: "devrmtapp.resmart.cn" + } + }; + // 确保mock目录存在 + var 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] \u5DF2\u521B\u5EFA\u9ED8\u8BA4\u914D\u7F6E\u6587\u4EF6: ".concat(CONFIG_FILE)); + // 加载配置(# 开头的路由视为注释,不参与 mock) + var configData = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8')); + MOCK_ROUTES = filterActiveRoutes(configData.routes || {}); + CONFIG = configData.config || CONFIG; + } + else { + var configData = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8')); + MOCK_ROUTES = filterActiveRoutes(configData.routes || {}); + CONFIG = configData.config || CONFIG; + console.log("[CONFIG] \u914D\u7F6E\u6587\u4EF6\u5DF2\u52A0\u8F7D\uFF0C\u5171 ".concat(Object.keys(MOCK_ROUTES).length, " \u4E2A\u8DEF\u7531").concat(CONFIG.mockEnabled !== false ? '' : '(mock 已关闭,全部走代理)')); + } + // 验证mock文件是否存在 + validateMockFiles(); + } + catch (error) { + console.error("[CONFIG] \u52A0\u8F7D\u914D\u7F6E\u6587\u4EF6\u5931\u8D25:", 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] \u9A8C\u8BC1mock\u6587\u4EF6..."); + var missingFiles = []; + for (var _i = 0, _a = Object.entries(MOCK_ROUTES); _i < _a.length; _i++) { + var _b = _a[_i], route = _b[0], filePath = _b[1]; + var fullPath = path.join(__dirname, filePath); + if (!fs.existsSync(fullPath)) { + missingFiles.push({ route: route, filePath: filePath, fullPath: fullPath }); + console.warn("[CONFIG] \u8B66\u544A: mock\u6587\u4EF6\u4E0D\u5B58\u5728 - ".concat(filePath, " (\u7528\u4E8E\u8DEF\u7531: ").concat(route, ")")); + } + } + if (missingFiles.length > 0) { + console.log("[CONFIG] \u7F3A\u5C11 ".concat(missingFiles.length, " \u4E2Amock\u6587\u4EF6\uFF0C\u8BF7\u521B\u5EFA\u8FD9\u4E9B\u6587\u4EF6")); + } + else { + console.log("[CONFIG] \u6240\u6709mock\u6587\u4EF6\u9A8C\u8BC1\u901A\u8FC7"); + } +} +// 检查是否为mock路由的函数 +function isMockRoute(requestPath) { + if (CONFIG.mockEnabled === false) + return false; + return MOCK_ROUTES.hasOwnProperty(requestPath); +} +// 获取mock文件路径 +function getMockFilePath(requestPath) { + return MOCK_ROUTES[requestPath]; +} +// 代理服务器 +var proxyServer = http.createServer(function (clientReq, clientRes) { + // 解析客户端请求的 URL + var parsedUrl = new URL("http://localhost".concat(clientReq.url)); + var requestPath = parsedUrl.pathname; + // 检查是否为需要mock的路由 + if (isMockRoute(requestPath)) { + var mockFile = getMockFilePath(requestPath); + console.log("[MOCK] \u62E6\u622A\u8DEF\u7531: ".concat(requestPath, " -> \u4F7F\u7528\u6587\u4EF6: ").concat(mockFile)); + try { + // 构建完整的文件路径 + var mockFilePath = path.join(__dirname, mockFile); + // 检查文件是否存在 + if (!fs.existsSync(mockFilePath)) { + console.error("[MOCK] Mock\u6587\u4EF6\u4E0D\u5B58\u5728: ".concat(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文件 + var mockData = fs.readFileSync(mockFilePath, 'utf-8'); + // 设置响应头 + var 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] \u6210\u529F\u8FD4\u56DEmock\u6570\u636E: ".concat(mockFile)); + } + catch (error) { + console.error("[MOCK] \u8BFB\u53D6mock\u6587\u4EF6\u5931\u8D25: ".concat(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.message, + timestamp: new Date().toISOString() + })); + } + return; // 直接返回,不转发到目标服务器 + } + // 如果不是mock路由,正常代理转发 + console.log("[PROXY] \u8F6C\u53D1\u8BF7\u6C42: ".concat(requestPath)); + // 目标服务器的选项 + var options = { + hostname: CONFIG.targetHost, + port: 443, // HTTPS 默认端口 + method: clientReq.method, + path: parsedUrl.pathname + parsedUrl.search, + headers: __assign(__assign({}, clientReq.headers), { host: CONFIG.targetHost }) + }; + // 创建到目标服务器的 HTTPS 请求 + var proxyReq = https.request(options, function (proxyRes) { + // 将目标服务器的响应头复制到客户端响应 + clientRes.writeHead(proxyRes.statusCode, proxyRes.headers); + // 将目标服务器的响应数据管道传输到客户端 + proxyRes.pipe(clientRes); + }); + // 错误处理 + proxyReq.on('error', function (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', function (req, res) { + var parsedUrl = new URL("http://localhost".concat(req.url)); + var pathname = parsedUrl.pathname; + // 管理接口:重新加载配置 + if (pathname === '/__reload-config' && req.method === 'POST') { + try { + var oldCount = Object.keys(MOCK_ROUTES).length; + loadConfig(); + var 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] \u901A\u8FC7API\u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E (\u8DEF\u7531\u6570: ".concat(oldCount, " -> ").concat(newCount, ")")); + } + catch (error) { + res.writeHead(500, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + success: false, + error: 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.message + })); + } + } +}); +// 初始化:第一次加载配置 +loadConfig(); +// 如果配置了文件监视,则监听配置文件变化 +if (CONFIG.reloadOnChange) { + fs.watchFile(CONFIG_FILE, function (curr, prev) { + console.log("[CONFIG] \u914D\u7F6E\u6587\u4EF6\u5DF2\u4FEE\u6539\uFF0C\u91CD\u65B0\u52A0\u8F7D..."); + try { + var oldRoutesCount = Object.keys(MOCK_ROUTES).length; + loadConfig(); + var newRoutesCount = Object.keys(MOCK_ROUTES).length; + console.log("[CONFIG] \u914D\u7F6E\u91CD\u8F7D\u5B8C\u6210 (\u8DEF\u7531\u6570: ".concat(oldRoutesCount, " -> ").concat(newRoutesCount, ")")); + } + catch (error) { + console.error("[CONFIG] \u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E\u6587\u4EF6\u5931\u8D25:", error); + } + }); + console.log("[CONFIG] \u5DF2\u542F\u7528\u914D\u7F6E\u6587\u4EF6\u76D1\u89C6: ".concat(CONFIG_FILE)); +} +proxyServer.listen(CONFIG.proxyPort, "0.0.0.0", function () { + console.log("========================================"); + console.log("\u4EE3\u7406\u670D\u52A1\u5668\u8FD0\u884C\u5728: http://localhost:".concat(CONFIG.proxyPort)); + console.log("\u76EE\u6807\u670D\u52A1\u5668: https://".concat(CONFIG.targetHost)); + console.log("\u914D\u7F6E\u6587\u4EF6: ".concat(CONFIG_FILE)); + console.log("\u5DF2\u914D\u7F6EMock\u8DEF\u7531: ".concat(Object.keys(MOCK_ROUTES).length, " \u4E2A").concat(CONFIG.mockEnabled !== false ? '' : '(当前 mockEnabled=false,未生效)')); + console.log("========================================"); + console.log("\u7BA1\u7406\u63A5\u53E3:"); + console.log(" GET http://localhost:".concat(CONFIG.proxyPort, "/__config \u67E5\u770B\u5F53\u524D\u914D\u7F6E")); + console.log(" POST http://localhost:".concat(CONFIG.proxyPort, "/__reload-config \u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E")); + console.log("========================================"); + console.log("Mock\u8DEF\u7531\u5217\u8868:"); + for (var _i = 0, _a = Object.entries(MOCK_ROUTES); _i < _a.length; _i++) { + var _b = _a[_i], route = _b[0], file = _b[1]; + var filePath = path.join(__dirname, file); + var exists = fs.existsSync(filePath) ? '✓' : '✗'; + console.log(" ".concat(exists, " ").concat(route, " -> ").concat(file)); + } + console.log("========================================"); +}); diff --git a/index.api.ts b/index.api.ts index 4f5bde7..9876789 100644 --- a/index.api.ts +++ b/index.api.ts @@ -12,6 +12,8 @@ interface RouteConfig { } interface AppConfig { + /** 为 false 时所有请求走代理,不命中 mock 路由;缺省为 true */ + mockEnabled?: boolean; cacheConfig: boolean; reloadOnChange: boolean; defaultContentType: string; @@ -83,7 +85,7 @@ function loadConfig() { 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} 个路由`); + console.log(`[CONFIG] 配置文件已加载,共 ${Object.keys(MOCK_ROUTES).length} 个路由${CONFIG.mockEnabled !== false ? '' : '(mock 已关闭,全部走代理)'}`); } // 验证mock文件是否存在 @@ -127,6 +129,7 @@ function validateMockFiles() { // 检查是否为mock路由的函数 function isMockRoute(requestPath: string): boolean { + if (CONFIG.mockEnabled === false) return false; return MOCK_ROUTES.hasOwnProperty(requestPath); } @@ -305,7 +308,7 @@ proxyServer.listen(CONFIG.proxyPort, "0.0.0.0", () => { 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(`已配置Mock路由: ${Object.keys(MOCK_ROUTES).length} 个${CONFIG.mockEnabled !== false ? '' : '(当前 mockEnabled=false,未生效)'}`); console.log(`========================================`); console.log(`管理接口:`); console.log(` GET http://localhost:${CONFIG.proxyPort}/__config 查看当前配置`);