Files
lingxi/packages/shared/src/audit/audit-actions.ts
T
Stev_Wang 408dc5e81b feat(shared): A5 审计契约、动作目录与配置
新增审计动作目录(10 分类 / 54 动作)、前后值 diff 纯函数、列表 / 详情 / 导出 Zod 契约;system_configs 增加 audit 分组与保留期 / 导出上限 / 日志平台地址 3 项配置。
2026-11-04 16:26:03 +08:00

414 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 审计动作目录(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);
}