From 8b1ae4dc020912b7335207adb4b6694e8c069632 Mon Sep 17 00:00:00 2001 From: SEAN Date: Wed, 11 Feb 2026 11:07:04 +0900 Subject: [PATCH] =?UTF-8?q?improvement:=20PushWorker=20=EC=9B=B9=ED=9B=85?= =?UTF-8?q?=20=EB=B0=9C=EC=86=A1=20=EC=97=B0=EB=8F=99=20(#158)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PushWorker에 IWebhookService 의존성 주입 - 발송 완료 후 push_sent/push_failed 이벤트 웹훅 호출 - TASKS.md API 커버리지 테이블 업데이트 (65/65 완료) Closes #158 --- SPMS.Infrastructure/Workers/PushWorker.cs | 18 ++++++ TASKS.md | 67 +++++++++++------------ 2 files changed, 51 insertions(+), 34 deletions(-) diff --git a/SPMS.Infrastructure/Workers/PushWorker.cs b/SPMS.Infrastructure/Workers/PushWorker.cs index d4d0de5..0ea5a5e 100644 --- a/SPMS.Infrastructure/Workers/PushWorker.cs +++ b/SPMS.Infrastructure/Workers/PushWorker.cs @@ -26,6 +26,7 @@ public class PushWorker : BackgroundService private readonly IDuplicateChecker _duplicateChecker; private readonly IBulkJobStore _bulkJobStore; private readonly ITokenCacheService _tokenCache; + private readonly IWebhookService _webhookService; private readonly IFcmSender _fcmSender; private readonly IApnsSender _apnsSender; private readonly ILogger _logger; @@ -37,6 +38,7 @@ public class PushWorker : BackgroundService IDuplicateChecker duplicateChecker, IBulkJobStore bulkJobStore, ITokenCacheService tokenCache, + IWebhookService webhookService, IFcmSender fcmSender, IApnsSender apnsSender, ILogger logger) @@ -47,6 +49,7 @@ public class PushWorker : BackgroundService _duplicateChecker = duplicateChecker; _bulkJobStore = bulkJobStore; _tokenCache = tokenCache; + _webhookService = webhookService; _fcmSender = fcmSender; _apnsSender = apnsSender; _logger = logger; @@ -262,6 +265,21 @@ public class PushWorker : BackgroundService "푸시 발송 완료: requestId={RequestId}, 성공={Success}, 실패={Fail}, 총={Total}", pushMessage.RequestId, successCount, failCount, allResults.Count); + // [6.5] 웹훅 발송 + var webhookEvent = failCount > 0 && successCount == 0 + ? WebhookEvent.PushFailed + : WebhookEvent.PushSent; + + _ = _webhookService.SendAsync(pushMessage.ServiceId, webhookEvent, new + { + request_id = pushMessage.RequestId, + message_id = pushMessage.MessageId, + send_type = pushMessage.SendType, + success_count = successCount, + fail_count = failCount, + total_count = allResults.Count + }); + // [7] Bulk job 진행률 업데이트 if (!string.IsNullOrEmpty(pushMessage.JobId)) { diff --git a/TASKS.md b/TASKS.md index 0a2b6b5..7ca84b3 100644 --- a/TASKS.md +++ b/TASKS.md @@ -21,11 +21,11 @@ | **Account** | 9 | 9 | 0 | Phase 2-1 + 3-2 ✅ | | **Service** | 13 | 13 | 0 | Phase 2-1 ✅ | | **Device** | 7 | 7 | 0 | Phase 2-2 ✅ | -| **Message** | 5 | 1 | 4 | Phase 3 ← 다음 | -| **Push** | 8 | 5 | 3 | Phase 3 | -| **Stats** | 5 | 0 | 5 | Phase 3-2 | +| **Message** | 5 | 5 | 0 | Phase 3 ✅ | +| **Push** | 8 | 8 | 0 | Phase 3 ✅ | +| **Stats** | 5 | 5 | 0 | Phase 3-2 ✅ | | **File** | 6 | 6 | 0 | Phase 2-2 ✅ | -| **총계** | **65** | **57** | **8** | - | +| **총계** | **65** | **65** | **0** | ✅ 전체 구현 완료 | --- @@ -1656,9 +1656,9 @@ Milestone: Phase 3: 메시지 & Push Core | 10 | [Feature] 웹훅 설정 API | Feature | High | WHK-01 | ✅ | | 11 | [Feature] **웹훅 발송 서비스** | Feature | High | WHK-02 | ✅ | | 12 | [Feature] **DailyStatWorker 구현** | Feature | Medium | AAG-01 | ✅ | -| 13 | [Feature] **DeadTokenCleanupWorker 구현** | Feature | Medium | DTK-01 | ⬜ | -| 14 | [Feature] **데이터 보관 주기 관리 배치** | Feature | Medium | RET-01 | ⬜ | -| 15 | [Feature] **Redis 토큰 캐시 관리** | Feature | Medium | - | ⬜ | +| 13 | [Feature] **DeadTokenCleanupWorker 구현** | Feature | Medium | DTK-01 | ✅ | +| 14 | [Feature] **데이터 보관 주기 관리 배치** | Feature | Medium | RET-01 | ✅ | +| 15 | [Feature] **Redis 토큰 캐시 관리** | Feature | Medium | - | ✅ | --- @@ -1750,17 +1750,18 @@ Branch: feature/#XX-daily-stat-worker 제목: [Feature] DeadTokenCleanupWorker 구현 (비활성 토큰 정리) Labels: Type/Feature, Priority/Medium, Status/Available Milestone: Phase 3-2: 통계 & Webhook & 배치 -Branch: feature/#XX-dead-token-cleanup +Branch: feature/#150-dead-token-cleanup +Gitea Issue: #150 ``` -**상태**: ⬜ 대기 +**상태**: ✅ 완료 (PR #151 머지됨) **설명**: 매주 일요일 03:00 KST에 비활성 상태로 7일 이상 경과한 Device 토큰을 물리 삭제하는 BackgroundService Worker를 구현한다. > **📌 참조**: `Documents/BatchScheduler_Design.md` §5 (DeadTokenCleanupWorker) **체크리스트 — Worker 구현**: -- [ ] `SPMS.Infrastructure/Workers/DeadTokenCleanupWorker.cs` — BackgroundService 상속 +- [x] `SPMS.Infrastructure/Workers/DeadTokenCleanupWorker.cs` — BackgroundService 상속 - Cron 스케줄: 매주 일요일 03:00 KST - 삭제 대상 조회: ```sql @@ -1777,17 +1778,17 @@ Branch: feature/#XX-dead-token-cleanup LIMIT 1000 ``` - 삭제된 행 > 0 → 반복, = 0 → 완료 - - Redis 토큰 캐시 무효화 (삭제된 Device 기반) + - Redis 토큰 캐시 무효화 → #15 구현 후 연동 예정 - SystemLog에 정리 완료 로그 **체크리스트 — 안전장치**: -- [ ] 배치 단위 삭제 (1000건씩) → DB 부하 분산 -- [ ] 삭제 전 카운트 로깅 -- [ ] 비정상 수치 감지 시 중단 (전체의 50% 이상이면 경고) +- [x] 배치 단위 삭제 (1000건씩) → DB 부하 분산 +- [x] 삭제 전 카운트 로깅 +- [x] 비정상 수치 감지 시 중단 (전체의 50% 이상이면 경고) **체크리스트 — 등록**: -- [ ] `Program.cs` — `builder.Services.AddHostedService()` -- [ ] 환경별 활성화 설정 (Debug/Staging: 비활성, Release: 활성) +- [x] `SPMS.Infrastructure/DependencyInjection.cs` — `AddHostedService()` +- [ ] 환경별 활성화 설정 (Debug/Staging: 비활성, Release: 활성) → MVP Phase에서 설정 --- @@ -1797,22 +1798,23 @@ Branch: feature/#XX-dead-token-cleanup 제목: [Feature] 데이터 보관 주기 관리 배치 Labels: Type/Feature, Priority/Medium, Status/Available Milestone: Phase 3-2: 통계 & Webhook & 배치 -Branch: feature/#XX-data-retention +Branch: feature/#152-data-retention +Gitea Issue: #152 ``` -**상태**: ⬜ 대기 +**상태**: ✅ 완료 (PR #153 머지됨) **설명**: 보관 주기가 지난 로그 데이터를 정리하는 배치 작업을 구현한다. **체크리스트**: -- [ ] `SPMS.Infrastructure/Workers/DataRetentionWorker.cs` — BackgroundService 상속 +- [x] `SPMS.Infrastructure/Workers/DataRetentionWorker.cs` — BackgroundService 상속 - Cron 스케줄: 매일 04:00 KST - PushSendLog: 90일 이전 데이터 삭제 - PushOpenLog: 90일 이전 데이터 삭제 - WebhookLog: 30일 이전 데이터 삭제 - SystemLog: 180일 이전 데이터 삭제 -- [ ] 배치 단위 삭제 (10000건씩) -- [ ] SystemLog에 정리 완료 로그 +- [x] 배치 단위 삭제 (10000건씩) +- [x] SystemLog에 정리 완료 로그 --- @@ -1822,28 +1824,25 @@ Branch: feature/#XX-data-retention 제목: [Feature] Redis 토큰 캐시 관리 Labels: Type/Feature, Priority/Medium, Status/Available Milestone: Phase 3-2: 통계 & Webhook & 배치 -Branch: feature/#XX-redis-token-cache +Branch: feature/#154-redis-token-cache +Gitea Issue: #154 ``` -**상태**: ⬜ 대기 +**상태**: ✅ 완료 (PR #155 머지됨) **설명**: 디바이스 토큰을 Redis에 캐싱하여 DB 조회를 최소화한다. -> **📌 참조**: MEMORY.md Redis 설정 - **체크리스트 — Infrastructure Layer**: -- [ ] `SPMS.Infrastructure/Caching/TokenCacheService.cs` — 토큰 캐시 관리 - - Key 형식: `device:token:{serviceId}:{userId}` +- [x] `SPMS.Application/Interfaces/ITokenCacheService.cs` — 인터페이스 + CachedDeviceInfo record +- [x] `SPMS.Infrastructure/Caching/TokenCacheService.cs` — Redis 기반 구현 + - Key 형식: `device:token:{serviceId}:{deviceId}` - TTL: 1시간 - - GetDeviceTokenAsync(serviceId, userId) → string? - - SetDeviceTokenAsync(serviceId, userId, token) - - InvalidateAsync(serviceId, userId) - - InvalidateByServiceAsync(serviceId) — 서비스 전체 무효화 + - GetDeviceInfoAsync / SetDeviceInfoAsync / InvalidateAsync / InvalidateByServiceAsync **체크리스트 — PushWorker 연동**: -- [ ] 발송 시 Redis 캐시 우선 조회 -- [ ] 캐시 미스 시 DB 조회 후 캐시 저장 -- [ ] Device 삭제/비활성 시 캐시 무효화 +- [x] 발송 시 Redis 캐시 우선 조회 (single 발송) +- [x] 캐시 미스 시 DB 조회 후 캐시 저장 +- [x] Device 등록/수정/삭제/수신동의 변경 시 캐시 무효화 ---