refactor: 架构整改 - Ticker 逻辑移到 Service 层,DTO 独立

This commit is contained in:
虾球 2026-03-06 10:06:31 +00:00
parent 86e073add7
commit 8830dd17ae
5 changed files with 89 additions and 42 deletions

View File

@ -1,9 +1,9 @@
using AssetManager.Data;
using AssetManager.Models; using AssetManager.Models;
using AssetManager.Models.DTOs;
using AssetManager.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using SqlSugar;
namespace AssetManager.API.Controllers; namespace AssetManager.API.Controllers;
@ -16,18 +16,18 @@ namespace AssetManager.API.Controllers;
public class TickerController : ControllerBase public class TickerController : ControllerBase
{ {
private readonly ILogger<TickerController> _logger; private readonly ILogger<TickerController> _logger;
private readonly ISqlSugarClient _db; private readonly ITickerService _tickerService;
public TickerController(ILogger<TickerController> logger, ISqlSugarClient db) public TickerController(ILogger<TickerController> logger, ITickerService tickerService)
{ {
_logger = logger; _logger = logger;
_db = db; _tickerService = tickerService;
} }
/// <summary> /// <summary>
/// 模糊搜索股票代码 /// 模糊搜索股票代码
/// </summary> /// </summary>
/// <param name="keyword">搜索关键词ticker 或 exchange</param> /// <param name="keyword">搜索关键词ticker 或 exchange 或 name</param>
/// <param name="limit">返回数量上限(默认 20</param> /// <param name="limit">返回数量上限(默认 20</param>
/// <returns>匹配的股票代码列表</returns> /// <returns>匹配的股票代码列表</returns>
[HttpGet("search")] [HttpGet("search")]
@ -39,30 +39,7 @@ public class TickerController : ControllerBase
{ {
_logger.LogInformation($"Search ticker with keyword: {keyword}, limit: {limit}"); _logger.LogInformation($"Search ticker with keyword: {keyword}, limit: {limit}");
if (string.IsNullOrWhiteSpace(keyword)) var results = await _tickerService.SearchTickerAsync(keyword, limit);
{
return Ok(new ApiResponse<List<TickerSearchResult>>
{
code = AssetManager.Models.StatusCodes.Success,
data = new List<TickerSearchResult>(),
message = "Keyword is empty"
});
}
// 模糊搜索ticker 包含 keyword 或者 exchange 包含 keyword 或者 name 包含 keyword
var results = await _db.Queryable<TiingoTicker>()
.Where(t => t.Ticker.Contains(keyword) || t.Exchange.Contains(keyword) || (t.Name != null && t.Name.Contains(keyword)))
.Take(limit)
.OrderBy(t => t.Ticker)
.Select(t => new TickerSearchResult
{
Ticker = t.Ticker,
Name = t.Name,
Exchange = t.Exchange,
AssetType = t.AssetType,
PriceCurrency = t.PriceCurrency
})
.ToListAsync();
return Ok(new ApiResponse<List<TickerSearchResult>> return Ok(new ApiResponse<List<TickerSearchResult>>
{ {
@ -84,15 +61,3 @@ public class TickerController : ControllerBase
} }
} }
} }
/// <summary>
/// 股票代码搜索结果
/// </summary>
public class TickerSearchResult
{
public string? Ticker { get; set; }
public string? Name { get; set; }
public string? Exchange { get; set; }
public string? AssetType { get; set; }
public string? PriceCurrency { get; set; }
}

View File

@ -68,6 +68,7 @@ builder.Services.AddScoped<AssetManager.Services.Services.WechatService>();
builder.Services.AddScoped<AssetManager.Services.Services.JwtService>(); builder.Services.AddScoped<AssetManager.Services.Services.JwtService>();
builder.Services.AddScoped<AssetManager.Services.IPortfolioService, AssetManager.Services.PortfolioService>(); builder.Services.AddScoped<AssetManager.Services.IPortfolioService, AssetManager.Services.PortfolioService>();
builder.Services.AddScoped<AssetManager.Services.IStrategyService, AssetManager.Services.StrategyService>(); builder.Services.AddScoped<AssetManager.Services.IStrategyService, AssetManager.Services.StrategyService>();
builder.Services.AddScoped<AssetManager.Services.ITickerService, AssetManager.Services.TickerService>();
// 市场数据服务:开发环境用 Mock生产环境用 Alpaca // 市场数据服务:开发环境用 Mock生产环境用 Alpaca
if (builder.Environment.IsDevelopment()) if (builder.Environment.IsDevelopment())

View File

@ -0,0 +1,13 @@
namespace AssetManager.Models.DTOs;
/// <summary>
/// 股票代码搜索结果
/// </summary>
public class TickerSearchResult
{
public string? Ticker { get; set; }
public string? Name { get; set; }
public string? Exchange { get; set; }
public string? AssetType { get; set; }
public string? PriceCurrency { get; set; }
}

View File

@ -0,0 +1,17 @@
using AssetManager.Models.DTOs;
namespace AssetManager.Services;
/// <summary>
/// 股票代码服务接口
/// </summary>
public interface ITickerService
{
/// <summary>
/// 模糊搜索股票代码
/// </summary>
/// <param name="keyword">搜索关键词</param>
/// <param name="limit">返回数量上限</param>
/// <returns>匹配的股票代码列表</returns>
Task<List<TickerSearchResult>> SearchTickerAsync(string keyword, int limit = 20);
}

View File

@ -0,0 +1,51 @@
using AssetManager.Data;
using AssetManager.Models.DTOs;
using Microsoft.Extensions.Logging;
using SqlSugar;
namespace AssetManager.Services;
/// <summary>
/// 股票代码服务实现
/// </summary>
public class TickerService : ITickerService
{
private readonly ILogger<TickerService> _logger;
private readonly ISqlSugarClient _db;
public TickerService(ILogger<TickerService> logger, ISqlSugarClient db)
{
_logger = logger;
_db = db;
}
/// <summary>
/// 模糊搜索股票代码
/// </summary>
public async Task<List<TickerSearchResult>> SearchTickerAsync(string keyword, int limit = 20)
{
_logger.LogInformation($"Search ticker with keyword: {keyword}, limit: {limit}");
if (string.IsNullOrWhiteSpace(keyword))
{
return new List<TickerSearchResult>();
}
// 模糊搜索ticker 包含 keyword 或者 exchange 包含 keyword 或者 name 包含 keyword
var results = await _db.Queryable<TiingoTicker>()
.Where(t => t.Ticker.Contains(keyword) || t.Exchange.Contains(keyword) || (t.Name != null && t.Name.Contains(keyword)))
.Take(limit)
.OrderBy(t => t.Ticker)
.Select(t => new TickerSearchResult
{
Ticker = t.Ticker,
Name = t.Name,
Exchange = t.Exchange,
AssetType = t.AssetType,
PriceCurrency = t.PriceCurrency
})
.ToListAsync();
return results;
}
}