1
This commit is contained in:
@@ -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 };
|
||||
@@ -1,36 +0,0 @@
|
||||
const baseModel = require("../../middleware/baseModel");
|
||||
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 {
|
||||
await baseModel.biz_audit_log.create({
|
||||
admin_user_id: p.admin_user_id || null,
|
||||
biz_user_id: p.biz_user_id || null,
|
||||
action: p.action,
|
||||
resource_type: p.resource_type || "",
|
||||
resource_id: p.resource_id != null ? p.resource_id : null,
|
||||
detail: p.detail || null,
|
||||
});
|
||||
} catch (e) {
|
||||
logs.error("[biz_audit] 写入失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
function pickAdminId(ctx) {
|
||||
if (!ctx) return null;
|
||||
const u = ctx.user || ctx.state?.user || ctx.session?.user;
|
||||
if (u && (u.id != null || u.userId != null)) return u.id != null ? u.id : u.userId;
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = { logAudit, pickAdminId };
|
||||
@@ -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 };
|
||||
@@ -1,128 +0,0 @@
|
||||
/**
|
||||
* 列表筛选、写入字段裁剪、审计日志列表补列(供 admin 控制器直接配合 baseModel 使用)
|
||||
*/
|
||||
const Sequelize = require("sequelize");
|
||||
const { Op } = Sequelize;
|
||||
|
||||
function build_search_where(model, seach_option) {
|
||||
const key = seach_option && seach_option.key;
|
||||
const raw = seach_option && seach_option.value;
|
||||
if (!key || raw === undefined || raw === null) return {};
|
||||
const str = String(raw).trim();
|
||||
if (str === "") return {};
|
||||
|
||||
const attr = model.rawAttributes[key];
|
||||
if (!attr) {
|
||||
return { [key]: { [Op.like]: `%${str}%` } };
|
||||
}
|
||||
|
||||
const type_key = attr.type && attr.type.key;
|
||||
|
||||
if (type_key === "BOOLEAN") {
|
||||
if (str === "true" || str === "1" || str === "是") return { [key]: true };
|
||||
if (str === "false" || str === "0" || str === "否") return { [key]: false };
|
||||
return {};
|
||||
}
|
||||
|
||||
if (type_key === "ENUM") {
|
||||
return { [key]: str };
|
||||
}
|
||||
|
||||
if (
|
||||
type_key === "INTEGER" ||
|
||||
type_key === "BIGINT" ||
|
||||
type_key === "FLOAT" ||
|
||||
type_key === "DOUBLE" ||
|
||||
type_key === "DECIMAL"
|
||||
) {
|
||||
const n = Number(str);
|
||||
if (!Number.isNaN(n)) return { [key]: n };
|
||||
return {};
|
||||
}
|
||||
|
||||
if (type_key === "DATE" || type_key === "DATEONLY") {
|
||||
return { [key]: str };
|
||||
}
|
||||
|
||||
return { [key]: { [Op.like]: `%${str}%` } };
|
||||
}
|
||||
|
||||
function normalize_for_write(model, data, { for_create } = {}) {
|
||||
const attrs = model.rawAttributes;
|
||||
const out = {};
|
||||
for (const k of Object.keys(data || {})) {
|
||||
if (!attrs[k]) continue;
|
||||
let v = data[k];
|
||||
if (v === "") {
|
||||
if (k === "id" && for_create) continue;
|
||||
if (k.endsWith("_id") || k === "id") {
|
||||
v = null;
|
||||
} else if (attrs[k].allowNull) {
|
||||
v = null;
|
||||
}
|
||||
}
|
||||
if (k === "enabled_features" && typeof v === "string" && v.trim() !== "") {
|
||||
try {
|
||||
v = JSON.parse(v);
|
||||
} catch (e) {
|
||||
/* 保持原字符串 */
|
||||
}
|
||||
}
|
||||
out[k] = v;
|
||||
}
|
||||
if (for_create && out.id !== undefined && (out.id === "" || out.id === null)) {
|
||||
delete out.id;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function list_query_extra(model_name, model) {
|
||||
if (model_name === "biz_audit_log") {
|
||||
const tn = model.tableName;
|
||||
return {
|
||||
attributes: {
|
||||
include: [[model.sequelize.col(`${tn}.created_at`), "created_at"]],
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
async function find_page(model, model_name, body, extra_find_options = {}) {
|
||||
const param = body.param || body;
|
||||
const page_option = param.pageOption || {};
|
||||
const seach_option = param.seachOption || {};
|
||||
const page_num = parseInt(page_option.page, 10) || 1;
|
||||
const page_size = parseInt(page_option.pageSize, 10) || 20;
|
||||
const offset = (page_num - 1) * page_size;
|
||||
const where = build_search_where(model, seach_option);
|
||||
return model.findAndCountAll({
|
||||
where,
|
||||
offset,
|
||||
limit: page_size,
|
||||
order: [["id", "DESC"]],
|
||||
...list_query_extra(model_name, model),
|
||||
...extra_find_options,
|
||||
});
|
||||
}
|
||||
|
||||
async function find_for_export(model, model_name, body, extra_find_options = {}) {
|
||||
const param = body.param || body;
|
||||
const where = build_search_where(model, param.seachOption || {});
|
||||
const rows = await model.findAll({
|
||||
where,
|
||||
limit: 10000,
|
||||
order: [["id", "DESC"]],
|
||||
...list_query_extra(model_name, model),
|
||||
...extra_find_options,
|
||||
});
|
||||
return { rows };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
build_search_where,
|
||||
normalize_for_write,
|
||||
list_query_extra,
|
||||
find_page,
|
||||
find_for_export,
|
||||
};
|
||||
Reference in New Issue
Block a user