Asp-Net-Core开发笔记:使用AOP实现动态审计日志功能
前言#
最近一直在写 Go 和 Python ,好久没写 C# ,重新回来写 C# 代码时竟有一种亲切感~
说回正题。
在当今这个数字化迅速发展的时代,每一个操作都可能对业务产生深远的影响,无论是对数据的简单查询,还是对系统配置的修改。在这样的背景下,审计日志不仅仅是一种遵循最佳实践的手段,更是确保数据安全、提高系统透明度、促进责任归属明晰的关键工具。通过详细记录谁在何时对系统进行了何种操作,审计日志帮助组织追踪用户活动,分析系统问题,甚至在发生安全事件时,提供必要的线索进行调查。
实现审计日志的方法多样,但如何在不干扰主业务逻辑的同时,高效地集成这一功能,是开发者们面临的一大挑战。本文着重探讨如何借鉴面向切面编程(Aspect-Oriented Programming, AOP)的设计思想,在ASP.NET Core应用中以最小化代码侵入性实现动态审计日志功能。AOP允许我们通过预定义的模式,如日志记录、性能统计和安全控制,以声明的方式增强代码功能,而无需修改实际的业务逻辑代码。
本文将指导读者从概念的理解到具体的实施,再到最终的数据持久化处理,特别是如何利用MongoDB这一强大的NoSQL数据库来持久化审计日志数据。无论你是刚刚接触ASP.NET Core的新手,还是寻求为现有项目增加审计功能的资深开发者,本文都将提供从理论到实践的全面指导。通过本文,你将学习到如何设计和实现一个灵活、可扩展的审计日志系统,同时保持对主业务逻辑的最小化干扰。
让我们开始这一旅程,一步步探索如何在ASP.NET Core应用中集成高效、灵活的审计日志机制,利用AOP设计思想实现高度解耦和动态增强的系统功能。
审计日志基础#
定义和用途#
审计日志有助于追踪用户的操作行为、数据变更记录以及系统的安全性分析等。
常用的审计日志有这些类型。
- 操作审计:记录用户对系统的所有操作,例如登录、登出、数据增删改查等。
- 数据审计:记录数据的变更详情,如记录数据修改前后的值。
- 安全审计:记录安全相关事件,如失败的登录尝试、权限变更等。
- 性能审计:记录关键操作的性能数据,帮助分析系统瓶颈。
本文的代码以实现操作审计为例。
模型定义&关键信息#
审计日志是系统安全和管理的关键部分,它帮助我们理解系统内发生了什么、何时发生、由谁触发。为了实现这一目标,审计日志记录需要包含几个关键的组成部分。
- EventId 是每条审计记录的唯一标识符。就像每个人都有一个独一无二的身份证号一样,每条审计日志也有一个独特的EventId。这使我们能够轻松地找到和引用特定的审计事件。
- EventType 描述了发生的事件类型。这告诉我们这条记录是关于什么的——是用户登录、数据修改,还是权限更改等。通过查看EventType,我们可以快速了解记录的核心信息,而无需深入研究细节。
- UserId 是触发事件的用户的标识。在审计日志中记录UserId非常重要,因为它帮助我们追踪谁负责了什么操作。如果发现了问题或者不当行为,我们可以通过UserId来确定责任人。
设计审计日志模型#
AuditLog 类#
新建 AuditLog.cs
类,每个字段都有注释,我就不再赘述了。
public class AuditLog {
/// <summary>
/// 事件唯一标识
/// </summary>
public string EventId { get; set; }
/// <summary>
/// 事件类型(例如:登录、登出、数据修改等)
/// </summary>
public string EventType { get; set; }
/// <summary>
/// 执行操作的用户标识
/// </summary>
public string UserId { get; set; }
/// <summary>
/// 执行操作的用户名
/// </summary>
public string Username { get; set; }
/// <summary>
/// 事件发生的时间戳
/// </summary>
public DateTime Timestamp { get; set; }
/// <summary>
/// 用户的IP地址
/// </summary>
public string? IPAddress { get; set; }
/// <summary>
/// 被操作的实体名称
/// </summary>
public string EntityName { get; set; }
/// <summary>
/// 被操作的实体标识
/// </summary>
public string EntityId { get; set; }
/// <summary>
/// 修改前的数据,可根据实际情况以JSON格式存储
/// </summary>
public string? OriginalValues { get; set; }
/// <summary>
/// 修改后的数据,可根据实际情况以JSON格式存储
/// </summary>
public string? CurrentValues { get; set; }
/// <summary>
/// 具体的更改内容,可根据实际情况以JSON格式存储
/// </summary>
public string? Changes { get; set; }
/// <summary>
/// 事件描述
/// </summary>
public string? Description { get; set; }
}
捕获审计日志#
IAuditLogService 接口#
先写一个接口,用来操作审计日志。使用接口可以保持代码的整洁和重用,同时也便于将来对审计日志记录逻辑进行扩展或修改。
为了简单起见,目前这里我们只写了一个记录的方法。
public interface IAuditLogService {
Task LogAsync(AuditLog auditLog);
}
之后在依赖注入容器里注册(假设实现类的名称为 AuditLogService
)
builder.Services.AddScope<IAuditLogService, AuditLogService>();
这个设计既保持了代码的清晰与简洁,也为将来可能的需求变更(如改变审计日志的存储方式、增加审计字段等)提供了足够的灵活性。
具体实现会在后续的数据持久化部分介绍。
ActionFilter 方式#
在ASP.NET Core中,Action过滤器提供了一种强大的机制,允许我们在控制器的动作执行前后插入自定义逻辑。
我们可以在不修改现有业务逻辑代码的情况下,自动地捕获用户的操作以及数据的更改。这种方式充分利用了AOP的思想,实现了代码的最小化侵入。
创建 AuditLogAttribute
类#
直接上代码了,继承自 ActionFilterAttribute
类,可以实现一个 Action 过滤器的特性,其中 EventType
和 EntityName
我设计成需要手动指定,其他的属性可以通过各种方法来获取。
public class AuditLogAttribute : ActionFilterAttribute {
public string EventType { get; set; }
public string EntityName { get; set; }
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) {
var sp = context.HttpContext.RequestServices;
var ctxItems = context.HttpContext.Items;
try {
var authService = sp.GetRequiredService<AuthService>();
// 在操作执行前
var executedContext = await next();
// 在操作执行后
// 获取当前用户的身份信息
var user = await authService.GetUserFromJwt(executedContext.HttpContext.User);
// 构造AuditLog对象
var auditLog = new AuditLog {
EventId = Guid.NewGuid().ToString(),
EventType = this.EventType,
UserId = user.UserId,
Username = user.Username,
Timestamp = DateTime.UtcNow,
IPAddress = GetIpAddress(executedContext.HttpContext),
EntityName = this.EntityName,
EntityId = ctxItems["AuditLog_EntityId"]?.ToString() ?? "",
OriginalValues = ctxItems["AuditLog_OriginalValues"]?.ToString(),
CurrentValues = ctxItems["AuditLog_CurrentValues"]?.ToString(),
Changes = ctxItems["AuditLog_Changes"]?.ToString(),
Description = $"操作类型:{this.EventType},实体名称:{this.EntityName}",
};
var auditService = sp.GetRequiredService<IAuditLogService>();
await auditService.LogAsync(auditLog);
} catch (Exception ex) {
var logger = sp.GetRequiredService<ILogger<AuditLogAttribute>>();
logger.LogError(ex, "An error occurred while logging audit information.");
}
}
}
注意事项
- 异常处理:考虑到日志记录不应影响主要业务流程的执行,需要添加异常处理逻辑,确保即使日志记录过程中发生异常,也不会干扰到正常的业务逻辑。
- 性能问题:虽然已经在异步方法中记录审计日志,但如果审计日志的记录过程很慢,可能会略微延迟响应时间。可以使用批处理、缓存来异步写入数据库,或者将记录逻辑放到后台任务、消息队列中。
获取IP地址#
通过HttpContext.Connection.RemoteIpAddress
属性可以获取 IP 地址,但如果应用部署在了代理服务器后面(例如使用了负载均衡器),直接获取的IP地址可能是代理服务器的地址,而不是客户端的真实IP地址。
所以这里我封装了 GetIpAddress
方法
private string? GetIpAddress(HttpContext httpContext) {
// 首先检查X-Forwarded-For头(当应用部署在代理后面时)
var forwardedFor = httpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault();
if (!string.IsNullOrWhiteSpace(forwardedFor)) {
return forwardedFor.Split(',').FirstOrDefault(); // 可能包含多个IP地址
}
// 如果没有X-Forwarded-For头,或者需要直接获取连接的远程IP地址
return httpContext.Connection.RemoteIpAddress?.ToString();
}
首先尝试从X-Forwarded-For
请求头中获取IP地址,这是一个标准的HTTP头,用于识别通过HTTP代理或负载均衡器发送请求的客户端的原始IP地址。如果请求没有经过代理,或者想要获取代理服务器的地址,那么它会回退到使用HttpContext.Connection.RemoteIpAddress
。
X-Forwarded-For
可能包含多个IP地址(如果请求通过多个代理传递),因此代码中使用了Split(',')
来处理这种情况,并且仅取第一个IP地址作为客户端的真实IP地址。
使用方法#
经过封装后可以很方便的使用这个审计功能了,只需要在接口上添加一行代码就可以实现审计功能。
[AuditLog(EventType = nameof(SetSubTaskFeedback), EntityName = nameof(SubTask))]
[HttpPost("sub-tasks/{subId}/set-feedback")]
public async Task<ApiResponse> SetSubTaskFeedback(string subId, [FromBody] SubTaskFeedbackDto dto) {}
手动记录方式#
尽管使用Action过滤器是一种高效的自动化方式,但在某些情况下,需要更精细地控制审计日志的记录。这时候只能修改接口代码,在业务逻辑里加入审计日志记录。
这种方式虽然需要直接修改业务代码,但它提供了最大的灵活性和控制能力。
这个代码就没什么特别的了,直接在接口中调用 IAuditLogService
的 LogAsync
方法来记录审计日志即可。
通过 HttpContext 共享数据#
有些参数是很难在 ActionFilter 里自动获取到的,这些往往跟业务逻辑是有关的,这时候 HttpContext 就成为了一个理想的桥梁。
我们可以将一些临时数据,比如操作前的数据快照,存储在 HttpContext.Items
中,然后在过滤器中访问这些数据来完成审计日志的记录。这种方法不仅保持了代码的解耦,还允许我们灵活地在应用的不同部分共享数据。
HttpContext.Items
是一个键值对集合,可用于在一个请求的生命周期内共享数据。
这样在接口中的代码就是
HttpContext.Items["AuditLog_OriginalValues"] = item.FeedbackId;
HttpContext.Items["AuditLog_CurrentValues"] = dto.FeedbackId;
HttpContext.Items["AuditLog_Changes"] = $"更新反馈结果 {item.FeedbackId} -> {dto.FeedbackId}";
注意事项#
- 确保业务逻辑和
AuditLogAttribute
中使用的键(如AuditLog_OriginalValues
)唯一且一致,以避免潜在的冲突。这里最好是自己封装一个 class 来提供这些 const ; - 如果业务逻辑抽象到了 service 层,则需要注入
IHttpContextAccessor
才能访问 HttpContext ,这个服务可以通过services.AddHttpContextAccessor()
来注册;
日志持久化#
审计日志的有效持久化是确保长期安全和合规性的关键。
选择存储方案#
在选择最合适的存储方案时,需要考虑数据的重要性、查询的频率、成本以及维护的复杂性等多个因素。
关系型数据库(RDS)#
关系型数据库,如MySQL、PostgreSQL等,以其稳定性和成熟性受到广泛认可。它们提供了严格的数据完整性保障和复杂查询的强大能力,适合需要执行复杂分析和报告的审计日志。
- 优点:数据结构化、支持复杂查询、成熟的管理工具。
- 缺点:相对较高的成本、可能需要复杂的架构来支持大规模数据。
NoSQL数据库#
NoSQL数据库,如MongoDB、Cassandra等,提供了灵活的数据模型和良好的横向扩展能力,适合于结构多变或数据量巨大的审计日志。
- 优点:高可扩展性、灵活的数据模型、快速的写入速度。
- 缺点:查询功能相对有限、数据一致性模型较弱。
文件系统#
直接将审计日志写入文件系统是最直接的存储方式,适用于日志量不是特别大或对查询需求不高的场景。
- 优点:实现简单、成本低廉、易于迁移;
- 缺点:查询和分析不便、难以管理大量日志文件、扩展性有限。
每种存储方案都有其适用场景,因此选择哪一种方案应根据具体需求和资源情况综合考虑。对于需要快速写入和高度可扩展的审计日志系统,NoSQL数据库是一个不错的选择。
因此本文选择了 MongoDB 来记录日志。
选择MongoDB作为审计日志的存储方案,不仅因为它的高性能和可扩展性,还因为它支持灵活的文档数据模型,使得存储非结构化或半结构化的审计数据变得简单。
实现 AuditLogMongoService#
在 C# 中使用 MongoDB 非常简单。
需要先添加 MongoDB.Driver 的 nuget 包
dotnet add MongoDB.Driver
直接上代码吧,
public class AuditLogMongoService : IAuditLogService {
private readonly IMongoCollection<AuditLog> _auditLogs;
public AuditLogMongoService(string connectionString, string databaseName) {
var client = new MongoClient(connectionString);
var database = client.GetDatabase(databaseName);
_auditLogs = database.GetCollection<AuditLog>("audit_logs");
}
public async Task LogAsync(AuditLog auditLog) {
await _auditLogs.InsertOneAsync(auditLog);
}
}
准备连接字符串&注册服务#
为了避免硬编码,将连接字符串放在配置文件(appsettings.json
)里
"ConnectionStrings": {
"Redis": "redis:6379",
"MongoDB": "mongodb://username:password@path-to-mongo:27017"
}
注册服务
builder.Services.AddSingleton<IAuditLogService>(sp => new AuditLogMongoService(builder.Configuration.GetConnectionString("MongoDB"), "db_name"));
搞定~
部署 MongoDB#
附上 MongoDB 的部署方法吧,我这里使用 docker ,很方便
version: '3.1'
services:
mongo:
image: mongo:4.4.6
restart: always
volumes:
- ./data:/data/db
environment:
MONGO_INITDB_ROOT_USERNAME: username
MONGO_INITDB_ROOT_PASSWORD: password
ports:
- 27017:27017
mongo-express:
image: mongo-express
restart: always
environment:
ME_CONFIG_MONGODB_ADMINUSERNAME: username
ME_CONFIG_MONGODB_ADMINPASSWORD: password
ME_CONFIG_MONGODB_URL: mongodb://username:password@mongo:27017/
ports:
- 8081:8081
使用 docker-compose 来编排,映射了 27017 和 8081 端口
可以使用 8081 端口访问 mongo-express 网页服务
如何查看日志#
- 使用 MongoDB Compass 这个软件来查看数据
- 使用 mongo-express 服务可以在网页上查看数据
小结#
虽然是比较简单的功能,不过使用 AOP 来实现用起来感觉还是蛮爽的,不得不说 AspNetCore 的功能确实丰富~
2024-04-29 21:02:06【出处】:https://www.cnblogs.com/deali/p/18086834
=======================================================================================
Asp-Net-Core开发笔记:进一步实现非侵入性审计日志功能
前言#
上次说了利用 AOP 思想实现了审计日志功能,不过有同学反馈还是无法实现完全无侵入,于是我又重构了一版新的。
回顾一下:Asp-Net-Core开发笔记:实现动态审计日志功能
现在已经可以实现对业务代码完全无侵入的审计日志了,在需要审计的接口上加上 [AuditLog]
特性,就可以记录这个接口的操作日志,还有相关的实体变化记录,还算是方便。
PS:后面我发现 ABP 里自带审计功能,突然感觉有点🤡了
重构#
先对之前的代码进行重构,之前把跟审计有关的代码分散到各个目录中,这个功能其实是个整体,应该把代码归集到一起比较好。
创建 src/Acme.Demo/Contrib/Audit
目录 (注:Acme.Demo
是项目名称,随便起的)
目录结构#
目录结构如下
Audit
├─ Services
│ ├─ IAuditLogService.cs
│ ├─ AuditLogService.cs
│ └─ AuditLogMongoService.cs
├─ Middlewares
│ └─ AuditLogMiddleware.cs
├─ Filters
│ └─ AuditLogAttribute.cs
├─ Extensions
│ └─ CfgAudit.cs
├─ EventHandlers
│ └─ FreeSqlAuditEventHandler.cs
├─ Entities
│ ├─ EntityChangeInfo.cs
│ └─ AuditLog.cs
└─ AuditConstant.cs
6 directories, 10 files
创建 EntityChangeInfo
实体#
用来保存实体变化
public class EntityChangeInfo {
public string Entity { get; set; }
public string Action { get; set; }
public string Sql { get; set; }
public Dictionary<string, object?> Parameters { get; set; }
}
AuditLog
重构#
之前我们是把实体变化内容直接保存在 AuditLog
里
现在要分离开,使用 List<EntityChangeInfo>
类型的 EntityChanges
属性来存放实体变化
public class AuditLog {
/// <summary>
/// 事件唯一标识
/// </summary>
public string EventId { get; set; }
/// <summary>
/// 事件类型(例如:登录、登出、数据修改等)
/// </summary>
public string EventType { get; set; }
/// <summary>
/// 执行操作的用户标识
/// </summary>
public string UserId { get; set; }
/// <summary>
/// 执行操作的用户名
/// </summary>
public string Username { get; set; }
/// <summary>
/// 事件发生的时间戳
/// </summary>
public DateTime Timestamp { get; set; }
/// <summary>
/// 用户的IP地址
/// </summary>
public string? IPAddress { get; set; }
/// <summary>
/// 实体更改内容,可根据实际情况以JSON格式存储
/// </summary>
public List<EntityChangeInfo>? EntityChanges { get; set; } = new();
/// <summary>
/// 路由信息
/// </summary>
public Dictionary<string, object?> RouteData { get; set; }
/// <summary>
/// 事件描述
/// </summary>
public string? Description { get; set; }
/// <summary>
/// 额外信息 (考虑以 JSON 格式保存)
/// </summary>
public object? Extra { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
/// <summary>
/// 修改时间
/// </summary>
public DateTime ModifiedTime { get; set; } = DateTime.UtcNow;
}
过滤器重构#
修改 AuditLogAttribute
类
涉及到的改动不多,就是简化了参数,只需要传入 EventType
就行
其他的都会自动获取
实体变化部分,需要使用到 ORM 的功能,接下来会介绍
public class AuditLogAttribute : ActionFilterAttribute {
public string EventType { get; set; }
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) {
var sp = context.HttpContext.RequestServices;
var ctxItems = context.HttpContext.Items;
try {
var authService = sp.GetRequiredService<AuthService>();
// 在操作执行前
var executedContext = await next();
// 在操作执行后
// 获取当前用户的身份信息
var user = await authService.GetUserFromJwt(executedContext.HttpContext.User);
// 构造AuditLog对象
var auditLog = new AuditLog {
EventId = Guid.NewGuid().ToString(),
EventType = this.EventType,
UserId = user.UserId,
Username = user.Username,
Timestamp = DateTime.UtcNow,
IPAddress = GetIpAddress(executedContext.HttpContext),
Description = $"操作类型:{this.EventType}",
};
if (ctxItems.TryGetValue(AuditConstant.EntityChanges, out var item)) {
auditLog.EntityChanges = item as List<EntityChangeInfo>;
}
var routeData = new Dictionary<string, object?>();
foreach (var (key, value) in context.RouteData.Values) {
routeData.Add(key, value);
}
auditLog.RouteData = routeData;
var auditService = sp.GetRequiredService<IAuditLogService>();
await auditService.LogAsync(auditLog);
} catch (Exception ex) {
var logger = sp.GetRequiredService<ILogger<AuditLogAttribute>>();
logger.LogError(ex, "An error occurred while logging audit information.");
}
Console.WriteLine(
"执行 AuditLogAttribute, " +
$"EventId: {ctxItems["AuditLog_EventId"]}");
}
private string? GetIpAddress(HttpContext httpContext) {
// 首先检查X-Forwarded-For头(当应用部署在代理后面时)
var forwardedFor = httpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault();
if (!string.IsNullOrWhiteSpace(forwardedFor)) {
return forwardedFor.Split(',').FirstOrDefault(); // 可能包含多个IP地址
}
// 如果没有X-Forwarded-For头,或者需要直接获取连接的远程IP地址
return httpContext.Connection.RemoteIpAddress?.ToString();
}
}
获取实体变化#
实体变化部分,需要使用到 ORM 的功能,不同的 ORM 能实现的实体变化监控不太一样,需要每种 ORM 写一个
我目前只实现了 FreeSQL 的实体变化监控
代码在 FreeSqlAuditEventHandler
中
public class FreeSqlAuditEventHandler {
private readonly ILogger<FreeSqlAuditEventHandler> _logger;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IDictionary<object, object?> _ctxItems;
public FreeSqlAuditEventHandler(IHttpContextAccessor httpContextAccessor,
ILogger<FreeSqlAuditEventHandler> logger) {
_httpContextAccessor = httpContextAccessor;
_logger = logger;
_ctxItems = httpContextAccessor.HttpContext?.Items ?? new Dictionary<object, object?>();
}
public void HandleCurdBefore(object? sender, CurdBeforeEventArgs args) {
// 捕获变更信息
var changeInfo = new EntityChangeInfo {
Entity = args.EntityType.Name,
Action = Enum.GetName(typeof(CurdType), args.CurdType) ?? "unknown",
Sql = args.Sql,
Parameters = new Dictionary<string, object?>(
args.DbParms.Select(p => new KeyValuePair<string, object?>(p.ParameterName, p.Value))
)
};
// 处理CurdBefore事件,将实体变化信息保存到HttpContext.Items
_logger.LogDebug(
$"执行 FreeSql CurdBefore, " +
$"EventId: {_httpContextAccessor.HttpContext?.Items["AuditLog_EventId"]}, " +
$"entityType: {args.EntityType.Name}, " +
$"crud: {Enum.GetName(typeof(CurdType), args.CurdType)}, ");
List<EntityChangeInfo> changes = new();
if (_ctxItems.TryGetValue(AuditConstant.EntityChanges, out var item)) {
changes = item as List<EntityChangeInfo> ?? new List<EntityChangeInfo>();
} else {
_ctxItems[AuditConstant.EntityChanges] = changes;
}
changes.Add(changeInfo);
}
}
这里很简单,利用 FreeSQL 的 Aop.CurdBefore
事件,把 HandleCurdBefore
绑定到事件上,就可以获取实体的变化了。
// 创建 IFreeSQL 实例
IFreeSql inst = ...;
// 实体 CRUD操作(create read update delete)事件
inst.Aop.CurdBefore += auditEventHandler.HandleCurdBefore;
这里吐槽一下 FreeSQL 的命名,一般都叫 crud ,你却搞特殊变成 curd ……
不过为了用国产数据库,只能凑合用咯~
扩展方法#
为了使用方便
我把注册服务和中间件都放在扩展方法中,符合 AspNetCore 的开发习惯
public static class CfgAudit {
public static IServiceCollection AddAudit(this IServiceCollection services, IConfiguration conf) {
services.AddSingleton<IAuditLogService>(sp =>
new AuditLogMongoService(conf.GetConnectionString("MongoDB"), "stu_data_hub"));
services.AddSingleton<FreeSqlAuditEventHandler>();
return services;
}
public static IApplicationBuilder UseAudit(this IApplicationBuilder app) {
app.UseMiddleware<AuditLogMiddleware>();
return app;
}
}
在 Program.cs
里注册
// 注册服务
builder.Services.AddAudit(builder.Configuration);
// 添加中间件
app.UseAudit();
PS:这里把配置传进去有点蠢,其实我完全可以在 AddAudit
方法里通过依赖注入的方式来获取配置对象的,不过既然都这样写了,懒得改了。
使用效果#
来看下使用效果
首先在需要审计的接口上加上 [AuditLog]
特性
/// <summary>
/// 设置反馈结果
/// </summary>
[AuditLog(EventType = "设置反馈结果")]
[HttpPost("{taskId}/sub-tasks/{subId}/set-feedback")]
public async Task<ApiResponse> SetSubTaskFeedback(string taskId, string subId, [FromBody] SubTaskFeedbackDto dto) {}
之后在 MongoDB 里可以看到审计日志(数据已脱敏)
{
"_id": {
"$oid": "65ff019f6de4b7290e1da9e9"
},
"EventId": "eb81f052-ce84-4923-bf9e-57582e464992",
"EventType": "设置反馈结果",
"UserId": "eb81f052",
"Username": "用户名",
"Timestamp": {
"$date": "2024-03-23T16:21:49.697Z"
},
"IPAddress": "1.2.3.4",
"EntityChanges": [
{
"Entity": "实体名称",
"Action": "Select",
"Sql": "Select 语句已脱敏",
"Parameters": {}
},
{
"Entity": "实体名称",
"Action": "Update",
"Sql": "UPDATE entity set some_col=:p_0",
"Parameters": {
":p_0": 6
}
}
],
"RouteData": {
"area": "Market",
"action": "SetSubTaskFeedback",
"controller": "Task",
"taskId": "eb81f052",
"subId": "57582e464992"
},
"Description": "操作类型:设置反馈结果",
"Extra": null,
"CreatedTime": {
"$date": "2024-03-23T16:21:49.697Z"
},
"ModifiedTime": {
"$date": "2024-03-23T16:21:49.697Z"
}
}
可以看到 EntityChanges
字段包含了这次事件中的实体操作,也就是对数据库的操作,共有两个,一个是 select 查询,另一个是 update 修改数据库。
AuditLog
中间件#
最后说下这个 AuditLogMiddleware
代码很简单,就是在每个请求进来的时候,在 HttpContext.Items
里添加一个 AuditConstant.EventId
public class AuditLogMiddleware {
private readonly RequestDelegate _next;
public AuditLogMiddleware(RequestDelegate next) {
_next = next;
}
public async Task Invoke(HttpContext context) {
// 生成 EventId 并存储到 HttpContext.Items 中
context.Items[AuditConstant.EventId] = Guid.NewGuid().ToString();
await _next(context);
}
}
虽然写了这个中间件,不过后面并没有用上这个 EventId
这个本来是用来把实体更新和 Filter 关系起来的,不过后面发现用不上。
先留着吧,万一后面有用呢?
2024-04-29 21:00:06【出处】:https://www.cnblogs.com/deali/p/18165737
=======================================================================================
关注我】。(●'◡'●)
如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的【因为,我的写作热情也离不开您的肯定与支持,感谢您的阅读,我是【Jack_孟】!
本文来自博客园,作者:jack_Meng,转载请注明原文链接:https://www.cnblogs.com/mq0036/p/18166652
【免责声明】本文来自源于网络,如涉及版权或侵权问题,请及时联系我们,我们将第一时间删除或更改!