improvement: PushWorker 웹훅 발송 연동 (#158)

- PushWorker에 IWebhookService 의존성 주입
- 발송 완료 후 push_sent/push_failed 이벤트 웹훅 호출
- TASKS.md API 커버리지 테이블 업데이트 (65/65 완료)

Closes #158
This commit is contained in:
SEAN 2026-02-11 11:07:04 +09:00
parent f972982b85
commit 8b1ae4dc02
2 changed files with 51 additions and 34 deletions

View File

@ -26,6 +26,7 @@ public class PushWorker : BackgroundService
private readonly IDuplicateChecker _duplicateChecker; private readonly IDuplicateChecker _duplicateChecker;
private readonly IBulkJobStore _bulkJobStore; private readonly IBulkJobStore _bulkJobStore;
private readonly ITokenCacheService _tokenCache; private readonly ITokenCacheService _tokenCache;
private readonly IWebhookService _webhookService;
private readonly IFcmSender _fcmSender; private readonly IFcmSender _fcmSender;
private readonly IApnsSender _apnsSender; private readonly IApnsSender _apnsSender;
private readonly ILogger<PushWorker> _logger; private readonly ILogger<PushWorker> _logger;
@ -37,6 +38,7 @@ public class PushWorker : BackgroundService
IDuplicateChecker duplicateChecker, IDuplicateChecker duplicateChecker,
IBulkJobStore bulkJobStore, IBulkJobStore bulkJobStore,
ITokenCacheService tokenCache, ITokenCacheService tokenCache,
IWebhookService webhookService,
IFcmSender fcmSender, IFcmSender fcmSender,
IApnsSender apnsSender, IApnsSender apnsSender,
ILogger<PushWorker> logger) ILogger<PushWorker> logger)
@ -47,6 +49,7 @@ public class PushWorker : BackgroundService
_duplicateChecker = duplicateChecker; _duplicateChecker = duplicateChecker;
_bulkJobStore = bulkJobStore; _bulkJobStore = bulkJobStore;
_tokenCache = tokenCache; _tokenCache = tokenCache;
_webhookService = webhookService;
_fcmSender = fcmSender; _fcmSender = fcmSender;
_apnsSender = apnsSender; _apnsSender = apnsSender;
_logger = logger; _logger = logger;
@ -262,6 +265,21 @@ public class PushWorker : BackgroundService
"푸시 발송 완료: requestId={RequestId}, 성공={Success}, 실패={Fail}, 총={Total}", "푸시 발송 완료: requestId={RequestId}, 성공={Success}, 실패={Fail}, 총={Total}",
pushMessage.RequestId, successCount, failCount, allResults.Count); 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 진행률 업데이트 // [7] Bulk job 진행률 업데이트
if (!string.IsNullOrEmpty(pushMessage.JobId)) if (!string.IsNullOrEmpty(pushMessage.JobId))
{ {

View File

@ -21,11 +21,11 @@
| **Account** | 9 | 9 | 0 | Phase 2-1 + 3-2 ✅ | | **Account** | 9 | 9 | 0 | Phase 2-1 + 3-2 ✅ |
| **Service** | 13 | 13 | 0 | Phase 2-1 ✅ | | **Service** | 13 | 13 | 0 | Phase 2-1 ✅ |
| **Device** | 7 | 7 | 0 | Phase 2-2 ✅ | | **Device** | 7 | 7 | 0 | Phase 2-2 ✅ |
| **Message** | 5 | 1 | 4 | Phase 3 ← 다음 | | **Message** | 5 | 5 | 0 | Phase 3 ✅ |
| **Push** | 8 | 5 | 3 | Phase 3 | | **Push** | 8 | 8 | 0 | Phase 3 ✅ |
| **Stats** | 5 | 0 | 5 | Phase 3-2 | | **Stats** | 5 | 5 | 0 | Phase 3-2 ✅ |
| **File** | 6 | 6 | 0 | Phase 2-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 | ✅ | | 10 | [Feature] 웹훅 설정 API | Feature | High | WHK-01 | ✅ |
| 11 | [Feature] **웹훅 발송 서비스** | Feature | High | WHK-02 | ✅ | | 11 | [Feature] **웹훅 발송 서비스** | Feature | High | WHK-02 | ✅ |
| 12 | [Feature] **DailyStatWorker 구현** | Feature | Medium | AAG-01 | ✅ | | 12 | [Feature] **DailyStatWorker 구현** | Feature | Medium | AAG-01 | ✅ |
| 13 | [Feature] **DeadTokenCleanupWorker 구현** | Feature | Medium | DTK-01 | | | 13 | [Feature] **DeadTokenCleanupWorker 구현** | Feature | Medium | DTK-01 | |
| 14 | [Feature] **데이터 보관 주기 관리 배치** | Feature | Medium | RET-01 | | | 14 | [Feature] **데이터 보관 주기 관리 배치** | Feature | Medium | RET-01 | |
| 15 | [Feature] **Redis 토큰 캐시 관리** | Feature | Medium | - | | | 15 | [Feature] **Redis 토큰 캐시 관리** | Feature | Medium | - | |
--- ---
@ -1750,17 +1750,18 @@ Branch: feature/#XX-daily-stat-worker
제목: [Feature] DeadTokenCleanupWorker 구현 (비활성 토큰 정리) 제목: [Feature] DeadTokenCleanupWorker 구현 (비활성 토큰 정리)
Labels: Type/Feature, Priority/Medium, Status/Available Labels: Type/Feature, Priority/Medium, Status/Available
Milestone: Phase 3-2: 통계 & Webhook & 배치 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를 구현한다. **설명**: 매주 일요일 03:00 KST에 비활성 상태로 7일 이상 경과한 Device 토큰을 물리 삭제하는 BackgroundService Worker를 구현한다.
> **📌 참조**: `Documents/BatchScheduler_Design.md` §5 (DeadTokenCleanupWorker) > **📌 참조**: `Documents/BatchScheduler_Design.md` §5 (DeadTokenCleanupWorker)
**체크리스트 — Worker 구현**: **체크리스트 — Worker 구현**:
- [ ] `SPMS.Infrastructure/Workers/DeadTokenCleanupWorker.cs` — BackgroundService 상속 - [x] `SPMS.Infrastructure/Workers/DeadTokenCleanupWorker.cs` — BackgroundService 상속
- Cron 스케줄: 매주 일요일 03:00 KST - Cron 스케줄: 매주 일요일 03:00 KST
- 삭제 대상 조회: - 삭제 대상 조회:
```sql ```sql
@ -1777,17 +1778,17 @@ Branch: feature/#XX-dead-token-cleanup
LIMIT 1000 LIMIT 1000
``` ```
- 삭제된 행 > 0 → 반복, = 0 → 완료 - 삭제된 행 > 0 → 반복, = 0 → 완료
- Redis 토큰 캐시 무효화 (삭제된 Device 기반) - Redis 토큰 캐시 무효화 #15 구현 후 연동 예정
- SystemLog에 정리 완료 로그 - SystemLog에 정리 완료 로그
**체크리스트 — 안전장치**: **체크리스트 — 안전장치**:
- [ ] 배치 단위 삭제 (1000건씩) → DB 부하 분산 - [x] 배치 단위 삭제 (1000건씩) → DB 부하 분산
- [ ] 삭제 전 카운트 로깅 - [x] 삭제 전 카운트 로깅
- [ ] 비정상 수치 감지 시 중단 (전체의 50% 이상이면 경고) - [x] 비정상 수치 감지 시 중단 (전체의 50% 이상이면 경고)
**체크리스트 — 등록**: **체크리스트 — 등록**:
- [ ] `Program.cs``builder.Services.AddHostedService<DeadTokenCleanupWorker>()` - [x] `SPMS.Infrastructure/DependencyInjection.cs``AddHostedService<DeadTokenCleanupWorker>()`
- [ ] 환경별 활성화 설정 (Debug/Staging: 비활성, Release: 활성) - [ ] 환경별 활성화 설정 (Debug/Staging: 비활성, Release: 활성) → MVP Phase에서 설정
--- ---
@ -1797,22 +1798,23 @@ Branch: feature/#XX-dead-token-cleanup
제목: [Feature] 데이터 보관 주기 관리 배치 제목: [Feature] 데이터 보관 주기 관리 배치
Labels: Type/Feature, Priority/Medium, Status/Available Labels: Type/Feature, Priority/Medium, Status/Available
Milestone: Phase 3-2: 통계 & Webhook & 배치 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 - Cron 스케줄: 매일 04:00 KST
- PushSendLog: 90일 이전 데이터 삭제 - PushSendLog: 90일 이전 데이터 삭제
- PushOpenLog: 90일 이전 데이터 삭제 - PushOpenLog: 90일 이전 데이터 삭제
- WebhookLog: 30일 이전 데이터 삭제 - WebhookLog: 30일 이전 데이터 삭제
- SystemLog: 180일 이전 데이터 삭제 - SystemLog: 180일 이전 데이터 삭제
- [ ] 배치 단위 삭제 (10000건씩) - [x] 배치 단위 삭제 (10000건씩)
- [ ] SystemLog에 정리 완료 로그 - [x] SystemLog에 정리 완료 로그
--- ---
@ -1822,28 +1824,25 @@ Branch: feature/#XX-data-retention
제목: [Feature] Redis 토큰 캐시 관리 제목: [Feature] Redis 토큰 캐시 관리
Labels: Type/Feature, Priority/Medium, Status/Available Labels: Type/Feature, Priority/Medium, Status/Available
Milestone: Phase 3-2: 통계 & Webhook & 배치 Milestone: Phase 3-2: 통계 & Webhook & 배치
Branch: feature/#XX-redis-token-cache Branch: feature/#154-redis-token-cache
Gitea Issue: #154
``` ```
**상태**: ⬜ 대기 **상태**: ✅ 완료 (PR #155 머지됨)
**설명**: 디바이스 토큰을 Redis에 캐싱하여 DB 조회를 최소화한다. **설명**: 디바이스 토큰을 Redis에 캐싱하여 DB 조회를 최소화한다.
> **📌 참조**: MEMORY.md Redis 설정
**체크리스트 — Infrastructure Layer**: **체크리스트 — Infrastructure Layer**:
- [ ] `SPMS.Infrastructure/Caching/TokenCacheService.cs` — 토큰 캐시 관리 - [x] `SPMS.Application/Interfaces/ITokenCacheService.cs` — 인터페이스 + CachedDeviceInfo record
- Key 형식: `device:token:{serviceId}:{userId}` - [x] `SPMS.Infrastructure/Caching/TokenCacheService.cs` — Redis 기반 구현
- Key 형식: `device:token:{serviceId}:{deviceId}`
- TTL: 1시간 - TTL: 1시간
- GetDeviceTokenAsync(serviceId, userId) → string? - GetDeviceInfoAsync / SetDeviceInfoAsync / InvalidateAsync / InvalidateByServiceAsync
- SetDeviceTokenAsync(serviceId, userId, token)
- InvalidateAsync(serviceId, userId)
- InvalidateByServiceAsync(serviceId) — 서비스 전체 무효화
**체크리스트 — PushWorker 연동**: **체크리스트 — PushWorker 연동**:
- [ ] 발송 시 Redis 캐시 우선 조회 - [x] 발송 시 Redis 캐시 우선 조회 (single 발송)
- [ ] 캐시 미스 시 DB 조회 후 캐시 저장 - [x] 캐시 미스 시 DB 조회 후 캐시 저장
- [ ] Device 삭제/비활성 시 캐시 무효화 - [x] Device 등록/수정/삭제/수신동의 변경 시 캐시 무효화
--- ---