/** * 企业信息查询接口调试代理(零依赖,Node >= 14) * * 用途: * 1. 托管同目录下的 index.html(访问 http://localhost:5500) * 2. 将其余请求原样转发到后端 127.0.0.1:8081,并在响应上附加 CORS 头, * 规避浏览器跨域限制,方便前端页面直接联调。 * * 启动: node proxy.js * 自定义:PORT=5501 TARGET_PORT=9090 node proxy.js */ "use strict"; const http = require("http"); const https = require("https"); const fs = require("fs"); const path = require("path"); const PORT = Number(process.env.PORT) || 9527; const TARGET_HOST = process.env.TARGET_HOST || "127.0.0.1"; const TARGET_PORT = Number(process.env.TARGET_PORT) || 8081; const CX_HOST = process.env.CX_HOST || "api-cx.dingtax.cn"; const PUB_HOST = process.env.PUB_HOST || "apit-dsb.dingtax.cn"; const MIME = { ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".css": "text/css; charset=utf-8", ".json": "application/json; charset=utf-8", ".png": "image/png", ".svg": "image/svg+xml", ".ico": "image/x-icon" }; function corsHeaders() { return { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET,POST,PUT,DELETE,PATCH,OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With" }; } const server = http.createServer((req, res) => { const cors = corsHeaders(); if (req.method === "OPTIONS") { res.writeHead(204, cors); return res.end(); } // 静态文件:/ 及 /index.html,其余静态路径按扩展名尝试读取 const urlPath = decodeURIComponent((req.url || "/").split("?")[0]); if (req.method === "GET" && !urlPath.startsWith("/dsb/") && !urlPath.startsWith("/cx/") && !urlPath.startsWith("/pub/")) { let file = path.join(__dirname, urlPath === "/" ? "index.html" : urlPath); if (!file.startsWith(__dirname)) file = path.join(__dirname, "index.html"); fs.readFile(file, (err, buf) => { if (err) { res.writeHead(404, { ...cors, "Content-Type": "text/plain; charset=utf-8" }); return res.end("404 Not Found: " + urlPath); } res.writeHead(200, { ...cors, "Content-Type": MIME[path.extname(file)] || "application/octet-stream" }); res.end(buf); }); return; } // /cx/* → 公网 api-cx.dingtax.cn (HTTPS);/dsb/* → 本机后端 (HTTP) const isCx = urlPath.startsWith("/cx/"); const isPub = urlPath.startsWith("/pub/"); const qs = (req.url || "").split("?").slice(1).join("?"); // apit-dsb 后端会校验 Origin/Referer(Invalid CORS request / Rap csrf Verify),转发前剥离浏览器来源头 const fwdHeaders = { ...req.headers }; ["origin", "referer", "sec-fetch-site", "sec-fetch-mode", "sec-fetch-dest", "sec-fetch-user", "accept-language"].forEach(h => delete fwdHeaders[h]); const options = isCx ? { host: CX_HOST, port: 443, path: req.url, method: req.method, headers: { ...fwdHeaders, host: CX_HOST }, rejectUnauthorized: false } : isPub ? { host: PUB_HOST, port: 443, path: "/dsb" + urlPath.slice(4) + (qs ? "?" + qs : ""), method: req.method, headers: { ...fwdHeaders, host: PUB_HOST }, rejectUnauthorized: false } : { host: TARGET_HOST, port: TARGET_PORT, path: req.url, method: req.method, headers: { ...fwdHeaders, host: `${TARGET_HOST}:${TARGET_PORT}` } }; const upstream = (isCx || isPub ? https : http).request(options, (proxyRes) => { res.writeHead(proxyRes.statusCode || 502, { ...proxyRes.headers, ...cors }); proxyRes.pipe(res); }); upstream.on("error", (e) => { res.writeHead(502, { ...cors, "Content-Type": "application/json; charset=utf-8" }); res.end(JSON.stringify({ code: "-1", message: `代理转发失败: ${e.message}(后端 ${TARGET_HOST}:${TARGET_PORT} 不可达?)` })); }); req.pipe(upstream); }); server.listen(PORT, () => { console.log("企业信息接口调试代理已启动"); console.log(` 测试页面: http://localhost:${PORT}`); console.log(` 转发目标: http://${TARGET_HOST}:${TARGET_PORT}`); console.log(" 按 Ctrl+C 停止"); });