P0 - 安全修复: - 移除硬编码 API Key,启动时校验必填环境变量 P1 - 高优先级: - Entity 拆分:Position.cs, Transaction.cs 独立文件 - Controller Facade 封装:IPortfolioFacade 减少依赖注入 P2 - 中优先级: - Repository 抽象:IPortfolioRepository, IMarketDataRepository - MarketDataService 拆分:组合模式整合 Tencent/Tiingo/OKX P3 - 低优先级: - DTO 命名规范:统一 PascalCase - 单元测试框架:xUnit + Moq + FluentAssertions
159 lines
4.9 KiB
C#
159 lines
4.9 KiB
C#
using AssetManager.Data;
|
|
using Microsoft.Extensions.Logging;
|
|
using SqlSugar;
|
|
|
|
namespace AssetManager.Data.Repositories;
|
|
|
|
/// <summary>
|
|
/// 投资组合仓储实现
|
|
/// </summary>
|
|
public class PortfolioRepository : IPortfolioRepository
|
|
{
|
|
private readonly ISqlSugarClient _db;
|
|
private readonly ILogger<PortfolioRepository> _logger;
|
|
|
|
public PortfolioRepository(ISqlSugarClient db, ILogger<PortfolioRepository> logger)
|
|
{
|
|
_db = db;
|
|
_logger = logger;
|
|
}
|
|
|
|
// ===== Portfolio =====
|
|
|
|
public async Task<Portfolio?> GetByIdAsync(string id, string userId)
|
|
{
|
|
return await _db.Queryable<Portfolio>()
|
|
.Where(p => p.Id == id && p.UserId == userId)
|
|
.FirstAsync();
|
|
}
|
|
|
|
public async Task<List<Portfolio>> GetByUserIdAsync(string userId)
|
|
{
|
|
return await _db.Queryable<Portfolio>()
|
|
.Where(p => p.UserId == userId)
|
|
.ToListAsync();
|
|
}
|
|
|
|
public async Task<Portfolio> CreateAsync(Portfolio portfolio)
|
|
{
|
|
await _db.Insertable(portfolio).ExecuteCommandAsync();
|
|
return portfolio;
|
|
}
|
|
|
|
public async Task<bool> UpdateAsync(Portfolio portfolio)
|
|
{
|
|
portfolio.UpdatedAt = DateTime.Now;
|
|
return await _db.Updateable(portfolio).ExecuteCommandAsync() > 0;
|
|
}
|
|
|
|
public async Task<bool> DeleteAsync(string id, string userId)
|
|
{
|
|
var portfolio = await GetByIdAsync(id, userId);
|
|
if (portfolio == null) return false;
|
|
|
|
// 删除相关数据
|
|
await _db.Deleteable<Position>().Where(p => p.PortfolioId == id).ExecuteCommandAsync();
|
|
await _db.Deleteable<Transaction>().Where(t => t.PortfolioId == id).ExecuteCommandAsync();
|
|
await _db.Deleteable<PortfolioNavHistory>().Where(n => n.PortfolioId == id).ExecuteCommandAsync();
|
|
|
|
return await _db.Deleteable(portfolio).ExecuteCommandAsync() > 0;
|
|
}
|
|
|
|
// ===== Position =====
|
|
|
|
public async Task<List<Position>> GetPositionsByPortfolioIdAsync(string portfolioId)
|
|
{
|
|
return await _db.Queryable<Position>()
|
|
.Where(p => p.PortfolioId == portfolioId)
|
|
.ToListAsync();
|
|
}
|
|
|
|
public async Task<Position?> GetPositionAsync(string portfolioId, string stockCode)
|
|
{
|
|
return await _db.Queryable<Position>()
|
|
.Where(p => p.PortfolioId == portfolioId && p.StockCode == stockCode)
|
|
.FirstAsync();
|
|
}
|
|
|
|
public async Task<Position> CreatePositionAsync(Position position)
|
|
{
|
|
position.CreatedAt = DateTime.Now;
|
|
position.UpdatedAt = DateTime.Now;
|
|
await _db.Insertable(position).ExecuteCommandAsync();
|
|
return position;
|
|
}
|
|
|
|
public async Task<bool> UpdatePositionAsync(Position position)
|
|
{
|
|
position.UpdatedAt = DateTime.Now;
|
|
return await _db.Updateable(position).ExecuteCommandAsync() > 0;
|
|
}
|
|
|
|
public async Task<bool> DeletePositionAsync(string positionId)
|
|
{
|
|
return await _db.Deleteable<Position>()
|
|
.Where(p => p.Id == positionId)
|
|
.ExecuteCommandAsync() > 0;
|
|
}
|
|
|
|
// ===== Transaction =====
|
|
|
|
public async Task<List<Transaction>> GetTransactionsAsync(string portfolioId, int limit, int offset)
|
|
{
|
|
return await _db.Queryable<Transaction>()
|
|
.Where(t => t.PortfolioId == portfolioId)
|
|
.OrderByDescending(t => t.TransactionTime)
|
|
.Skip(offset)
|
|
.Take(limit)
|
|
.ToListAsync();
|
|
}
|
|
|
|
public async Task<int> GetTransactionCountAsync(string portfolioId)
|
|
{
|
|
return await _db.Queryable<Transaction>()
|
|
.Where(t => t.PortfolioId == portfolioId)
|
|
.CountAsync();
|
|
}
|
|
|
|
public async Task<Transaction> CreateTransactionAsync(Transaction transaction)
|
|
{
|
|
transaction.CreatedAt = DateTime.Now;
|
|
await _db.Insertable(transaction).ExecuteCommandAsync();
|
|
return transaction;
|
|
}
|
|
|
|
// ===== NavHistory =====
|
|
|
|
public async Task<List<PortfolioNavHistory>> GetNavHistoryAsync(string portfolioId, DateTime? startDate, DateTime? endDate)
|
|
{
|
|
var query = _db.Queryable<PortfolioNavHistory>()
|
|
.Where(n => n.PortfolioId == portfolioId);
|
|
|
|
if (startDate.HasValue)
|
|
{
|
|
query = query.Where(n => n.Date >= startDate.Value.Date);
|
|
}
|
|
if (endDate.HasValue)
|
|
{
|
|
query = query.Where(n => n.Date <= endDate.Value.Date);
|
|
}
|
|
|
|
return await query.OrderBy(n => n.Date).ToListAsync();
|
|
}
|
|
|
|
public async Task<int> DeleteNavHistoryAfterDateAsync(string portfolioId, DateTime date)
|
|
{
|
|
return await _db.Deleteable<PortfolioNavHistory>()
|
|
.Where(n => n.PortfolioId == portfolioId && n.Date >= date.Date)
|
|
.ExecuteCommandAsync();
|
|
}
|
|
|
|
public async Task<int> CreateNavHistoryBatchAsync(List<PortfolioNavHistory> records)
|
|
{
|
|
if (records == null || records.Count == 0) return 0;
|
|
|
|
return await _db.Insertable(records)
|
|
.ExecuteCommandAsync();
|
|
}
|
|
}
|