| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758 |
- using Application.Abstractions.Data;
- using Application.Abstractions.Messaging;
- using Application.Abstractions.Stocks;
- using Microsoft.EntityFrameworkCore;
- namespace Application.Features.Api.Forum.StockBoard.Trending;
- /// <summary>
- /// 종목 트렌딩 랭킹 (d2 §④·§⑥ M2) — Redis Sorted Set 캐시(TTL 10분)에서 상위 종목 코드/점수를 가져와
- /// Stock(종목명·denorm 시세)·StockBoardStats(총 글 수)를 조인해 반환한다. 익명 열람.
- /// </summary>
- internal sealed class Handler(IAppDbContext db, IStockBoardTrendingCache cache) : IQueryHandler<Query, Response>
- {
- private const int DefaultCount = 20;
- private const int MaxCount = 50;
- public async Task<Response> Handle(Query request, CancellationToken ct)
- {
- var count = request.Count <= 0 ? DefaultCount : Math.Min(request.Count, MaxCount);
- var ranked = await cache.GetTopAsync(count, db, ct);
- if (ranked.Count == 0)
- {
- return new Response([]);
- }
- var codes = ranked.Select(c => c.StockCode).ToList();
- var stocks = await db.Stock.AsNoTracking()
- .Where(c => codes.Contains(c.Code))
- .Select(c => new { c.Code, c.Name, c.LastClosePrice, c.LastChangeRate })
- .ToDictionaryAsync(c => c.Code, ct);
- var postCounts = await db.StockBoardStats.AsNoTracking()
- .Where(c => codes.Contains(c.StockCode))
- .Select(c => new { c.StockCode, c.Posts })
- .ToDictionaryAsync(c => c.StockCode, c => c.Posts, ct);
- var rows = new List<Response.Row>(ranked.Count);
- for (var i = 0; i < ranked.Count; i++)
- {
- var entry = ranked[i];
- stocks.TryGetValue(entry.StockCode, out var stock);
- postCounts.TryGetValue(entry.StockCode, out var posts);
- rows.Add(new Response.Row(
- Rank: i + 1,
- StockCode: entry.StockCode,
- StockName: stock?.Name,
- ClosePrice: stock?.LastClosePrice,
- ChangeRate: stock?.LastChangeRate,
- Posts: posts,
- Score: entry.Score));
- }
- return new Response(rows);
- }
- }
|