操作审计(Audit Log)
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
export const auditDict: Record<
|
||||
string,
|
||||
{ label: string; actionText: string; targetLabel: string }
|
||||
> = {
|
||||
AE_CLOSED: {
|
||||
label: "关闭 AE(不良事件)",
|
||||
actionText: "关闭了 AE",
|
||||
targetLabel: "不良事件",
|
||||
},
|
||||
SUBJECT_STATUS_CHANGED: {
|
||||
label: "受试者状态变更",
|
||||
actionText: "变更了受试者状态",
|
||||
targetLabel: "受试者",
|
||||
},
|
||||
FINANCE_STATUS_CHANGED: {
|
||||
label: "费用状态变更",
|
||||
actionText: "变更了费用状态",
|
||||
targetLabel: "费用记录",
|
||||
},
|
||||
ADMIN_RESET_PASSWORD: {
|
||||
label: "重置用户密码",
|
||||
actionText: "重置了用户密码",
|
||||
targetLabel: "用户账号",
|
||||
},
|
||||
TASK_STATUS_CHANGED: {
|
||||
label: "任务状态变更",
|
||||
actionText: "更新了任务状态",
|
||||
targetLabel: "任务",
|
||||
},
|
||||
ISSUE_STATUS_CHANGED: {
|
||||
label: "风险/问题状态变更",
|
||||
actionText: "更新了风险/问题状态",
|
||||
targetLabel: "风险/问题",
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
import { auditDict } from "./auditDict";
|
||||
import { roleDict, getDictLabel, statusDict } from "../dictionaries";
|
||||
|
||||
export interface AuditEvent {
|
||||
eventType: string;
|
||||
eventLabel: string;
|
||||
actorId: string;
|
||||
actorName: string;
|
||||
actorRole: string;
|
||||
actorRoleLabel: string;
|
||||
targetType: string;
|
||||
targetTypeLabel: string;
|
||||
targetId: string;
|
||||
targetName?: string;
|
||||
actionText: string;
|
||||
before?: Record<string, any>;
|
||||
after?: Record<string, any>;
|
||||
diffText?: string[];
|
||||
result: "SUCCESS" | "FAIL";
|
||||
resultLabel: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
const buildDiffText = (before?: Record<string, any>, after?: Record<string, any>) => {
|
||||
if (!before || !after) return [];
|
||||
const lines: string[] = [];
|
||||
Object.keys({ ...before, ...after }).forEach((key) => {
|
||||
const prev = before[key];
|
||||
const next = after[key];
|
||||
if (prev === next) return;
|
||||
if (key.toLowerCase().includes("status")) {
|
||||
lines.push(`状态:${getDictLabel(statusDict, prev)} → ${getDictLabel(statusDict, next)}`);
|
||||
} else {
|
||||
lines.push(`${key}:${prev ?? "-"} → ${next ?? "-"}`);
|
||||
}
|
||||
});
|
||||
return lines;
|
||||
};
|
||||
|
||||
export const normalizeAuditEvent = (raw: any, userMap: Record<string, string>): AuditEvent => {
|
||||
const dict = auditDict[raw.action] || {
|
||||
label: "系统操作",
|
||||
actionText: "执行了操作",
|
||||
targetLabel: raw.entity_type || "对象",
|
||||
};
|
||||
let detailObj: any = {};
|
||||
if (raw.detail) {
|
||||
try {
|
||||
detailObj = JSON.parse(raw.detail);
|
||||
} catch {
|
||||
detailObj = { description: raw.detail };
|
||||
}
|
||||
}
|
||||
const before = detailObj.before;
|
||||
const after = detailObj.after;
|
||||
const diffText = detailObj.diffText || buildDiffText(before, after);
|
||||
return {
|
||||
eventType: raw.action,
|
||||
eventLabel: dict.label,
|
||||
actorId: raw.operator_id,
|
||||
actorName: userMap[raw.operator_id] || raw.operator_id,
|
||||
actorRole: raw.operator_role,
|
||||
actorRoleLabel: getDictLabel(roleDict, raw.operator_role),
|
||||
targetType: raw.entity_type || "",
|
||||
targetTypeLabel: dict.targetLabel,
|
||||
targetId: raw.entity_id || "",
|
||||
targetName: detailObj.targetName,
|
||||
actionText: dict.actionText,
|
||||
before,
|
||||
after,
|
||||
diffText: Array.isArray(diffText) ? diffText : diffText ? [diffText] : [],
|
||||
result: detailObj.result || "SUCCESS",
|
||||
resultLabel: detailObj.result === "FAIL" ? "失败" : "成功",
|
||||
timestamp: raw.created_at,
|
||||
};
|
||||
};
|
||||
|
||||
export { auditDict };
|
||||
|
||||
// 前端占位的 logAudit:不修改后端逻辑,失败不影响主流程
|
||||
export const logAudit = async (_eventType: string, _payload: Record<string, any>) => Promise.resolve();
|
||||
Reference in New Issue
Block a user