NextcloudTalkAlertService.cs 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. using System.Net.Http.Headers;
  2. using System.Net.Http.Json;
  3. using System.Text;
  4. using Application.Abstractions.Notification;
  5. using Microsoft.Extensions.Hosting;
  6. using Microsoft.Extensions.Logging;
  7. using Microsoft.Extensions.Options;
  8. using SharedKernel;
  9. namespace Infrastructure.Notification;
  10. /// <summary>
  11. /// Nextcloud Talk OCS Chat API 운영 알림 발송기.
  12. /// 메시지는 백그라운드로 발송(fire-and-forget) — 외부 메신저 장애가 파트너 API 응답을 지연/실패시키지 않는다.
  13. /// 모든 메시지에 [환경명] prefix 를 붙여 dev/prod 발신원을 구분한다.
  14. /// </summary>
  15. internal sealed class NextcloudTalkAlertService(
  16. IHttpClientFactory httpClientFactory,
  17. IOptions<AppSettings> options,
  18. IHostEnvironment env,
  19. ILogger<NextcloudTalkAlertService> logger
  20. ) : IAdminAlertService
  21. {
  22. public Task SendActivityAsync(string message, CancellationToken ct = default)
  23. {
  24. QueueSend(options.Value.NextcloudTalk.ActivityRoomToken, message);
  25. return Task.CompletedTask;
  26. }
  27. public Task SendAlertAsync(string message, CancellationToken ct = default)
  28. {
  29. QueueSend(options.Value.NextcloudTalk.AlertRoomToken, message);
  30. return Task.CompletedTask;
  31. }
  32. private void QueueSend(string roomToken, string message)
  33. {
  34. var cfg = options.Value.NextcloudTalk;
  35. if (!cfg.Enabled || string.IsNullOrWhiteSpace(cfg.BaseUrl) || string.IsNullOrWhiteSpace(cfg.Username) || string.IsNullOrWhiteSpace(cfg.AppPassword) || string.IsNullOrWhiteSpace(roomToken))
  36. {
  37. return;
  38. }
  39. // fire-and-forget — 호출자 CancellationToken 과 분리해 클라이언트 연결 종료에도 발송을 완료한다
  40. var label = string.IsNullOrWhiteSpace(cfg.Label) ? env.EnvironmentName : cfg.Label;
  41. _ = Task.Run(() => SendCoreAsync(cfg, roomToken, $"[{label}] {message}"));
  42. }
  43. private async Task SendCoreAsync(AppSettings.NextcloudTalkSection cfg, string roomToken, string message)
  44. {
  45. try
  46. {
  47. var client = httpClientFactory.CreateClient("NextcloudTalk");
  48. var request = new HttpRequestMessage(HttpMethod.Post, $"{cfg.BaseUrl.TrimEnd('/')}/ocs/v2.php/apps/spreed/api/v1/chat/{roomToken}");
  49. request.Headers.Add("OCS-APIRequest", "true");
  50. request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
  51. request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{cfg.Username}:{cfg.AppPassword}")));
  52. request.Content = JsonContent.Create(new { message });
  53. var response = await client.SendAsync(request);
  54. if (!response.IsSuccessStatusCode)
  55. {
  56. logger.LogWarning("[NextcloudTalk] 발송 실패 — HTTP {Status}, room={Room}", (int)response.StatusCode, roomToken);
  57. }
  58. }
  59. catch (Exception ex)
  60. {
  61. logger.LogWarning(ex, "[NextcloudTalk] 발송 오류 — room={Room}", roomToken);
  62. }
  63. }
  64. }