数据安全与合规:审计日志与实体变更追踪
哈喽,我是黑棠
企业级系统里,最容易被低估、但又最经不起事故验证的能力之一,就是"事后还原现场":
- 谁在什么时间做了什么操作?
- 这个操作影响了哪些数据?字段从什么值变成了什么值?
- 这次变更来自哪个入口(API/后台任务/集成回调)?耗时与来源 IP 是什么?
这些信息既是合规审计的基本盘,也是排障和事故复盘的证据链。ABP 内置的审计体系把这件事做成了基础设施:你不用在每个接口里手动写日志,也能拿到统一、可查询的记录。
ABP 的 审计日志 (Audit Logging) 体系通常分两层:
- 接口调用日志:谁调了哪个 API,参数是什么,耗时多少,IP 是多少。
- 实体变更日志:某个实体被创建/修改/删除时,具体改了哪个字段,旧值是什么,新值是什么。
本篇会把它放回工程视角:不仅告诉你"能记录什么",还会强调"哪些不要记录"、"怎么控制成本"、"如何让日志可用"。

一、DDD 基础:聚合根与实体的生命周期
在讲审计日志之前,我们需要理解 DDD 中一个非常重要的概念:聚合根(Aggregate Root) 和 实体生命周期。
1. 聚合根:领域对象的"族长"
在 DDD 中,如果说聚合根是一组相关对象的"管理者",负责维护内部数据的一致性。
那么聚合根就更像一个家族的族长:
- Book(图书)是聚合根 → 族长
- Chapter(章节)是实体 → 族人
- 规则:外部只能通过 Book 来操作 Chapter,不能直接找 Chapter。
csharp
public class Book : AggregateRoot<Guid> // 我是族长
{
private List<Chapter> _chapters; // 族人们
public void AddChapter(Chapter chapter) // 外部必须通过我来加族人
{
// 我可以检查业务规则:比如章节数不能超过100
if (_chapters.Count >= 100)
throw new Exception("章节太多了!");
_chapters.Add(chapter);
}
}
核心区别
| 维度 | 实体(Entity) | 聚合根(Aggregate Root) |
|---|---|---|
| 定义 | 有唯一标识,可变的对象 | 特殊的实体,是聚合的入口 |
| 生命周期 | 可以独立存在,也可以属于聚合根 | 独立存在,管理内部实体的生命周期 |
| 访问规则 | 外部可以直接引用 | 外部只能通过聚合根访问内部实体 |
| 职责 | 承载业务逻辑 | 保证聚合内部数据一致性 |
2. 实体的生命周期管理
在 ABP 中,实体(Entity)和聚合根(Aggregate Root)有一套完整的基类继承体系,涵盖了从"简单实体"到"全功能聚合根"的各种需求。请根据业务场景按需选择:
| 基类 | 类型 | 审计能力 | 包含特性 | 适用场景 |
|---|---|---|---|---|
Entity<TKey> |
实体 | 无 | Id |
简单实体(非聚合根),无审计需求 |
CreationAuditedEntity<TKey> |
实体 | 仅创建 | Id + 创建人/时间 |
只记创建的简单实体(如关联表记录) |
AuditedEntity<TKey> |
实体 | 创建 + 修改 | 上述 + 修改人/时间 | 需要知道"谁改了数据"的简单实体 |
FullAuditedEntity<TKey> |
实体 | 完整 (含软删除) | 上述 + 软删除 (IsDeleted) | 需要"回收站"功能的简单实体 |
AggregateRoot<TKey> |
聚合根 | 无 | Id + 并发控制 + 领域事件 + 扩展属性 |
标准聚合根,无审计需求(如配置表) |
CreationAuditedAggregateRoot<TKey> |
聚合根 | 仅创建 | AggregateRoot + 创建人/时间 |
只记创建的聚合根(如流水日志) |
AuditedAggregateRoot<TKey> |
聚合根 | 创建 + 修改 | 上述 + 修改人/时间 | 标准业务聚合根 |
FullAuditedAggregateRoot<TKey> |
聚合根 | 完整 (含软删除) | 上述 + 软删除 (IsDeleted) | 需要软删除和完整审计的核心数据(如订单) |
为什么要有这么多基类?
因为不是所有数据都需要追踪!比如:
- 配置表 (如"支付方式"):不需要审计,用
AggregateRoot即可。 - 订单 :必须审计,用
FullAuditedAggregateRoot,合规要求。
3. 软删除:数据的"假死状态"
在 DDD 中,删除一个聚合根有两种方式:
硬删除(物理删除):
sql
DELETE FROM Books WHERE Id = 'xxx'; -- 数据真的没了
软删除(逻辑删除):
sql
UPDATE Books SET IsDeleted = 1 WHERE Id = 'xxx'; -- 数据还在,只是标记删除
为什么需要软删除?
- 合规要求:金融、医疗行业法律要求数据不能真删。
- 数据恢复:用户误删后可以找回(回收站功能)。
- 审计追踪:可以看到"谁在什么时候删除了什么"。
继承 FullAuditedAggregateRoot 后,ABP 会自动把 Delete 操作变成 Update,并自动过滤查询中的已删除数据。
二、审计日志 (Audit Log)
ABP 默认已经开启了审计日志。所有的应用服务(Application Service)方法调用都会被自动记录。

1. 默认行为
当你执行更新操作调用 BookAppService.UpdateAsync 时,ABP 会自动在数据库的 AbpAuditLogs 表中插入一条记录,包含:
- UserId: 当前操作用户
- ExecutionTime: 执行时间
- ExecutionDuration: 执行耗时
- ClientIpAddress: 客户端 IP
- BrowserInfo: 浏览器信息
- Url: 请求 URL
- HttpStatusCode : 响应状态码

2. 查看日志
如果没有 UI 界面,你也可以直接查询数据库表 AbpAuditLogs。

3. 你需要主动做的两件事
-
敏感信息治理
- 账号密码、验证码、Access Token、Refresh Token、银行卡号等,原则上不应进入审计日志。
- 只要接口参数里可能出现敏感字段,就要考虑"记录摘要"而不是"记录原文"。
反例:
csharp// [危险] 默认情况下,ABP 会序列化整个 input 参数记录到数据库 // 如果 input 包含 Password 字段,明文密码就会永久留在审计日志里 public async Task RegisterAsync(RegisterDto input) { // ... }规范:
使用
[DisableAuditing]特性标记敏感字段:csharppublic class RegisterDto { public string Username { get; set; } [DisableAuditing] // 审计日志会自动忽略此字段 public string Password { get; set; } } -
成本控制
- 审计数据是"会无限增长"的,必须有归档/清理策略(例如保留 90 天在线可查,历史落冷存储)。
- 高并发接口要评估记录粒度:并不是所有接口都需要记录完整参数。
三、实体变更追踪 (Entity Change Tracking)
接口日志只能看到"谁调了接口",但看不到"数据变成了什么样"。这时候就需要实体变更追踪。
如果不使用 ABP 的自动追踪,你可能需要在业务代码里写满这样的"牛皮癣":
csharp
// [治理前] 手动记录变更
public async Task UpdatePriceAsync(Guid id, float newPrice)
{
var book = await _repository.GetAsync(id);
var oldPrice = book.Price;
book.Price = newPrice;
// 手动拼接日志,不仅啰嗦,还容易漏字段
Logger.LogInformation($"User {CurrentUser.Id} changed Book {id} price from {oldPrice} to {newPrice}");
}
这种手动日志既难以维护,也无法支持统一的"历史回溯"查询。
1. 启用全量审计
要记录实体的具体字段变更,只需让实体继承 FullAuditedAggregateRoot 或 FullAuditedEntity。
- 修改文件 :
src/Acme.BookStore.Domain/Books/Book.cs
将AuditedAggregateRoot修改为FullAuditedAggregateRoot。
csharp
using Volo.Abp.Domain.Entities.Auditing;
namespace Acme.BookStore.Domain.Books;
// 修改继承基类
public class Book : FullAuditedAggregateRoot<Guid>
{
// ...
}
FullAuditedAggregateRoot 带来了什么?
它在 AuditedAggregateRoot(创建人/时间、修改人/时间)的基础上,增加了:
- IsDeleted: 软删除标记
- DeleterId: 删除人 ID
- DeletionTime: 删除时间
更重要的是,ABP 会自动监听它的变化,并将变更明细写入 AbpEntityChanges 和 AbpEntityPropertyChanges 表。
2. DTO 也需要更新吗?
为了在前端能看到删除信息(如果需要),DTO 也可以对应升级。
- 修改文件 :
src/Acme.BookStore.Application.Contracts/Books/BookDto.cs
csharp
using Volo.Abp.Application.Dtos;
namespace Acme.BookStore.Application.Contracts.Books;
// 修改继承基类
public class BookDto : FullAuditedEntityDto<Guid>
{
// ...
}
3. 数据迁移
由于我们引入了 FullAudited,实体增加了 IsDeleted 等字段,需要进行数据库迁移。
(在实际开发中,你需要运行 Add-Migration 和 Update-Database。但在本教程的模拟环境中,我们假设数据库已经是最新的。)
4. 审计追踪的边界建议
实体变更追踪最有价值的部分,通常是"核心业务字段"的变化。实践中建议明确边界:
- 核心交易/订单/权限类数据:字段级追踪几乎是必需品。
- 大字段(例如富文本、长 JSON、文件内容):谨慎开启字段级追踪,避免审计表被写爆。
- 频繁变化的技术字段(例如心跳时间、最后活跃时间):通常不需要字段级追踪,记录在业务日志或指标更合适。
四、实战:测试变更追踪
现在,当你更新一本书的价格时:
- 操作 :调用
BookAppService.UpdateAsync,将价格从 100 改为 200。 - 数据库记录 :
AbpAuditLogs: 记录了UpdateAsync方法被调用。AbpEntityChanges: 记录了Book实体发生了Update操作。AbpEntityPropertyChanges: 记录了Price字段,OriginalValue="100",NewValue="200"。
五、软删除 (Soft Delete)
继承 FullAuditedAggregateRoot 后,Book 实体自动获得了软删除能力。
1. 什么是软删除?
当你调用 Repository.DeleteAsync(book) 时,ABP 不会 执行 DELETE FROM Books,而是执行 UPDATE Books SET IsDeleted = 1。
2. 自动过滤
ABP 的仓储在查询时(GetListAsync 等),会自动加上 WHERE IsDeleted = 0。也就是说,被软删除的数据,默认是查不出来的。
3. 如何查询已删除的数据?
如果你是管理员,想查看回收站里的书,可以使用 IDataFilter 禁用过滤:
csharp
public class BookAppService : ...
{
private readonly IDataFilter _dataFilter;
public BookAppService(IDataFilter dataFilter, ...)
{
_dataFilter = dataFilter;
}
public async Task<List<BookDto>> GetDeletedBooksAsync()
{
// 临时禁用软删除过滤
using (_dataFilter.Disable<ISoftDelete>())
{
var books = await Repository.GetListAsync(x => x.IsDeleted);
return ObjectMapper.Map<List<Book>, List<BookDto>>(books);
}
}
}
六、小结
本篇我们学习了:
- DDD 聚合根:理解实体的生命周期管理和不同基类的选择。
- 审计日志:自动记录 API 调用详情。
- 实体变更追踪 :通过继承
FullAuditedAggregateRoot,自动记录字段级的变更历史。 - 软删除:数据"假删"机制及其过滤控制(DDD 中保护聚合根完整性的重要手段)。
安全与合规不是"上线前做一次",而是要变成系统的默认能力。把审计体系落到位,你就能在事故发生时更快定位根因,也能在合规检查时拿出可核验的证据链。
下一篇,我们将进入一个非常实用的功能------后台任务 (Background Jobs),看看如何处理发邮件、生成报表等耗时任务。
本文首发于CSDN:[黑棠会长],转载请注明来源。
关注我,一起用轻松的方式读懂前沿科技。