Compare commits

...

2 Commits

Author SHA1 Message Date
OpenClaw Agent
3768f6e747 fix: 防止价格获取并发竞态条件
问题:多个并发请求同时获取同一股票价格,导致:
1. 外部 API 被重复调用(可能被限流)
2. 部分请求失败导致收益率显示 0%

解决方案:
- 使用 ConcurrentDictionary 存储进行中的请求
- GetOrAdd 模式确保同一时间只有一个请求在获取价格
- 其他并发请求等待第一个请求的结果
- 请求完成后移除 pending 任务
2026-03-24 08:57:45 +00:00
OpenClaw Agent
0579e2f47a fix: 修复交易时间解析逻辑
- TransactionDate 和 TransactionTime 正确组合
- 移除 else if 改为独立 if,支持日期+时间
- CreateTransactionAsync 返回实际交易时间而非当前时间
2026-03-24 08:53:47 +00:00
2 changed files with 38 additions and 8 deletions

View File

@ -2,6 +2,7 @@ using AssetManager.Data;
using AssetManager.Data.Repositories;
using AssetManager.Models.DTOs;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
namespace AssetManager.Infrastructure.Services;
@ -21,6 +22,9 @@ public class MarketDataService : IMarketDataService
private readonly IOkxMarketService _okxService;
private readonly IMarketDataRepository _marketDataRepo;
// 防止并发请求同一股票
private readonly ConcurrentDictionary<string, Task<MarketPriceResponse>> _pendingPriceRequests = new();
public MarketDataService(
ILogger<MarketDataService> logger,
ITencentMarketService tencentService,
@ -39,9 +43,11 @@ public class MarketDataService : IMarketDataService
/// <summary>
/// 获取实时价格(自动根据资产类型路由到对应数据源)
/// 使用并发控制防止重复请求
/// </summary>
public async Task<MarketPriceResponse> GetPriceAsync(string symbol, string assetType)
{
var cacheKey = $"{symbol.ToUpper()}_{assetType.ToUpper()}";
_logger.LogInformation("获取实时价格: {Symbol}, 资产类型: {AssetType}", symbol, assetType);
// 先查缓存
@ -60,7 +66,27 @@ public class MarketDataService : IMarketDataService
};
}
// 缓存未命中调用API
// 使用 GetOrAdd 模式防止并发重复请求
var priceTask = _pendingPriceRequests.GetOrAdd(cacheKey, _ => FetchPriceFromSourceAsync(symbol, assetType));
try
{
return await priceTask;
}
finally
{
// 请求完成后移除(无论成功失败)
_pendingPriceRequests.TryRemove(cacheKey, out _);
}
}
/// <summary>
/// 从数据源获取价格(内部方法)
/// </summary>
private async Task<MarketPriceResponse> FetchPriceFromSourceAsync(string symbol, string assetType)
{
_logger.LogInformation("从数据源获取价格: {Symbol}, 资产类型: {AssetType}", symbol, assetType);
MarketPriceResponse response;
string source;

View File

@ -657,15 +657,15 @@ public class PortfolioService : IPortfolioService
{
if (DateTime.TryParse(request.TransactionDate, out var parsedDate))
{
// 如果只传了日期,时间部分默认用当前时间
transactionTime = parsedDate.Date + DateTime.Now.TimeOfDay;
transactionTime = parsedDate.Date;
}
}
else if (!string.IsNullOrEmpty(request.TransactionTime))
// 组合时间部分
if (!string.IsNullOrEmpty(request.TransactionTime))
{
if (DateTime.TryParse(request.TransactionTime, out var parsedTime))
if (TimeSpan.TryParse(request.TransactionTime, out var parsedTime))
{
transactionTime = parsedTime;
transactionTime = transactionTime.Date + parsedTime;
}
}
@ -821,12 +821,16 @@ public class PortfolioService : IPortfolioService
request.PortfolioId = portfolioId;
var response = await CreateTransaction(request, userId);
// 使用实际交易时间
var transactionDate = request.TransactionDate ?? DateTime.Now.ToString("yyyy-MM-dd");
var transactionTime = request.TransactionTime ?? DateTime.Now.ToString("HH:mm");
return new TransactionItem
{
Id = response.Id,
PortfolioId = portfolioId,
Date = DateTime.Now.ToString("yyyy-MM-dd"),
Time = DateTime.Now.ToString("HH:mm:ss"),
Date = transactionDate,
Time = transactionTime,
Type = request.Type,
StockCode = request.StockCode,
Amount = response.TotalAmount,