feat(shared): A5 审计契约、动作目录与配置
新增审计动作目录(10 分类 / 54 动作)、前后值 diff 纯函数、列表 / 详情 / 导出 Zod 契约;system_configs 增加 audit 分组与保留期 / 导出上限 / 日志平台地址 3 项配置。
This commit is contained in:
1 parent
a5a2914214
commit
408dc5e81b
9 files changed
+1211
No files matched your search
@@ -0,0 +1,413 @@
|
||||
/**
|
||||
* 审计动作目录(A5-BE-02 / 工程规范 08 LOG-12)
|
||||
*
|
||||
* - `AUDIT_CATEGORIES`:审计分类(前端筛选与色彩映射),共 9 类业务分类 + 1 类兜底;
|
||||
* - `AUDIT_ACTION_CATALOG`:动作 code → 中文名 / 分类 / 默认 targetType / 是否敏感;
|
||||
* - 动作中文名与分类**只在本文件维护**,`audit_logs` 表只存 code,避免字典漂移。
|
||||
*
|
||||
* 约束:新增 `@Audit('...')` 必须同步登记本目录,否则会走兜底(label = code),
|
||||
* `audit-actions.spec.ts` 会扫描源码断言一致性。
|
||||
*/
|
||||
|
||||
export const AUDIT_CATEGORIES = [
|
||||
{ key: 'auth', label: '登录与会话' },
|
||||
{ key: 'permission', label: '权限与角色' },
|
||||
{ key: 'client', label: '客户与行业' },
|
||||
{ key: 'catalog', label: '字典' },
|
||||
{ key: 'config', label: '系统配置' },
|
||||
{ key: 'export', label: '导出' },
|
||||
{ key: 'task', label: '任务' },
|
||||
{ key: 'asset', label: '资产' },
|
||||
{ key: 'sensitive', label: '敏感访问' },
|
||||
/** 未知动作的兜底分类(新增动作未登记时使用,避免前端筛选出现空分类) */
|
||||
{ key: 'other', label: '其他' },
|
||||
] as const;
|
||||
|
||||
export type AuditCategoryKey = (typeof AUDIT_CATEGORIES)[number]['key'];
|
||||
|
||||
export const AUDIT_CATEGORY_KEYS = AUDIT_CATEGORIES.map((item) => item.key) as [
|
||||
AuditCategoryKey,
|
||||
...AuditCategoryKey[],
|
||||
];
|
||||
|
||||
const AUDIT_CATEGORY_LABEL_MAP: ReadonlyMap<string, string> = new Map(
|
||||
AUDIT_CATEGORIES.map((item) => [item.key, item.label]),
|
||||
);
|
||||
|
||||
export function auditCategoryLabel(category: string): string {
|
||||
return AUDIT_CATEGORY_LABEL_MAP.get(category) ?? category;
|
||||
}
|
||||
|
||||
/** 统一对象类型白名单(target_type),前端按此渲染对象列;未登记类型只做文本展示 */
|
||||
export const AUDIT_TARGET_TYPES = [
|
||||
'user',
|
||||
'department',
|
||||
'role',
|
||||
'customer',
|
||||
'industry',
|
||||
'customer_group',
|
||||
'customer_field',
|
||||
'dict_type',
|
||||
'dict_item',
|
||||
'system_config',
|
||||
'audit_log',
|
||||
'audit_export',
|
||||
/** B1 / A6 预留 */
|
||||
'asset',
|
||||
'task',
|
||||
] as const;
|
||||
|
||||
export type AuditTargetType = (typeof AUDIT_TARGET_TYPES)[number];
|
||||
|
||||
export interface AuditActionMeta {
|
||||
code: string;
|
||||
label: string;
|
||||
category: AuditCategoryKey;
|
||||
/** 未显式声明时由拦截器按请求参数 / Service patchAudit 决定 */
|
||||
targetType?: AuditTargetType;
|
||||
/** 敏感访问(查看成本 / 资产包 / 审计等),需额外留痕与前端提示 */
|
||||
sensitive?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 动作目录:覆盖 A1–A4 已落地的 36 个 `@Audit(...)` code,
|
||||
* 并登记 A5 新增(登录 / 登出 / 审计查看 / 审计导出 / 导出下载)与后续批次预留项。
|
||||
*/
|
||||
export const AUDIT_ACTION_CATALOG: Readonly<Record<string, AuditActionMeta>> = {
|
||||
// ---------------- 登录与会话 ----------------
|
||||
'identity.auth.login': {
|
||||
code: 'identity.auth.login',
|
||||
label: '登录',
|
||||
category: 'auth',
|
||||
targetType: 'user',
|
||||
sensitive: true,
|
||||
},
|
||||
'identity.auth.logout': {
|
||||
code: 'identity.auth.logout',
|
||||
label: '登出',
|
||||
category: 'auth',
|
||||
targetType: 'user',
|
||||
},
|
||||
'identity.auth.change-password': {
|
||||
code: 'identity.auth.change-password',
|
||||
label: '修改密码',
|
||||
category: 'auth',
|
||||
targetType: 'user',
|
||||
sensitive: true,
|
||||
},
|
||||
|
||||
// ---------------- 权限与角色 / 组织 ----------------
|
||||
'identity.role.create': {
|
||||
code: 'identity.role.create',
|
||||
label: '新建角色',
|
||||
category: 'permission',
|
||||
targetType: 'role',
|
||||
},
|
||||
'identity.role.update': {
|
||||
code: 'identity.role.update',
|
||||
label: '编辑角色',
|
||||
category: 'permission',
|
||||
targetType: 'role',
|
||||
},
|
||||
'identity.role.delete': {
|
||||
code: 'identity.role.delete',
|
||||
label: '删除角色',
|
||||
category: 'permission',
|
||||
targetType: 'role',
|
||||
},
|
||||
'identity.user.create': {
|
||||
code: 'identity.user.create',
|
||||
label: '新建用户',
|
||||
category: 'permission',
|
||||
targetType: 'user',
|
||||
},
|
||||
'identity.user.update': {
|
||||
code: 'identity.user.update',
|
||||
label: '编辑用户',
|
||||
category: 'permission',
|
||||
targetType: 'user',
|
||||
},
|
||||
'identity.user.disable': {
|
||||
code: 'identity.user.disable',
|
||||
label: '停用用户',
|
||||
category: 'permission',
|
||||
targetType: 'user',
|
||||
},
|
||||
'identity.user.enable': {
|
||||
code: 'identity.user.enable',
|
||||
label: '启用用户',
|
||||
category: 'permission',
|
||||
targetType: 'user',
|
||||
},
|
||||
'identity.user.transfer_customers': {
|
||||
code: 'identity.user.transfer_customers',
|
||||
label: '转交客户',
|
||||
category: 'client',
|
||||
targetType: 'customer',
|
||||
},
|
||||
'identity.department.create': {
|
||||
code: 'identity.department.create',
|
||||
label: '新建部门',
|
||||
category: 'permission',
|
||||
targetType: 'department',
|
||||
},
|
||||
'identity.department.update': {
|
||||
code: 'identity.department.update',
|
||||
label: '编辑部门',
|
||||
category: 'permission',
|
||||
targetType: 'department',
|
||||
},
|
||||
'identity.department.disable': {
|
||||
code: 'identity.department.disable',
|
||||
label: '停用部门',
|
||||
category: 'permission',
|
||||
targetType: 'department',
|
||||
},
|
||||
'identity.department.enable': {
|
||||
code: 'identity.department.enable',
|
||||
label: '启用部门',
|
||||
category: 'permission',
|
||||
targetType: 'department',
|
||||
},
|
||||
|
||||
// ---------------- 客户与行业 ----------------
|
||||
'client.customer.create': {
|
||||
code: 'client.customer.create',
|
||||
label: '新建客户',
|
||||
category: 'client',
|
||||
targetType: 'customer',
|
||||
},
|
||||
'client.customer.update': {
|
||||
code: 'client.customer.update',
|
||||
label: '编辑客户',
|
||||
category: 'client',
|
||||
targetType: 'customer',
|
||||
},
|
||||
'client.customer.status': {
|
||||
code: 'client.customer.status',
|
||||
label: '变更客户状态',
|
||||
category: 'client',
|
||||
targetType: 'customer',
|
||||
},
|
||||
'client.customer.transfer': {
|
||||
code: 'client.customer.transfer',
|
||||
label: '转交客户负责人',
|
||||
category: 'client',
|
||||
targetType: 'customer',
|
||||
},
|
||||
'client.customer.groups': {
|
||||
code: 'client.customer.groups',
|
||||
label: '调整客户组归属',
|
||||
category: 'client',
|
||||
targetType: 'customer',
|
||||
},
|
||||
'client.customer.apply_industry_pack': {
|
||||
code: 'client.customer.apply_industry_pack',
|
||||
label: '套用行业资产包',
|
||||
category: 'client',
|
||||
targetType: 'customer',
|
||||
},
|
||||
'client.customer_field.apply': {
|
||||
code: 'client.customer_field.apply',
|
||||
label: '申请客户字段',
|
||||
category: 'client',
|
||||
targetType: 'customer_field',
|
||||
},
|
||||
'client.customer_field.approve': {
|
||||
code: 'client.customer_field.approve',
|
||||
label: '审批客户字段',
|
||||
category: 'client',
|
||||
targetType: 'customer_field',
|
||||
sensitive: true,
|
||||
},
|
||||
'client.industry.create': {
|
||||
code: 'client.industry.create',
|
||||
label: '新建行业',
|
||||
category: 'client',
|
||||
targetType: 'industry',
|
||||
},
|
||||
'client.industry.update': {
|
||||
code: 'client.industry.update',
|
||||
label: '编辑行业',
|
||||
category: 'client',
|
||||
targetType: 'industry',
|
||||
},
|
||||
'client.industry.status': {
|
||||
code: 'client.industry.status',
|
||||
label: '变更行业状态',
|
||||
category: 'client',
|
||||
targetType: 'industry',
|
||||
},
|
||||
'client.industry.delete': {
|
||||
code: 'client.industry.delete',
|
||||
label: '删除行业',
|
||||
category: 'client',
|
||||
targetType: 'industry',
|
||||
},
|
||||
'client.customer_group.create': {
|
||||
code: 'client.customer_group.create',
|
||||
label: '新建客户组',
|
||||
category: 'client',
|
||||
targetType: 'customer_group',
|
||||
},
|
||||
'client.customer_group.update': {
|
||||
code: 'client.customer_group.update',
|
||||
label: '编辑客户组',
|
||||
category: 'client',
|
||||
targetType: 'customer_group',
|
||||
},
|
||||
'client.customer_group.delete': {
|
||||
code: 'client.customer_group.delete',
|
||||
label: '删除客户组',
|
||||
category: 'client',
|
||||
targetType: 'customer_group',
|
||||
},
|
||||
'client.customer_group.members': {
|
||||
code: 'client.customer_group.members',
|
||||
label: '调整客户组成员',
|
||||
category: 'client',
|
||||
targetType: 'customer_group',
|
||||
},
|
||||
|
||||
// ---------------- 字典 ----------------
|
||||
'catalog.dict_type.create': {
|
||||
code: 'catalog.dict_type.create',
|
||||
label: '新建字典类型',
|
||||
category: 'catalog',
|
||||
targetType: 'dict_type',
|
||||
},
|
||||
'catalog.dict_type.update': {
|
||||
code: 'catalog.dict_type.update',
|
||||
label: '编辑字典类型',
|
||||
category: 'catalog',
|
||||
targetType: 'dict_type',
|
||||
},
|
||||
'catalog.dict_type.status': {
|
||||
code: 'catalog.dict_type.status',
|
||||
label: '变更字典类型状态',
|
||||
category: 'catalog',
|
||||
targetType: 'dict_type',
|
||||
},
|
||||
'catalog.dict_type.delete': {
|
||||
code: 'catalog.dict_type.delete',
|
||||
label: '删除字典类型',
|
||||
category: 'catalog',
|
||||
targetType: 'dict_type',
|
||||
},
|
||||
'catalog.dict_item.create': {
|
||||
code: 'catalog.dict_item.create',
|
||||
label: '新建字典条目',
|
||||
category: 'catalog',
|
||||
targetType: 'dict_item',
|
||||
},
|
||||
'catalog.dict_item.update': {
|
||||
code: 'catalog.dict_item.update',
|
||||
label: '编辑字典条目',
|
||||
category: 'catalog',
|
||||
targetType: 'dict_item',
|
||||
},
|
||||
|
||||
// ---------------- 系统配置 ----------------
|
||||
'platform.system_config.update': {
|
||||
code: 'platform.system_config.update',
|
||||
label: '保存系统配置',
|
||||
category: 'config',
|
||||
targetType: 'system_config',
|
||||
},
|
||||
|
||||
// ---------------- 审计与导出(A5) ----------------
|
||||
'audit.log.view': {
|
||||
code: 'audit.log.view',
|
||||
label: '查看审计日志',
|
||||
category: 'sensitive',
|
||||
targetType: 'audit_log',
|
||||
sensitive: true,
|
||||
},
|
||||
'audit.export': {
|
||||
code: 'audit.export',
|
||||
label: '导出审计日志',
|
||||
category: 'export',
|
||||
targetType: 'audit_export',
|
||||
},
|
||||
'audit.export.download': {
|
||||
code: 'audit.export.download',
|
||||
label: '下载审计导出文件',
|
||||
category: 'export',
|
||||
targetType: 'audit_export',
|
||||
},
|
||||
|
||||
// ---------------- 后续批次预留(B1 资产 / A6 任务) ----------------
|
||||
'asset.create': {
|
||||
code: 'asset.create',
|
||||
label: '上传资产',
|
||||
category: 'asset',
|
||||
targetType: 'asset',
|
||||
},
|
||||
'asset.update': {
|
||||
code: 'asset.update',
|
||||
label: '编辑资产',
|
||||
category: 'asset',
|
||||
targetType: 'asset',
|
||||
},
|
||||
'asset.disable': {
|
||||
code: 'asset.disable',
|
||||
label: '停用资产',
|
||||
category: 'asset',
|
||||
targetType: 'asset',
|
||||
},
|
||||
'asset.delete': {
|
||||
code: 'asset.delete',
|
||||
label: '删除资产',
|
||||
category: 'asset',
|
||||
targetType: 'asset',
|
||||
},
|
||||
'task.submit': { code: 'task.submit', label: '提交任务', category: 'task', targetType: 'task' },
|
||||
'task.cancel': { code: 'task.cancel', label: '取消任务', category: 'task', targetType: 'task' },
|
||||
'task.retry': { code: 'task.retry', label: '重试任务', category: 'task', targetType: 'task' },
|
||||
'task.finished': {
|
||||
code: 'task.finished',
|
||||
label: '任务完成',
|
||||
category: 'task',
|
||||
targetType: 'task',
|
||||
},
|
||||
'task.failed': { code: 'task.failed', label: '任务失败', category: 'task', targetType: 'task' },
|
||||
|
||||
// ---------------- 敏感访问(A5 预留,成本 / 资产包接口就绪后接入) ----------------
|
||||
'sensitive.cost.view': {
|
||||
code: 'sensitive.cost.view',
|
||||
label: '查看成本数据',
|
||||
category: 'sensitive',
|
||||
sensitive: true,
|
||||
},
|
||||
'sensitive.asset_pack.view': {
|
||||
code: 'sensitive.asset_pack.view',
|
||||
label: '查看客户资产包',
|
||||
category: 'sensitive',
|
||||
sensitive: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const AUDIT_ACTION_CODES: readonly string[] = Object.keys(AUDIT_ACTION_CATALOG);
|
||||
|
||||
const AUDIT_ACTION_META_MAP: ReadonlyMap<string, AuditActionMeta> = new Map(
|
||||
Object.entries(AUDIT_ACTION_CATALOG),
|
||||
);
|
||||
|
||||
/**
|
||||
* 取动作元数据;未登记 code 兜底:label 用 code 本身、分类 `other`。
|
||||
* 兜底不抛错,保证新增动作忘登记时审计写入不被阻塞。
|
||||
*/
|
||||
export function auditActionMeta(code: string): AuditActionMeta {
|
||||
return (
|
||||
AUDIT_ACTION_META_MAP.get(code) ?? {
|
||||
code,
|
||||
label: code,
|
||||
category: 'other',
|
||||
sensitive: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function isKnownAuditAction(code: string): boolean {
|
||||
return AUDIT_ACTION_META_MAP.has(code);
|
||||
}
|
||||
Reference in new issue
Block a user