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);
}
夜雨聆风