feat: 실시간 알림 시스템 (Web Push + 알림 벨 + 서비스간 알림 연동)

- Phase 1: 모든 서비스 헤더에 알림 벨 UI 추가 (notification-bell.js)
- Phase 2: VAPID Web Push 구독/전송 (push-sw.js, pushSubscription API)
- Phase 3: 내부 알림 API + notifyHelper로 서비스간 알림 연동
  - tksafety: 출입 승인/반려, 안전교육 완료, 방문자 체크인
  - tkpurchase: 일용공 신청, 작업보고서 제출
  - system2-report: 신고 접수/확인/처리완료
- Phase 4: 30일 이상 알림 자동 정리 cron, Redis 캐싱
- CORS에 tkuser/tkpurchase/tksafety 서브도메인 추가
- HTML cache busting 버전 갱신

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-03-13 15:01:44 +09:00
parent 1ad82fd52c
commit 7fd646e9ba
102 changed files with 1446 additions and 94 deletions

View File

@@ -33,6 +33,9 @@ class App {
// 라우터 초기화
this.initializeRouter();
// 알림 벨 로드
this._loadNotificationBell();
// 대시보드 데이터 로드
await this.loadDashboardData();
@@ -392,6 +395,16 @@ class App {
}
}
/**
* 알림 벨 로드
*/
_loadNotificationBell() {
var h = window.location.hostname;
var s = document.createElement('script');
s.src = (h.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : window.location.protocol + '//' + h + ':30000') + '/shared/notification-bell.js?v=1';
document.head.appendChild(s);
}
/**
* 로딩 표시
*/

View File

@@ -1,7 +1,11 @@
// 서비스 워커 해제 (캐시 간섭으로 인한 인증 루프 방지)
// 서비스 워커 해제 (push-sw.js 제외)
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then(function(registrations) {
registrations.forEach(function(registration) { registration.unregister(); });
registrations.forEach(function(registration) {
if (!registration.active || !registration.active.scriptURL.includes('push-sw.js')) {
registration.unregister();
}
});
});
if (typeof caches !== 'undefined') {
caches.keys().then(function(names) {

View File

@@ -281,7 +281,9 @@ class PagePreloader {
try {
const registrations = await navigator.serviceWorker.getRegistrations();
for (const registration of registrations) {
await registration.unregister();
if (!registration.active || !registration.active.scriptURL.includes('push-sw.js')) {
await registration.unregister();
}
}
// 모든 캐시 삭제
const cacheNames = await caches.keys();