Handler.cs 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Application.Abstractions.Stocks;
  4. using Microsoft.EntityFrameworkCore;
  5. namespace Application.Features.Api.Forum.StockBoard.Trending;
  6. /// <summary>
  7. /// 종목 트렌딩 랭킹 (d2 §④·§⑥ M2) — Redis Sorted Set 캐시(TTL 10분)에서 상위 종목 코드/점수를 가져와
  8. /// Stock(종목명·denorm 시세)·StockBoardStats(총 글 수)를 조인해 반환한다. 익명 열람.
  9. /// </summary>
  10. internal sealed class Handler(IAppDbContext db, IStockBoardTrendingCache cache) : IQueryHandler<Query, Response>
  11. {
  12. private const int DefaultCount = 20;
  13. private const int MaxCount = 50;
  14. public async Task<Response> Handle(Query request, CancellationToken ct)
  15. {
  16. var count = request.Count <= 0 ? DefaultCount : Math.Min(request.Count, MaxCount);
  17. var ranked = await cache.GetTopAsync(count, db, ct);
  18. if (ranked.Count == 0)
  19. {
  20. return new Response([]);
  21. }
  22. var codes = ranked.Select(c => c.StockCode).ToList();
  23. var stocks = await db.Stock.AsNoTracking()
  24. .Where(c => codes.Contains(c.Code))
  25. .Select(c => new { c.Code, c.Name, c.LastClosePrice, c.LastChangeRate })
  26. .ToDictionaryAsync(c => c.Code, ct);
  27. var postCounts = await db.StockBoardStats.AsNoTracking()
  28. .Where(c => codes.Contains(c.StockCode))
  29. .Select(c => new { c.StockCode, c.Posts })
  30. .ToDictionaryAsync(c => c.StockCode, c => c.Posts, ct);
  31. var rows = new List<Response.Row>(ranked.Count);
  32. for (var i = 0; i < ranked.Count; i++)
  33. {
  34. var entry = ranked[i];
  35. stocks.TryGetValue(entry.StockCode, out var stock);
  36. postCounts.TryGetValue(entry.StockCode, out var posts);
  37. rows.Add(new Response.Row(
  38. Rank: i + 1,
  39. StockCode: entry.StockCode,
  40. StockName: stock?.Name,
  41. ClosePrice: stock?.LastClosePrice,
  42. ChangeRate: stock?.LastChangeRate,
  43. Posts: posts,
  44. Score: entry.Score));
  45. }
  46. return new Response(rows);
  47. }
  48. }