refactor(core): 重构核心分层结构并添加发布库聚合
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Gpulse.WCT.DataAnalyzer.Core.Infrastructure.LocalData;
|
||||
using Gpulse.WCT.DataAnalyzer.Core.Domain.Local;
|
||||
|
||||
namespace Gpulse.WCT.DataAnalyzer.Core.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 维度表管理服务 - 负责维度表的CRUD操作(并发安全版本)
|
||||
/// </summary>
|
||||
public class DimensionService
|
||||
{
|
||||
private readonly WctMinerDbContext _context;
|
||||
private readonly ILogger<DimensionService> _logger;
|
||||
|
||||
public DimensionService(WctMinerDbContext context, ILogger<DimensionService> logger)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或创建TX面板维度(并发安全)
|
||||
/// </summary>
|
||||
public async Task<TxPanel> GetOrCreateTxPanelAsync(string name)
|
||||
{
|
||||
return await GetOrCreateDimensionAsync(
|
||||
() => _context.TxPanels.AsNoTracking().FirstOrDefaultAsync(p => p.Name == name),
|
||||
() => new TxPanel { Name = name },
|
||||
entity => _context.TxPanels.Add(entity),
|
||||
name,
|
||||
"TX Panel"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或创建TX硬件版本维度(并发安全)
|
||||
/// </summary>
|
||||
public async Task<TxHardware> GetOrCreateTxHardwareAsync(string version)
|
||||
{
|
||||
return await GetOrCreateDimensionAsync(
|
||||
() => _context.TxHardwares.AsNoTracking().FirstOrDefaultAsync(h => h.Version == version),
|
||||
() => new TxHardware { Version = version },
|
||||
entity => _context.TxHardwares.Add(entity),
|
||||
version,
|
||||
"TX Hardware"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或创建TX软件版本维度(并发安全)
|
||||
/// </summary>
|
||||
public async Task<TxSoftware> GetOrCreateTxSoftwareAsync(string version)
|
||||
{
|
||||
return await GetOrCreateDimensionAsync(
|
||||
() => _context.TxSoftwares.AsNoTracking().FirstOrDefaultAsync(s => s.Version == version),
|
||||
() => new TxSoftware { Version = version },
|
||||
entity => _context.TxSoftwares.Add(entity),
|
||||
version,
|
||||
"TX Software"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或创建RX类型维度(并发安全)
|
||||
/// </summary>
|
||||
public async Task<RxType> GetOrCreateRxTypeAsync(string name)
|
||||
{
|
||||
return await GetOrCreateDimensionAsync(
|
||||
() => _context.RxTypes.AsNoTracking().FirstOrDefaultAsync(r => r.Name == name),
|
||||
() => new RxType { Name = name },
|
||||
entity => _context.RxTypes.Add(entity),
|
||||
name,
|
||||
"RX Type"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通用的并发安全 GetOrCreate 模式
|
||||
/// </summary>
|
||||
private async Task<T> GetOrCreateDimensionAsync<T>(
|
||||
Func<Task<T?>> findExisting,
|
||||
Func<T> createNew,
|
||||
Action<T> addToContext,
|
||||
string identifier,
|
||||
string dimensionName) where T : class
|
||||
{
|
||||
const int maxRetries = 3;
|
||||
|
||||
for (int attempt = 0; attempt < maxRetries; attempt++)
|
||||
{
|
||||
// 使用 AsNoTracking 避免 ChangeTracker 缓存
|
||||
var existing = await findExisting();
|
||||
|
||||
if (existing != null) return existing;
|
||||
|
||||
var newEntity = createNew();
|
||||
addToContext(newEntity);
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogDebug("创建新 {DimensionName} 维度: {Identifier}", dimensionName, identifier);
|
||||
return newEntity;
|
||||
}
|
||||
catch (DbUpdateException ex) when (IsUniqueConstraintViolation(ex))
|
||||
{
|
||||
// Detach 实体以避免 ChangeTracker 问题
|
||||
_context.Entry(newEntity).State = EntityState.Detached;
|
||||
_logger.LogDebug("{DimensionName} 并发创建冲突,重试: {Identifier}", dimensionName, identifier);
|
||||
|
||||
if (attempt < maxRetries - 1)
|
||||
{
|
||||
// 随机退避,减少并发冲突
|
||||
await Task.Delay(Random.Shared.Next(5, 20));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 最后一次尝试:必然存在(唯一约束已生效)
|
||||
var finalEntity = await findExisting();
|
||||
if (finalEntity == null)
|
||||
{
|
||||
// 极端情况:重试全部失败但数据不存在,抛出异常
|
||||
throw new InvalidOperationException($"无法获取或创建 {dimensionName} 维度: {identifier}");
|
||||
}
|
||||
return finalEntity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测 SQLite 唯一约束违反错误
|
||||
/// </summary>
|
||||
private static bool IsUniqueConstraintViolation(DbUpdateException ex)
|
||||
{
|
||||
return ex.InnerException is Microsoft.Data.Sqlite.SqliteException sqliteEx
|
||||
&& sqliteEx.SqliteErrorCode == 19; // SQLITE_CONSTRAINT
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user