- build-flow.host → BuildFlow.Host(子目录也驼峰:Configuration/Endpoints/Hubs/Services) - build-flow.shared → BuildFlow.Shared - build-flow.slnx → BuildFlow.slnx - 同步所有 slnx/ProjectReference/run-dev.sh/架构测试 路径 - 删 macOS 大小写残留重复目录(旧 BuildFlow.Host/BuildFlow.Shared 空壳) - build 0/0
108 lines
4.3 KiB
C#
108 lines
4.3 KiB
C#
using Microsoft.EntityFrameworkCore;
|
||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||
using BuildFlow.Shared.Domain.Common;
|
||
|
||
namespace BuildFlow.Shared.Infrastructure;
|
||
|
||
/// <summary>
|
||
/// 审计拦截器,在 SaveChanges 时自动填充实体的审计字段(创建人/时间、修改人/时间、IP、软删除)。
|
||
/// 通过标记接口(IAuditable/ISoftDelete/IHasOperatorIP)判断实体需要哪些审计行为。
|
||
/// 注意:未实现 ISoftDelete 的实体在 Deleted 分支不触发,走真实物理删除。
|
||
/// </summary>
|
||
public class AuditInterceptor : SaveChangesInterceptor
|
||
{
|
||
private readonly ICurrentUserContext _currentUserContext;
|
||
|
||
public AuditInterceptor(ICurrentUserContext currentUserContext)
|
||
{
|
||
_currentUserContext = currentUserContext;
|
||
}
|
||
|
||
public override InterceptionResult<int> SavingChanges(
|
||
DbContextEventData eventData,
|
||
InterceptionResult<int> result)
|
||
{
|
||
ApplyAuditFields(eventData.Context);
|
||
return base.SavingChanges(eventData, result);
|
||
}
|
||
|
||
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
|
||
DbContextEventData eventData,
|
||
InterceptionResult<int> result,
|
||
CancellationToken cancellationToken = default)
|
||
{
|
||
ApplyAuditFields(eventData.Context);
|
||
return base.SavingChangesAsync(eventData, result, cancellationToken);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 遍历 ChangeTracker 中所有实体,根据状态和标记接口自动填充审计字段。
|
||
/// </summary>
|
||
private void ApplyAuditFields(DbContext? context)
|
||
{
|
||
if (context is null) return;
|
||
|
||
var userId = _currentUserContext.UserId;
|
||
var ipAddress = _currentUserContext.IPAddress;
|
||
var utcNow = DateTime.UtcNow;
|
||
|
||
foreach (var entry in context.ChangeTracker.Entries())
|
||
{
|
||
switch (entry.State)
|
||
{
|
||
case EntityState.Added:
|
||
if (entry.Entity is IAuditable auditableAdded)
|
||
{
|
||
auditableAdded.CreatedBy = userId;
|
||
auditableAdded.CreatedAt = utcNow;
|
||
// 新增时也填充 Updated 字段,便于"最后变更"查询
|
||
auditableAdded.UpdatedBy = userId;
|
||
auditableAdded.UpdatedAt = utcNow;
|
||
}
|
||
if (entry.Entity is IHasOperatorIP hasIpAdded)
|
||
{
|
||
hasIpAdded.OperatorIP = ipAddress;
|
||
}
|
||
break;
|
||
|
||
case EntityState.Modified:
|
||
if (entry.Entity is IAuditable auditableModified)
|
||
{
|
||
auditableModified.UpdatedBy = userId;
|
||
auditableModified.UpdatedAt = utcNow;
|
||
// 防止业务层误改创建信息,强制标记为未修改
|
||
entry.Property(nameof(IAuditable.CreatedBy)).IsModified = false;
|
||
entry.Property(nameof(IAuditable.CreatedAt)).IsModified = false;
|
||
}
|
||
if (entry.Entity is IHasOperatorIP hasIpModified)
|
||
{
|
||
hasIpModified.OperatorIP = ipAddress;
|
||
}
|
||
break;
|
||
|
||
case EntityState.Deleted:
|
||
// 软删除:将硬删除操作拦截,转为修改 IsDeleted = true
|
||
// 未实现 ISoftDelete 的实体(如 RefreshToken)仍走硬删除
|
||
if (entry.Entity is ISoftDelete softDeletable)
|
||
{
|
||
entry.State = EntityState.Modified;
|
||
softDeletable.IsDeleted = true;
|
||
|
||
if (entry.Entity is IAuditable auditableDeleted)
|
||
{
|
||
auditableDeleted.UpdatedBy = userId;
|
||
auditableDeleted.UpdatedAt = utcNow;
|
||
entry.Property(nameof(IAuditable.CreatedBy)).IsModified = false;
|
||
entry.Property(nameof(IAuditable.CreatedAt)).IsModified = false;
|
||
}
|
||
if (entry.Entity is IHasOperatorIP hasIpDeleted)
|
||
{
|
||
hasIpDeleted.OperatorIP = ipAddress;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|