This commit is contained in:
张成
2026-04-01 13:05:27 +08:00
parent 6f61287c70
commit e9fd55666f
18 changed files with 9301 additions and 251 deletions

View File

@@ -1,9 +1,21 @@
const stats = require("../service/biz_api_stats_service");
const Sequelize = require("sequelize");
const { Op } = Sequelize;
const baseModel = require("../../middleware/baseModel");
const { find_page } = require("../service/biz_query_helpers");
const { find_page } = require("../utils/query_helpers");
function build_date_where(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;
}
module.exports = {
/** 按用户查询调用统计 */
"POST /biz_api_stats/by_user": async (ctx) => {
const body = ctx.getBody();
const { user_id, start_date, end_date } = body;
@@ -11,11 +23,22 @@ module.exports = {
ctx.fail("缺少 user_id");
return;
}
const data = await stats.getStatsByUser(user_id, start_date, end_date);
ctx.success(data);
const where = { user_id, ...build_date_where(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);
ctx.success({ total, rows });
},
/** 按接口路径查询调用统计 */
"POST /biz_api_stats/by_api": async (ctx) => {
const body = ctx.getBody();
const { api_path, start_date, end_date } = body;
@@ -23,19 +46,65 @@ module.exports = {
ctx.fail("缺少 api_path");
return;
}
const data = await stats.getStatsByApi(api_path, start_date, end_date);
ctx.success(data);
const where = { api_path, ...build_date_where(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);
ctx.success({ total, rows });
},
/** 综合统计面板 */
"POST /biz_api_stats/summary": async (ctx) => {
const body = ctx.getBody();
const { start_date, end_date, top_limit } = body;
const data = await stats.getSummary(start_date, end_date, top_limit || 10);
ctx.success(data);
const dateWhere = build_date_where(start_date, end_date);
const Seq = baseModel.Sequelize;
const lim = top_limit || 10;
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,
});
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: lim,
raw: true,
});
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: lim,
raw: true,
});
ctx.success({
total_calls: totalResult,
daily_trend,
top_apis,
top_users,
});
},
/** 调用日志分页列表 */
"POST /biz_api_call_log/page": async (ctx) => {
const body = ctx.getBody();
const { count, rows } = await find_page(baseModel.biz_api_call_log, "biz_api_call_log", body);

View File

@@ -1,5 +1,5 @@
const baseModel = require("../../middleware/baseModel");
const { find_page, find_for_export } = require("../service/biz_query_helpers");
const { find_page, find_for_export } = require("../utils/query_helpers");
module.exports = {
"POST /biz_audit_log/page": async (ctx) => {

View File

@@ -1,8 +1,48 @@
const dashboard = require("../service/biz_dashboard_service");
const Sequelize = require("sequelize");
const { Op } = Sequelize;
const baseModel = require("../../middleware/baseModel");
module.exports = {
"GET /biz_dashboard/summary": async (ctx) => {
const data = await dashboard.summary();
ctx.success(data);
const now = new Date();
const in7 = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
const [
userTotal,
userActive,
planActive,
subPending,
subActive,
subExpired,
tokenActive,
renewSoon,
] = await Promise.all([
baseModel.biz_user.count(),
baseModel.biz_user.count({ where: { status: "active" } }),
baseModel.biz_plan.count({ where: { status: "active" } }),
baseModel.biz_subscription.count({ where: { status: "pending" } }),
baseModel.biz_subscription.count({ where: { status: "active" } }),
baseModel.biz_subscription.count({ where: { status: "expired" } }),
baseModel.biz_api_token.count({ where: { status: "active" } }),
baseModel.biz_subscription.count({
where: {
status: "active",
end_time: { [Op.between]: [now, in7] },
},
}),
]);
ctx.success({
users: { total: userTotal, active: userActive },
plans: { active: planActive },
subscriptions: {
pending: subPending,
active: subActive,
expired: subExpired,
renew_within_7d: renewSoon,
},
tokens: { active: tokenActive },
server_time: now.toISOString(),
});
},
};

View File

@@ -1,5 +1,5 @@
const logic = require("../service/biz_subscription_logic");
const audit = require("../service/biz_audit_service");
const audit = require("../utils/biz_audit");

View File

@@ -1,6 +1,6 @@
const baseModel = require("../../middleware/baseModel");
const { find_page, find_for_export, normalize_for_write } = require("../service/biz_query_helpers");
const audit = require("../service/biz_audit_service");
const { find_page, find_for_export, normalize_for_write } = require("../utils/query_helpers");
const audit = require("../utils/biz_audit");
module.exports = {
"POST /biz_plan/page": async (ctx) => {

View File

@@ -1,7 +1,7 @@
const baseModel = require("../../middleware/baseModel");
const { find_page, find_for_export } = require("../service/biz_query_helpers");
const { find_page, find_for_export } = require("../utils/query_helpers");
const logic = require("../service/biz_subscription_logic");
const audit = require("../service/biz_audit_service");
const audit = require("../utils/biz_audit");
module.exports = {
"POST /biz_subscription/page": async (ctx) => {

View File

@@ -1,7 +1,7 @@
const baseModel = require("../../middleware/baseModel");
const { find_page, find_for_export } = require("../service/biz_query_helpers");
const { find_page, find_for_export } = require("../utils/query_helpers");
const tokenLogic = require("../service/biz_token_logic");
const audit = require("../service/biz_audit_service");
const audit = require("../utils/biz_audit");
module.exports = {
"POST /biz_token/page": async (ctx) => {

View File

@@ -1,5 +1,5 @@
const baseModel = require("../../middleware/baseModel");
const { find_page, find_for_export, normalize_for_write } = require("../service/biz_query_helpers");
const { find_page, find_for_export, normalize_for_write } = require("../utils/query_helpers");
module.exports = {
"POST /biz_usage/page": async (ctx) => {

View File

@@ -1,8 +1,8 @@
const Sequelize = require("sequelize");
const { find_for_export, normalize_for_write, build_search_where } = require("../service/biz_query_helpers");
const { find_for_export, normalize_for_write, build_search_where } = require("../utils/query_helpers");
const baseModel = require("../../middleware/baseModel");
const tokenLogic = require("../service/biz_token_logic");
const audit = require("../service/biz_audit_service");
const audit = require("../utils/biz_audit");
module.exports = {
"POST /biz_user/page": async (ctx) => {

View File

@@ -1,126 +0,0 @@
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

@@ -1,47 +0,0 @@
const baseModel = require("../../middleware/baseModel");
const { op } = baseModel;
async function summary() {
const now = new Date();
const in7 = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
const [
userTotal,
userActive,
planActive,
subPending,
subActive,
subExpired,
tokenActive,
renewSoon,
] = await Promise.all([
baseModel.biz_user.count(),
baseModel.biz_user.count({ where: { status: "active" } }),
baseModel.biz_plan.count({ where: { status: "active" } }),
baseModel.biz_subscription.count({ where: { status: "pending" } }),
baseModel.biz_subscription.count({ where: { status: "active" } }),
baseModel.biz_subscription.count({ where: { status: "expired" } }),
baseModel.biz_api_token.count({ where: { status: "active" } }),
baseModel.biz_subscription.count({
where: {
status: "active",
end_time: { [op.between]: [now, in7] },
},
}),
]);
return {
users: { total: userTotal, active: userActive },
plans: { active: planActive },
subscriptions: {
pending: subPending,
active: subActive,
expired: subExpired,
renew_within_7d: renewSoon,
},
tokens: { active: tokenActive },
server_time: now.toISOString(),
};
}
module.exports = { summary };

View File

@@ -3,13 +3,6 @@ const logs = require("../../tool/logs_proxy");
/**
* 记录审计失败不影响主流程
* @param {object} p
* @param {number} [p.admin_user_id]
* @param {number} [p.biz_user_id]
* @param {string} p.action
* @param {string} [p.resource_type]
* @param {number} [p.resource_id]
* @param {object} [p.detail]
*/
async function logAudit(p) {
try {

View File

@@ -1,5 +1,5 @@
/**
* 列表筛选写入字段裁剪审计日志列表补列 admin 控制器直接配合 baseModel 使用
* 管理端列表筛选导出写入字段裁剪 controller_admin 配合 baseModel
*/
const Sequelize = require("sequelize");
const { Op } = Sequelize;