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

29
api/utils/biz_audit.js Normal file
View File

@@ -0,0 +1,29 @@
const baseModel = require("../../middleware/baseModel");
const logs = require("../../tool/logs_proxy");
/**
* 记录审计(失败不影响主流程)
*/
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 };

128
api/utils/query_helpers.js Normal file
View File

@@ -0,0 +1,128 @@
/**
* 管理端列表筛选、导出、写入字段裁剪(供 controller_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,
};