This commit is contained in:
张成
2026-03-27 13:14:10 +08:00
parent 42907d0f21
commit 2f04459492
10 changed files with 465 additions and 1 deletions

View File

@@ -0,0 +1,126 @@
const baseModel = require("../../middleware/baseModel");
const { op } = baseModel;
/**
* 构建日期范围 where 条件
*/
function buildDateWhere(start_date, end_date) {
const where = {};
if (start_date && end_date) {
where.call_date = { [op.between]: [start_date, end_date] };
} else if (start_date) {
where.call_date = { [op.gte]: start_date };
} else if (end_date) {
where.call_date = { [op.lte]: end_date };
}
return where;
}
/**
* 按用户统计调用量(按接口路径分组)
* @param {number} user_id
* @param {string} [start_date] - YYYY-MM-DD
* @param {string} [end_date] - YYYY-MM-DD
*/
async function getStatsByUser(user_id, start_date, end_date) {
const where = { user_id, ...buildDateWhere(start_date, end_date) };
const rows = await baseModel.biz_api_call_log.findAll({
attributes: [
"api_path",
[baseModel.Sequelize.fn("COUNT", baseModel.Sequelize.col("id")), "call_count"],
[baseModel.Sequelize.fn("AVG", baseModel.Sequelize.col("response_time")), "avg_response_time"],
],
where,
group: ["api_path"],
order: [[baseModel.Sequelize.literal("call_count"), "DESC"]],
raw: true,
});
const total = rows.reduce((s, r) => s + Number(r.call_count), 0);
return { total, rows };
}
/**
* 按接口路径统计调用量(按用户分组)
* @param {string} api_path
* @param {string} [start_date]
* @param {string} [end_date]
*/
async function getStatsByApi(api_path, start_date, end_date) {
const where = { api_path, ...buildDateWhere(start_date, end_date) };
const rows = await baseModel.biz_api_call_log.findAll({
attributes: [
"user_id",
[baseModel.Sequelize.fn("COUNT", baseModel.Sequelize.col("id")), "call_count"],
[baseModel.Sequelize.fn("AVG", baseModel.Sequelize.col("response_time")), "avg_response_time"],
],
where,
group: ["user_id"],
order: [[baseModel.Sequelize.literal("call_count"), "DESC"]],
raw: true,
});
const total = rows.reduce((s, r) => s + Number(r.call_count), 0);
return { total, rows };
}
/**
* 综合统计面板总调用量、按天趋势、Top 接口、Top 用户
* @param {string} [start_date]
* @param {string} [end_date]
* @param {number} [top_limit=10]
*/
async function getSummary(start_date, end_date, top_limit = 10) {
const dateWhere = buildDateWhere(start_date, end_date);
const Seq = baseModel.Sequelize;
// 总调用量
const totalResult = await baseModel.biz_api_call_log.count({ where: dateWhere });
// 按天趋势
const daily_trend = await baseModel.biz_api_call_log.findAll({
attributes: [
"call_date",
[Seq.fn("COUNT", Seq.col("id")), "call_count"],
],
where: dateWhere,
group: ["call_date"],
order: [["call_date", "ASC"]],
raw: true,
});
// Top 接口
const top_apis = await baseModel.biz_api_call_log.findAll({
attributes: [
"api_path",
[Seq.fn("COUNT", Seq.col("id")), "call_count"],
],
where: dateWhere,
group: ["api_path"],
order: [[Seq.literal("call_count"), "DESC"]],
limit: top_limit,
raw: true,
});
// Top 用户
const top_users = await baseModel.biz_api_call_log.findAll({
attributes: [
"user_id",
[Seq.fn("COUNT", Seq.col("id")), "call_count"],
],
where: dateWhere,
group: ["user_id"],
order: [[Seq.literal("call_count"), "DESC"]],
limit: top_limit,
raw: true,
});
return {
total_calls: totalResult,
daily_trend,
top_apis,
top_users,
};
}
module.exports = { getStatsByUser, getStatsByApi, getSummary };

View File

@@ -0,0 +1,83 @@
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";
/**
* 转发请求到上游并记录调用日志
* @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,
}).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 }) {
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,
call_date,
created_at: now,
});
}
module.exports = { forwardRequest };