AuditLogger
3.0.0 页面说明
- 3.0.0 说明:当前页已重写为
3.x的审计日志主题页。- 当前组件入口: Aegis.Security.Audit。
AuditLogger 用来记录业务操作留痕。它最适合放在 Service 层,把关键动作沉淀成统一的审计记录。
推荐接入路径
第一步:启用组件
{
"Components": {
"Services": [
"Security.Audit"
],
"Middlewares": []
}
}
第二步:注入 AuditLogger<T>
public class SettlementService
{
private readonly AuditLogger<SettlementService> _auditLogger;
public SettlementService(AuditLogger<SettlementService> auditLogger)
{
_auditLogger = auditLogger;
}
}
最常见的两种写法
方式一:每次显式指定操作者
await _auditLogger
.SetOperator("1", OperatorType.医生, "张三")
.SetDomain(Domain.结算单)
.SetContent(OperationType.创建, "SETTLE_001", "创建结算单", "创建结算单 SETTLE_001")
.LogAsync();
适合场景:
- 操作者信息每次都不同
- 控制器里临时记一笔审计
方式二:先 PreSetOperator(...)
public class SettlementService
{
private readonly AuditLogger<SettlementService> _auditLogger;
public SettlementService(AuditLogger<SettlementService> auditLogger)
{
_auditLogger = auditLogger;
_auditLogger.PreSetOperator("1", OperatorType.医生, "张三");
}
public async Task CreateAsync()
{
await _auditLogger
.SetDomain(Domain.结算单)
.SetContent(OperationType.创建, "SETTLE_001", "创建结算单", "创建结算单 SETTLE_001")
.LogAsync();
}
}
适合场景:
- Service 中的操作者通常就是当前登录用户
- 希望减少重复代码
什么时候要带资源标识
如果你希望后续能直接按资源查审计记录,建议用带 operationResourceId 的重载。
await _auditLogger
.SetOperator("1", OperatorType.医生, "张三")
.SetDomain(Domain.结算单)
.SetContent(OperationType.更新, "SETTLE_001", "更新结算单", "修改结算单金额")
.LogAsync();
什么时候要带变更前后数据
对于“修改类”操作,更推荐补上 SetChangeData(...)。
await _auditLogger
.SetOperator("1", OperatorType.医生, "张三")
.SetDomain(Domain.结算单)
.SetContent(OperationType.更新, "SETTLE_001", "更新结算单", "修改结算单金额")
.SetChangeData("金额,状态", newData, oldData)
.LogAsync();
正式项目通常还要做什么
默认情况下,组件会把 IAuditStorage 指到控制台存储。
推荐方案:引入 Aegis.Audit.FreeSql NuGet 包,它会自动注册基于数据库的审计存储和分页查询服务,无需手动编写存储代码。详见 安全审计文档的 FreeSql 章节。
如果你需要将审计数据写入消息队列、Elasticsearch 等其他介质,可以自己实现 IAuditStorage。
services.AddScoped<IAuditStorage, AuditDatabaseStorage>();
关于来源系统标识(SourceSystem)
每条审计记录都会自动携带 SourceSystem 字段,标识该记录来自哪个系统。填充优先级如下:
Aegis:AppName配置(推荐)- 当前项目程序集名称(
Assembly.GetEntryAssembly().GetName().Name,自动 fallback) "Unknown"(兜底值)
{
"Aegis": {
"AppName": "MyService"
}
}
- 多系统审计:如果多个系统共用同一个审计数据库,务必为每个系统配置不同的
AppName - 查询时筛选:可通过
IAuditQueryService的SourceSystem查询条件筛选特定系统的记录 - 无需手动赋值:
AuditLogger在构建审计记录时会自动填充此字段 - 未配置 AppName:如果未配置
Aegis:AppName,系统自动使用当前项目的程序集名称(即.csproj文件名),无需额外配置
关于客户端 IP(ClientIp)
每条审计记录都会自动携带 ClientIp 字段,记录发起本次操作的客户端 IP。无需手动赋值。
填充来源是 ActionContext.Current.IpAddress,由 ContextInspector 中间件在每个 HTTP 请求时自动采集:
- 优先取
X-Forwarded-For第一个 IP(最外层代理看到的客户端真实 IP) - 无 XFF 时回退到
RemoteIpAddress(直连场景) - 自动清理 IPv4-mapped IPv6 前缀
::ffff:
也就是说,只要项目启用了 ContextInspector 中间件,所有审计日志都会自动带上正确的客户端 IP,业务代码无需任何改动。
手动覆盖:WithIp(...)
后台任务、消息消费等非 HTTP 上下文场景下,ActionContext.Current 为 null,ClientIp 会是 null。如果这类场景需要标记来源,可以用 WithIp(...) 手动覆盖:
await _auditLogger
.SetOperator("system", OperatorType.系统, "定时任务")
.SetDomain(Domain.结算单)
.SetContent(OperationType.对账, "批量对账完成")
.WithIp("background-job")
.LogAsync();
WithIp(...) 也可用于 HTTP 场景下强制覆盖自动读取的值(一般不需要)。
关于操作时间(OperationTime)
每条审计记录都会自动携带 OperationTime 字段,默认为构建审计记录时的当前时间(DateTime.Now),绝大多数场景无需手动指定。
手动覆盖:WithOperationTime(...)
补录历史操作、异步任务补审计、批量导入等场景下,操作实际发生时间早于记录时间,可用 WithOperationTime(...) 指定真实发生时间:
// 补录昨天的一笔结算操作
await _auditLogger
.SetOperator("1", OperatorType.医生, "张三")
.SetDomain(Domain.结算单)
.SetContent(OperationType.创建, "SETTLE_001", "创建结算单", "补录昨日结算")
.WithOperationTime(yesterday)
.LogAsync();
常见问题
为什么 SetDomain(...) 直接报错
一般是因为你用了预设操作者模式,但没有先调用 PreSetOperator(...)。
审计日志应该记在 Controller 还是 Service
更推荐记在 Service。
这样更贴近业务动作,也更容易长期维护。
默认存储能不能直接用于生产
不建议。
正式环境推荐引入 Aegis.Audit.FreeSql 包实现数据库持久化,或自己实现 IAuditStorage。