const axios = require("axios"); const baseModel = require("../../middleware/baseModel"); const config = require("../../config/config"); const logs = require("../../tool/logs_proxy"); const upstreamBaseUrl = config.upstream_api_url || "http://127.0.0.1:8888"; /** 写入日志用:序列化响应并截断,避免 TEXT 过大 */ const RESPONSE_BODY_MAX_LEN = 16000; function serialize_response_for_log(data) { if (data === undefined || data === null) return ""; let s; if (typeof data === "string") s = data; else { try { s = JSON.stringify(data); } catch (e) { s = String(data); } } if (s.length > RESPONSE_BODY_MAX_LEN) { return `${s.slice(0, RESPONSE_BODY_MAX_LEN)}…[truncated]`; } return s; } /** * 转发请求到上游并记录调用日志 * @param {object} params * @param {string} params.api_path - 接口路径,如 /user/GetProfile * @param {string} params.method - HTTP 方法 * @param {object} params.query - query 参数(透传) * @param {object} params.body - body 参数(透传) * @param {object} params.headers - 需要透传的请求头 * @param {object} params.auth_ctx - 鉴权上下文(verifyRequest 返回的 context) * @returns {object} { status, data, headers } */ async function forwardRequest({ api_path, method, query, body, headers, auth_ctx = {} }) { const url = `${upstreamBaseUrl}${api_path}`; const start = Date.now(); let status_code = 0; let resp_data = null; let resp_headers = {}; try { const forwardHeaders = {}; if (headers["content-type"]) forwardHeaders["content-type"] = headers["content-type"]; if (headers["user-agent"]) forwardHeaders["user-agent"] = headers["user-agent"]; const resp = await axios({ method: method.toLowerCase(), url, params: query, data: body, headers: forwardHeaders, timeout: 30000, validateStatus: () => true, }); status_code = resp.status; resp_data = resp.data; resp_headers = resp.headers; } catch (err) { status_code = 502; resp_data = { ok: false, error_code: "UPSTREAM_ERROR", message: err.message }; logs.error(`[proxy] 转发失败 ${api_path}`, err.message); } const response_time = Date.now() - start; // 异步写调用日志,不阻塞响应 writeCallLog({ user_id: auth_ctx.user_id, token_id: auth_ctx.token_id, api_path, http_method: method.toUpperCase(), status_code, response_time, response_body: serialize_response_for_log(resp_data), }).catch((e) => logs.error("[proxy] 写调用日志失败", e.message)); return { status: status_code, data: resp_data, headers: resp_headers }; } /** * 写入 API 调用日志 */ async function writeCallLog({ user_id, token_id, api_path, http_method, status_code, response_time, response_body, }) { try { const now = new Date(); const call_date = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`; await baseModel.biz_api_call_log.create({ user_id, token_id, api_path, http_method, status_code, response_time, response_body: response_body || null, call_date, created_at: now, }); } catch (e) { logs.error("[proxy] 写调用日志失败", e.message); } } module.exports = { forwardRequest };