diff --git a/config.json b/config.json index 5db879c..3ef7b12 100644 --- a/config.json +++ b/config.json @@ -1,6 +1,6 @@ { "routes": { - "/api/system/user/info": "mock/service_failed.txt" + "/api/system/user/info": "mock/user_info.txt" }, "config": { "mockEnabled": true, diff --git a/index.api.ts b/index.api.ts index 28d3357..e5c16ce 100644 --- a/index.api.ts +++ b/index.api.ts @@ -2,6 +2,7 @@ import * as http from "http"; import * as https from "https"; import * as fs from "fs"; import * as path from "path"; +import * as zlib from "zlib"; // 配置文件路径 const CONFIG_FILE = path.join(__dirname, "config.json"); @@ -160,6 +161,27 @@ function getMockFilePath(requestPath: string): string { return MOCK_ROUTES[requestPath]; } +function decodeBodyByEncoding( + bodyBuffer: Buffer, + contentEncoding?: string, +): Buffer { + const encoding = (contentEncoding || "").toLowerCase().trim(); + try { + if (encoding.includes("gzip")) { + return zlib.gunzipSync(bodyBuffer); + } + if (encoding.includes("br")) { + return zlib.brotliDecompressSync(bodyBuffer); + } + if (encoding.includes("deflate")) { + return zlib.inflateSync(bodyBuffer); + } + } catch (error) { + console.warn("[MOCK] 解压上游响应失败,按原始内容写入文件", error); + } + return bodyBuffer; +} + // 代理服务器 const proxyServer = http.createServer((clientReq, clientRes) => { // 解析客户端请求的 URL @@ -265,16 +287,75 @@ const proxyServer = http.createServer((clientReq, clientRes) => { // 检查文件是否存在 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(), - }), - ); + console.warn(`[MOCK] Mock文件不存在,回源并自动生成: ${mockFilePath}`); + + const targetPort = CONFIG.targetPort ?? 443; + const options: http.RequestOptions = { + hostname: CONFIG.targetHost, + port: targetPort, + method: clientReq.method, + path: parsedUrl.pathname + parsedUrl.search, + headers: { + ...clientReq.headers, + host: CONFIG.targetHost, + }, + }; + + const proxyReq = https.request(options, (proxyRes) => { + const chunks: Buffer[] = []; + proxyRes.on("data", (chunk: Buffer) => { + chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); + }); + + proxyRes.on("end", () => { + const bodyBuffer = Buffer.concat(chunks); + + try { + const decodedBuffer = decodeBodyByEncoding( + bodyBuffer, + Array.isArray(proxyRes.headers["content-encoding"]) + ? proxyRes.headers["content-encoding"][0] + : proxyRes.headers["content-encoding"], + ); + const contentType = String(proxyRes.headers["content-type"] || ""); + const toWrite = + contentType.includes("application/json") || + contentType.includes("text/") || + contentType.includes("application/xml") || + contentType.includes("application/javascript") + ? decodedBuffer.toString("utf-8") + : decodedBuffer; + fs.mkdirSync(path.dirname(mockFilePath), { recursive: true }); + fs.writeFileSync(mockFilePath, toWrite); + console.log(`[MOCK] 已自动写入mock文件: ${mockFilePath}`); + } catch (writeErr) { + console.error(`[MOCK] 自动写入mock文件失败: ${mockFilePath}`, writeErr); + } + + clientRes.writeHead(proxyRes.statusCode || 200, { + ...proxyRes.headers, + "X-Mock-Autogenerated": "true", + "X-Mock-Source": mockFile, + }); + clientRes.end(bodyBuffer); + }); + }); + + proxyReq.on("error", (err) => { + console.error("Proxy request error:", err); + clientRes.writeHead(500, { "Content-Type": "application/json" }); + clientRes.end( + JSON.stringify({ + error: "Proxy error", + route: requestPath, + file: mockFile, + message: err.message, + timestamp: new Date().toISOString(), + }), + ); + }); + + clientReq.pipe(proxyReq); return; } diff --git a/mock/user_info.txt b/mock/user_info.txt new file mode 100644 index 0000000..8ee1463 --- /dev/null +++ b/mock/user_info.txt @@ -0,0 +1 @@ +{"code":200,"msg":"","data":{"permissions":["quant:account:create","quant:strategy:create","quant:alarm:create","quant:agentServer:create","quant:form:create","quant:robotPosition:create","quant:robot:create","quant:robotOrder:create","quant:robotOrder:delete","quant:account:delete","quant:strategy:delete","quant:robot:delete","quant:agentServer:delete","quant:robotPosition:delete","quant:alarm:delete","quant:form:delete","quant:agentServer:update","quant:robotOrder:update","quant:robotPosition:update","quant:robot:update","quant:alarm:update","quant:account:update","system:file:upload","quant:strategy:update","quant:form:update","quant:robot:select","quant:agentServer:select","quant:account:select","system:file:delete","quant:strategy:select","quant:robotOrder:select","quant:robotPosition:select","quant:alarm:select","quant:form:select","quant:strategy:transfer","quant:robot:deploy","quant:form:design","system:file:select","quant:robot:run","system:file:category","quant:robot:notify"],"roles":["quant_vip"],"user":{"userId":1933359028013174785,"parentId":0,"inviteCode":"QfWk5SDx","username":"h01LxpmZfGzWu8dv5xdh","avatar":"","nickname":"Sandy","phone":"","birthday":"2025-07-07","email":"975303544@qq.com","sex":2,"deptId":null,"deptName":null,"postId":null,"postName":null,"creditScore":100,"score":0,"wallet":0.00,"registerDate":"2025-06-13 11:01:27"}}} \ No newline at end of file