/** * 审计动作目录(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 = 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> = { // ---------------- 登录与会话 ---------------- '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 = 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); }