Program.cs 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. using Application.Abstractions.Cache;
  2. using Application.Abstractions.Data;
  3. using Infrastructure.Cache;
  4. using Infrastructure.Messaging.Email;
  5. using Infrastructure.Persistence;
  6. using MailWorker;
  7. using Serilog;
  8. using Serilog.Events;
  9. using SharedKernel;
  10. using StackExchange.Redis;
  11. using Microsoft.EntityFrameworkCore;
  12. using Microsoft.Extensions.Configuration;
  13. using Microsoft.Extensions.DependencyInjection;
  14. using Microsoft.Extensions.Hosting;
  15. using Microsoft.Extensions.Hosting.WindowsServices;
  16. using Microsoft.Extensions.Logging;
  17. using Microsoft.AspNetCore.DataProtection;
  18. var builder = Host.CreateApplicationBuilder(args);
  19. var settings = builder.Configuration.Get<AppSettings>()!;
  20. if (!WindowsServiceHelpers.IsWindowsService())
  21. {
  22. Console.Title = "DPOT MailWorker";
  23. }
  24. Console.WriteLine($"ENV={builder.Environment.EnvironmentName}");
  25. Console.WriteLine($"현재 시간: {DateTime.Now} / {TimeZoneInfo.Local.Id}");
  26. builder.Services.AddWindowsService();
  27. if (builder.Environment.IsProduction())
  28. {
  29. Log.Logger = new LoggerConfiguration()
  30. .MinimumLevel.Information()
  31. .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
  32. .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning)
  33. .WriteTo.Console()
  34. .WriteTo.File("logs/mailworker-.log",
  35. rollingInterval: RollingInterval.Day,
  36. retainedFileCountLimit: 30,
  37. outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
  38. .CreateLogger();
  39. builder.Logging.ClearProviders();
  40. builder.Logging.AddSerilog(Log.Logger);
  41. }
  42. builder.Services.Configure<AppSettings>(builder.Configuration);
  43. // DB
  44. var dbConn = builder.Configuration.GetConnectionString("DefaultConnection");
  45. if (string.IsNullOrWhiteSpace(dbConn))
  46. {
  47. throw new InvalidOperationException("Connection string 'DefaultConnection' is not configured.");
  48. }
  49. builder.Services.AddDbContext<AppDbContext>(options => options
  50. .UseSqlServer(dbConn)
  51. .ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning)));
  52. builder.Services.AddScoped<IAppDbContext>(sp => sp.GetRequiredService<AppDbContext>());
  53. // Redis (Config 캐시 공유)
  54. var redis = ConnectionMultiplexer.Connect(settings.Redis.DefaultConnection);
  55. builder.Services.AddSingleton<IConnectionMultiplexer>(redis);
  56. builder.Services.AddDataProtection()
  57. .SetApplicationName(settings.App.Name)
  58. .PersistKeysToStackExchangeRedis(redis, settings.Redis.DataProtectionKey)
  59. .ProtectKeysWithDpapi(protectToLocalMachine: true) // key를 암호화하여 저장 (로컬 머신에서만 복호화 가능, 서버간 공유 불가)
  60. .SetDefaultKeyLifetime(settings.Redis.DefaultKeyLifetime); // 기본 90일 주기
  61. builder.Services.AddStackExchangeRedisCache(options =>
  62. {
  63. options.Configuration = settings.Redis.DefaultConnection;
  64. options.InstanceName = settings.Redis.CachePrefix;
  65. });
  66. builder.Services.AddSingleton<ICacheService, RedisCacheService>();
  67. // SMTP 직발 (Worker는 항상 Direct)
  68. builder.Services.AddScoped<DirectMailService>();
  69. // Hosted services
  70. builder.Services.AddHostedService<MailQueueWorker>();
  71. builder.Services.AddHostedService<EmailLogJanitor>();
  72. builder.Build().Run();