Handler.cs 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. using Application.Abstractions.Cache;
  2. using Application.Abstractions.Data;
  3. using Application.Abstractions.Messaging;
  4. using Application.Abstractions.YouTube;
  5. using Microsoft.EntityFrameworkCore;
  6. using SharedKernel.Results;
  7. namespace Application.Features.Api.Channel.GetCreators;
  8. public sealed class Handler(
  9. IAppDbContext db,
  10. IYouTubeChannelCache channelCache,
  11. IYouTubeLiveStateStore liveStateStore,
  12. ICacheService cache
  13. ) : IQueryHandler<Query, Result<Response>>
  14. {
  15. private const string CacheKey = "channel:creators:main";
  16. private static readonly TimeSpan CacheTtl = TimeSpan.FromSeconds(30);
  17. public async Task<Result<Response>> Handle(Query request, CancellationToken ct)
  18. {
  19. var cached = await cache.GetAsync<List<CreatorItem>>(CacheKey, ct);
  20. if (cached is not null)
  21. {
  22. return new Response(cached);
  23. }
  24. var items = await BuildAsync(ct);
  25. await cache.SetAsync(CacheKey, items, CacheTtl, ct);
  26. return new Response(items);
  27. }
  28. private async Task<List<CreatorItem>> BuildAsync(CancellationToken ct)
  29. {
  30. // 활성 채널 + 회원 집계(팔로워)·등급 조인. 구독자/썸네일은 아래 YouTube 캐시에서 보강.
  31. var rows = await db.Channel.AsNoTracking().Where(c => c.IsActive).Select(c => new
  32. {
  33. c.SID,
  34. c.Name,
  35. c.Handle,
  36. FollowerCount = c.Member.MemberStats != null ? c.Member.MemberStats.FollowerCount : 0,
  37. GradeName = c.Member.MemberGrade != null ? c.Member.MemberGrade.KorName : null,
  38. GradeImage = c.Member.MemberGrade != null ? c.Member.MemberGrade.Image : null,
  39. GradeColor = c.Member.MemberGrade != null ? c.Member.MemberGrade.TextColor : null
  40. }).ToListAsync(ct);
  41. if (rows.Count == 0)
  42. {
  43. return [];
  44. }
  45. // 구독자 수·썸네일은 Redis YouTube 캐시에서 (엔티티 컬럼은 30일 미동기 시 0/null 로 비워짐)
  46. var ytCache = await channelCache.GetManyAsync(rows.Select(r => r.SID));
  47. var allLive = await liveStateStore.GetAllLiveAsync();
  48. var liveMap = allLive.Where(l => l.IsLive).ToDictionary(l => l.ChannelId);
  49. var items = new List<CreatorItem>(rows.Count);
  50. foreach (var r in rows)
  51. {
  52. var hasYt = ytCache.TryGetValue(r.SID, out var yt);
  53. items.Add(new CreatorItem(
  54. r.SID,
  55. r.Name,
  56. r.Handle,
  57. hasYt ? yt!.ThumbnailUrl : null,
  58. hasYt ? yt!.SubscriberCount : 0,
  59. r.FollowerCount,
  60. r.GradeName,
  61. r.GradeImage,
  62. r.GradeColor,
  63. liveMap.ContainsKey(r.SID)
  64. ));
  65. }
  66. return items;
  67. }
  68. }