如何将EF Core的SaveChangesInterceptor、CommandInterceptor与审计落地实现一招多用的拦截器实战?

摘要:审计不是“给表补几个 CreatedBy 字段”,也不是“在业务方法里顺手记日志”。它本质上是系统级可追溯能力,设计目标是让系统在任何写路径下都能稳定回答四个问题:谁发起、改了什么、何时发生、通过哪条链路触发。 真正的难点不在 API 用法
审计不是“给表补几个 CreatedBy 字段”,也不是“在业务方法里顺手记日志”。它本质上是系统级可追溯能力,设计目标是让系统在任何写路径下都能稳定回答四个问题:谁发起、改了什么、何时发生、通过哪条链路触发。 真正的难点不在 API 用法,而在系统设计阶段是否把审计定义成基础设施能力。这里聚焦两层落地:SaveChangesInterceptor 负责实体变更审计,CommandInterceptor 负责 SQL 执行审计,两者一起组成可观测、可追溯、可审计的最小闭环。 1. 问题背景:为什么审计必须在系统设计期落地 如果你刚开始做系统设计,通常会先把功能跑通,再逐步补监控、日志和审计。这条路径很正常,但系统从单一写入口演进到多入口后,审计会出现一些典型断层: HTTP 请求有 TraceId,但数据库变更记录无法关联到具体调用链路。 Web 接口有审计字段,批处理、后台任务、集成事件消费链路没有统一审计。 SQL 慢查询能看到语句本身,但看不到对应业务场景和调用来源。 合规追查时能找到结果,找不到完整过程和责任主体。 这些现象不是某个人“写错了”,而是系统设计阶段还没有建立统一审计模型: 还没先定义统一的审计契约(身份、时间、来源、关联链路)。 写入路径没有统一审计入口,不同调用通道口径自然会分化。 变更审计和 SQL 观测没有打通,排障时很难快速复原完整链路。 只有开发约定,没有系统级自动机制,随着迭代推进就容易出现漏记。 这篇文章要解决的核心问题不是“把审计代码写到哪一层”,而是“如何在系统层提供默认生效、可验证、可扩展的审计能力”,并且让这套能力能跟着系统规模一起演进。 2. 原理解析:先拆职责,再落地拦截器 如果你是第一次从系统设计角度做审计,最稳妥的方法是先做职责拆分,再做实现落地。这里按“三步法”来理解:先统一实体变更审计,再统一 SQL 观测入口,最后明确拦截器边界。 2.1 第一步:用 SaveChangesInterceptor 统一实体变更审计 SaveChangesInterceptor 适合处理实体状态相关规则,例如: Added 时补 CreatedAt/CreatedBy Modified 时补 UpdatedAt/UpdatedBy 软删除时把 Deleted 改写成 Modified + IsDeleted 这类逻辑和实体状态强相关,放在拦截器里能覆盖所有调用路径,避免每个 Service 重复写一遍。 2.2 第二步:用 CommandInterceptor 统一 SQL 观测入口 DbCommandInterceptor 适合做 SQL 级别的统一观测: 慢 SQL 记录 SQL 失败统一日志 参数快照和调用耗时 它不适合做业务决策,但非常适合做“排障入口统一化”。 2.3 第三步:守住边界,避免拦截器承载业务规则 拦截器是横切能力,不是业务规则容器。像“订单状态机是否允许跳转”这类业务校验,仍然应该放在领域或应用服务层。把业务规则塞进拦截器,后期只会让行为变得不可预测。
阅读全文