fix: 增强价格获取稳定性和日志追踪

问题排查:
1. 缓存命中时验证价格有效性(Price > 0)
2. 外部 API 返回无效价格时拒绝写入缓存
3. 缓存查询层添加详细日志
4. 捕获缓存查询异常并返回 null

改进:
- 缓存价格 <= 0 时忽略缓存重新获取
- 外部 API 价格 <= 0 时抛出异常,避免污染缓存
- 详细日志追踪价格获取全流程
This commit is contained in:
OpenClaw Agent 2026-03-24 09:35:50 +00:00
parent 3768f6e747
commit 3fb2403e85
2 changed files with 74 additions and 27 deletions

View File

@ -25,9 +25,30 @@ public class MarketDataRepository : IMarketDataRepository
public async Task<MarketPriceCache?> GetPriceCacheAsync(string symbol, string assetType) public async Task<MarketPriceCache?> GetPriceCacheAsync(string symbol, string assetType)
{ {
var cacheKey = GenerateCacheKey(symbol, assetType); var cacheKey = GenerateCacheKey(symbol, assetType);
return await _db.Queryable<MarketPriceCache>() _logger.LogDebug("[缓存查询] CacheKey={CacheKey}, Symbol={Symbol}, AssetType={AssetType}", cacheKey, symbol, assetType);
.Where(p => p.Id == cacheKey && p.ExpiredAt > DateTime.Now)
.FirstAsync(); try
{
var result = await _db.Queryable<MarketPriceCache>()
.Where(p => p.Id == cacheKey && p.ExpiredAt > DateTime.Now)
.FirstAsync();
if (result != null)
{
_logger.LogDebug("[缓存查询成功] CacheKey={CacheKey}, Price={Price}, ExpiredAt={ExpiredAt}", cacheKey, result.Price, result.ExpiredAt);
}
else
{
_logger.LogDebug("[缓存查询无结果] CacheKey={CacheKey}", cacheKey);
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "[缓存查询异常] CacheKey={CacheKey}", cacheKey);
return null;
}
} }
public async Task<bool> SavePriceCacheAsync(MarketPriceCache cache) public async Task<bool> SavePriceCacheAsync(MarketPriceCache cache)

View File

@ -48,35 +48,54 @@ public class MarketDataService : IMarketDataService
public async Task<MarketPriceResponse> GetPriceAsync(string symbol, string assetType) public async Task<MarketPriceResponse> GetPriceAsync(string symbol, string assetType)
{ {
var cacheKey = $"{symbol.ToUpper()}_{assetType.ToUpper()}"; var cacheKey = $"{symbol.ToUpper()}_{assetType.ToUpper()}";
_logger.LogInformation("获取实时价格: {Symbol}, 资产类型: {AssetType}", symbol, assetType); _logger.LogInformation("[价格查询开始] Symbol={Symbol}, AssetType={AssetType}, CacheKey={CacheKey}", symbol, assetType, cacheKey);
// 先查缓存
var cached = await _marketDataRepo.GetPriceCacheAsync(symbol, assetType);
if (cached != null)
{
_logger.LogDebug("缓存命中: {Symbol} {AssetType}, 价格: {Price}", symbol, assetType, cached.Price);
return new MarketPriceResponse
{
Symbol = cached.Symbol,
Price = cached.Price,
PreviousClose = cached.PreviousClose ?? 0,
Timestamp = cached.FetchedAt,
AssetType = cached.AssetType
};
}
// 使用 GetOrAdd 模式防止并发重复请求
var priceTask = _pendingPriceRequests.GetOrAdd(cacheKey, _ => FetchPriceFromSourceAsync(symbol, assetType));
try try
{ {
return await priceTask; // 先查缓存
var cached = await _marketDataRepo.GetPriceCacheAsync(symbol, assetType);
if (cached != null && cached.Price > 0) // ← 验证价格有效
{
_logger.LogInformation("[缓存命中] Symbol={Symbol}, Price={Price}, ExpiredAt={ExpiredAt}", symbol, cached.Price, cached.ExpiredAt);
return new MarketPriceResponse
{
Symbol = cached.Symbol,
Price = cached.Price,
PreviousClose = cached.PreviousClose ?? 0,
Timestamp = cached.FetchedAt,
AssetType = cached.AssetType
};
}
if (cached != null && cached.Price <= 0)
{
_logger.LogWarning("[缓存命中但价格无效] Symbol={Symbol}, Price={Price},忽略缓存重新获取", symbol, cached.Price);
}
else
{
_logger.LogWarning("[缓存未命中] Symbol={Symbol}, AssetType={AssetType},需要从数据源获取", symbol, assetType);
}
// 使用 GetOrAdd 模式防止并发重复请求
var priceTask = _pendingPriceRequests.GetOrAdd(cacheKey, _ => FetchPriceFromSourceAsync(symbol, assetType));
try
{
var result = await priceTask;
_logger.LogInformation("[价格获取成功] Symbol={Symbol}, Price={Price}", symbol, result.Price);
return result;
}
finally
{
// 请求完成后移除(无论成功失败)
_pendingPriceRequests.TryRemove(cacheKey, out _);
}
} }
finally catch (Exception ex)
{ {
// 请求完成后移除(无论成功失败) _logger.LogError(ex, "[价格获取异常] Symbol={Symbol}, AssetType={AssetType}", symbol, assetType);
_pendingPriceRequests.TryRemove(cacheKey, out _); throw;
} }
} }
@ -120,6 +139,13 @@ public class MarketDataService : IMarketDataService
} }
} }
// 验证价格有效性
if (response.Price <= 0)
{
_logger.LogError("[价格无效] Symbol={Symbol}, Price={Price}, Source={Source},不写入缓存", symbol, response.Price, source);
throw new InvalidOperationException($"获取到的价格无效: {symbol} = {response.Price}");
}
// 写入缓存 // 写入缓存
await SavePriceCacheAsync(symbol, assetType, response, source); await SavePriceCacheAsync(symbol, assetType, response, source);