using System.Diagnostics;
using Medallion.Threading;
namespace Dpz.Core.Web.Library;
/// <summary>
/// 应用启动时自动将 wwwroot 目录下所有文件上传到对象存储
/// </summary>
internal sealed class AssetUploadHostedService(
AssetService assetService,
IFusionCache fusionCache,
IDistributedLockProvider distributedLockProvider,
ILogger<AssetUploadHostedService> logger
) : IHostedService
{
private readonly string _instanceId = Guid.NewGuid().ToString();
private const string LockKey = "Dpz.Core.AssetUpload.Startup";
private const string LastSuccessCacheKey = "Dpz.Core.AssetUpload.LastSuccessUtc";
private const int SkipMinutes = 30;
private static readonly TimeSpan LockWaitTimeout = TimeSpan.FromSeconds(5);
private static readonly TimeSpan LastSuccessCacheTtl = TimeSpan.FromDays(7);
public async Task StartAsync(CancellationToken cancellationToken)
{
await using var lockHandle = await TryAcquireStartupLockAsync(cancellationToken);
if (lockHandle == null)
{
logger.LogInformation(
"实例 {InstanceId} 未获取到资产上传锁,跳过本次上传",
_instanceId
);
return;
}
var lastSuccess = await fusionCache.TryGetAsync<DateTimeOffset>(
LastSuccessCacheKey,
token: cancellationToken
);
if (lastSuccess.HasValue)
{
var elapsed = DateTimeOffset.UtcNow - lastSuccess.Value;
if (elapsed < TimeSpan.FromMinutes(SkipMinutes))
{
logger.LogInformation(
"实例 {InstanceId} 跳过资产上传:距上次成功仅 {ElapsedMinutes:F1} 分钟,阈值 {SkipMinutes} 分钟",
_instanceId,
elapsed.TotalMinutes,
SkipMinutes
);
return;
}
}
logger.LogInformation("实例 {InstanceId} 开始上传 wwwroot 资产到对象存储", _instanceId);
var stopwatch = Stopwatch.StartNew();
try
{
await assetService.UploadAssentAsync(cancellationToken);
stopwatch.Stop();
await fusionCache.SetAsync(
LastSuccessCacheKey,
DateTimeOffset.UtcNow,
LastSuccessCacheTtl,
token: cancellationToken
);
logger.LogInformation(
"实例 {InstanceId} 资产上传完成,耗时 {ElapsedMs}ms",
_instanceId,
stopwatch.ElapsedMilliseconds
);
}
catch (Exception ex)
{
stopwatch.Stop();
logger.LogError(
ex,
"实例 {InstanceId} 资产上传失败,耗时 {ElapsedMs}ms",
_instanceId,
stopwatch.ElapsedMilliseconds
);
}
}
private async Task<IDistributedSynchronizationHandle?> TryAcquireStartupLockAsync(
CancellationToken cancellationToken
)
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(LockWaitTimeout);
try
{
return await distributedLockProvider.AcquireLockAsync(
LockKey,
timeout: null,
cancellationToken: cts.Token
);
}
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
{
return null;
}
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
这是一个 ASP.NET Core 的后台服务(Hosted Service),主要功能是在应用启动时自动将 wwwroot 目录下的静态资源文件上传到对象存储服务。让我详细解释一下这个代码的各个部分:
类的基本结构
internal sealed class AssetUploadHostedService : IHostedService
这是一个实现了 IHostedService 接口的密封类,会在应用启动时自动运行。
依赖注入的服务
通过构造函数注入了以下服务:
AssetService:负责实际的资产上传逻辑IFusionCache:分布式缓存,用于记录上次成功上传的时间IDistributedLockProvider:分布式锁提供者,确保多实例环境下只有一个实例执行上传ILogger:日志记录
核心常量和字段
private readonly string _instanceId = Guid.NewGuid().ToString();
private const string LockKey = "Dpz.Core.AssetUpload.Startup";
private const string LastSuccessCacheKey = "Dpz.Core.AssetUpload.LastSuccessUtc";
private const int SkipMinutes = 30;
_instanceId:每个实例的唯一标识LockKey:分布式锁的键名LastSuccessCacheKey:缓存上次成功时间的键名SkipMinutes:如果距离上次成功上传不足30分钟,则跳过本次上传
主要逻辑 - StartAsync 方法
1. 获取分布式锁
await using var lockHandle = await TryAcquireStartupLockAsync(cancellationToken);
if (lockHandle == null) { /* 跳过上传 */ }
确保在多实例部署环境下,同一时间只有一个实例执行上传任务。
2. 检查上次成功时间
var lastSuccess = await fusionCache.TryGetAsync<DateTimeOffset>(LastSuccessCacheKey, token: cancellationToken);
if (lastSuccess.HasValue && elapsed < TimeSpan.FromMinutes(SkipMinutes)) { /* 跳过上传 */ }
如果距离上次成功上传不足30分钟,则跳过本次上传,避免频繁上传。
3. 执行上传
await assetService.UploadAssentAsync(cancellationToken);
调用 AssetService 执行实际的文件上传操作。
4. 记录成功状态
await fusionCache.SetAsync(LastSuccessCacheKey, DateTimeOffset.UtcNow, LastSuccessCacheTtl, token: cancellationToken);
上传成功后,将当前时间记录到缓存中,缓存有效期为7天。
分布式锁获取逻辑
private async Task<IDistributedSynchronizationHandle?> TryAcquireStartupLockAsync(CancellationToken cancellationToken)
这个方法尝试获取分布式锁,设置了5秒的超时时间。如果超时则返回 null,表示获取锁失败。
设计优势
- 防止重复执行:通过分布式锁确保多实例环境下不会重复上传
- 避免频繁上传:通过缓存记录上次成功时间,30分钟内不重复上传
- 性能监控:使用
Stopwatch记录上传耗时 - 异常处理:完整的异常捕获和日志记录
- 资源管理:正确使用
await using管理分布式锁资源
这个服务特别适用于微服务架构或容器化部署场景,能够智能地处理静态资源的上传同步问题。
评论加载中...