乐于分享
好东西不私藏

ABP 审计、后台作业、缓存源码解读:三个基础设施的设计

ABP 审计、后台作业、缓存源码解读:三个基础设施的设计

1. 审计系统的两层设计

实体级审计

全在实体基类中,不需要额外的接口或配置。选对了基类就自动拥有对应的审计字段。

// FullAuditedAggregateRoot.cs
public
 abstract class FullAuditedAggregateRoot<TKey> : AuditedAggregateRoot<TKey>, IFullAuditedObject
{
    public
 virtual bool IsDeleted { get; set; }
    public
 virtual Guid? DeleterId { get; set; }
    public
 virtual DateTime? DeletionTime { get; set; }
}

操作级审计

通过 IAuditingEnabled 标记接口 + Castle DynamicProxy 拦截器实现。

AuditingInterceptor 在方法进入时记录入参,方法结束时记录执行结果(成功/失败+耗时)。

// AuditingInterceptor 的核心逻辑:
1.
 检查方法/类是否有 [Audited] 或 [DisableAuditing]
2.
 记录调用时间、方法名、参数
3.
 执行业务方法
4.
 记录执行耗时、异常(如果有)
5.
 将审计日志保存到 AuditLog 实体

通过 AbpAuditingOptions 控制审计行为:

Configure<AbpAuditingOptions>(options =>
{
    options.IsEnabled = true;                          // 总开关
    options.HideErrors = true;                         // 审计失败不中断业务
    options.IgnoredTypes.Add(typeof(HealthCheckController));  // 忽略健康检查
});

2. 后台作业

public interface IBackgroundJobManager
{
Task<string> EnqueueAsync<TArgs>(
        TArgs args,
        BackgroundJobPriority priority = BackgroundJobPriority.Normal,
        TimeSpan? delay = null    // 延迟执行
)
;
}

public
 interface IBackgroundJob<in TArgs>
{
void Execute(TArgs args)
;
}

EnqueueAsync 返回 job ID,可用于后续的状态查询。

多提供者模式

ABP 后台作业支持多种后端,通过模块依赖切换:

// 默认:Volo.Abp.BackgroundJobs(内存队列)
// 生产:引用 Volo.Abp.BackgroundJobs.HangFire → 配置 Hangfire 即可

// 调度:引用 Volo.Abp.BackgroundJobs.Quartz → 复杂 Cron 表达式

接口不变,换依赖包即可切换后端。

3. 缓存

ABP 对 ASP.NET Core 的 IDistributedCache 做了泛型封装。

public interface IDistributedCache<TCacheItem, TCacheKey>
    where
 TCacheItem : class
{
    Task<TCacheItem?> GetAsync(TCacheKey key, ...);
    Task<TCacheItem?> GetOrAddAsync(TCacheKey key, Func<Task<TCacheItem>> factory, ...);
    Task SetAsync(TCacheKey key, TCacheItem value, ...)
;
    Task RemoveAsync(TCacheKey key, ...)
;
}

泛型隔离的实现原理:内部将 TCacheItem.FullName 作为 key 前缀拼接,不同缓存类型自动使用不同前缀,不会冲突。

GetOrAddAsync 的用法

// 等效于:
// 1. 尝试从缓存获取

// 2. 没有命中则调用 factory

// 3. 将 factory 结果写入缓存

// 4. 返回结果

var
 book = await _cache.GetOrAddAsync(
    bookId.ToString(),
    async
 () => await _bookRepo.GetAsync(bookId)
);

重要参数

// considerUow: true — 缓存写入延迟到 UOW 提交
// hideErrors: true — 缓存异常不中断业务(默认)

三者的协作

public async Task<OrderDto> CreateAsync(CreateOrderDto input)
{
    // 1. ApplicationService 自动记录审计(谁、何时、调了什么方法)

    var
 order = await _orderRepo.InsertAsync(order);
    //    FullAuditedAggregateRoot 自动记录 CreationTime / CreatorId


    // 2. 清理缓存(下次查询重新加载)

    await
 _cache.RemoveAsync(order.Id.ToString());

    // 3. 后台作业异步发送邮件(用户不需要等待)

    await
 _backgroundJobManager.EnqueueAsync(
        new
 SendEmailArgs { To = order.Email, OrderId = order.Id },
        delay: TimeSpan.FromMinutes(1)
    );

    return
 ObjectMapper.Map<Order, OrderDto>(order);
}