diff --git a/src/main/java/com/sesac/boheommong/domain/bookmark/service/BookmarkServiceImpl.java b/src/main/java/com/sesac/boheommong/domain/bookmark/service/BookmarkServiceImpl.java index dcdee47..7d574d8 100644 --- a/src/main/java/com/sesac/boheommong/domain/bookmark/service/BookmarkServiceImpl.java +++ b/src/main/java/com/sesac/boheommong/domain/bookmark/service/BookmarkServiceImpl.java @@ -6,6 +6,7 @@ import com.sesac.boheommong.domain.bookmark.swagger.*; import com.sesac.boheommong.domain.insurance.entity.InsuranceProduct; import com.sesac.boheommong.domain.insurance.repository.InsuranceProductRepository; +import com.sesac.boheommong.domain.notification.service.NotificationService; import com.sesac.boheommong.domain.user.entity.User; import com.sesac.boheommong.domain.user.repository.UserRepository; import com.sesac.boheommong.global.exception.BaseException; @@ -25,6 +26,7 @@ public class BookmarkServiceImpl implements BookmarkService { private final BookmarkRepository bookmarkRepository; private final UserRepository userRepository; private final InsuranceProductRepository insuranceProductRepository; + private final NotificationService notificationService; /** * 특정 상품이 북마크되었는지 여부 조회 @@ -86,10 +88,21 @@ public void addOrCancelBookmark(String loginEmail, Long productId) { // (A) 이미 북마크가 존재하면 -> 물리 삭제 bookmarkRepository.delete(optionalBookmark.get()); // 실제 DB row가 삭제되므로 중복키 충돌 안 남. + notificationService.publishNotification( + user.getUserId(), // 알림 수신자 ID + "북마크가 해제되었습니다.", // 알림 내용 + "/mypage/bookmark" // 클릭 시 이동할 URL (예시) + ); } else { // (B) 없으면 -> 새 북마크 생성 Bookmark bookmark = Bookmark.create(user, product); bookmarkRepository.save(bookmark); + + notificationService.publishNotification( + user.getUserId(), + "북마크가 추가되었습니다.", + "/mypage/bookmark" + ); } } diff --git a/src/main/java/com/sesac/boheommong/domain/notification/controller/NotificationController.java b/src/main/java/com/sesac/boheommong/domain/notification/controller/NotificationController.java index f17ca4d..6a64cdb 100644 --- a/src/main/java/com/sesac/boheommong/domain/notification/controller/NotificationController.java +++ b/src/main/java/com/sesac/boheommong/domain/notification/controller/NotificationController.java @@ -4,6 +4,8 @@ import com.sesac.boheommong.domain.notification.enums.NotificationType; import com.sesac.boheommong.domain.notification.service.NotificationService; import com.sesac.boheommong.domain.notification.swagger.*; +import com.sesac.boheommong.global.exception.BaseException; +import com.sesac.boheommong.global.exception.error.ErrorCode; import com.sesac.boheommong.global.jwt.service.TokenProvider; import com.sesac.boheommong.global.response.Response; import jakarta.servlet.http.HttpServletRequest; @@ -27,13 +29,17 @@ public class NotificationController { @SubscribeNotification @GetMapping("/subscribe") public SseEmitter subscribe( - HttpServletRequest request, + @RequestParam("token") String token, // <-- 추가 @RequestHeader(value = "Last-Event-ID", required = false, defaultValue = "") String lastEventId ) { - // 1) 로그인 이메일 추출 - String loginEmail = tokenProvider.getUserLoginEmail(request); + // (1) 토큰 검증 & 로그인 이메일 추출 + if (!tokenProvider.validToken(token)) { + throw BaseException.from(ErrorCode.TOKEN_EXPIRED); + } + // Overload한 getUserLoginEmail(token) 호출 + String loginEmail = tokenProvider.getUserLoginEmail(token); - // 2) Service 호출 + // (2) SSE 구독 return notificationService.subscribe(loginEmail, lastEventId); } diff --git a/src/main/java/com/sesac/boheommong/domain/notification/service/NotificationService.java b/src/main/java/com/sesac/boheommong/domain/notification/service/NotificationService.java index a5de66b..8942207 100644 --- a/src/main/java/com/sesac/boheommong/domain/notification/service/NotificationService.java +++ b/src/main/java/com/sesac/boheommong/domain/notification/service/NotificationService.java @@ -48,7 +48,10 @@ public SseEmitter subscribe(String loginEmail, String lastEventId) { emitter.onTimeout(() -> emitterRepository.deleteById(emitterId)); // 최초 연결 시 더미데이터가 없으면 503 오류가 발생하기 때문에 해당 더미 데이터 생성 - sendToClient(emitter,emitterId, "EventStream Created. [userId=" + userId + "]"); + Response> dummy = Response.success( + Map.of("message", "EventStream Created. [userId=" + userId + "]") + ); + sendToClient(emitter, emitterId, dummy); // SSE의 자동 재연결 기능 때문에 재연결시 lastEventId를 기준으로 이후의 이벤트들만 전송 if (!lastEventId.isEmpty()) { diff --git a/src/main/java/com/sesac/boheommong/global/exception/error/ErrorCode.java b/src/main/java/com/sesac/boheommong/global/exception/error/ErrorCode.java index ffdaeba..92071b8 100644 --- a/src/main/java/com/sesac/boheommong/global/exception/error/ErrorCode.java +++ b/src/main/java/com/sesac/boheommong/global/exception/error/ErrorCode.java @@ -10,6 +10,7 @@ public enum ErrorCode { // user USER_NOT_FOUND("USER-0000", "해당 회원이 존재하지 않습니다.", ErrorDisplayType.POPUP), INVALID_PERMISSION("USER-0001", "허용된 접근이 아닙니다.", ErrorDisplayType.POPUP), + TOKEN_EXPIRED("USER-0002", "유효하지 않은 토큰입니다.", ErrorDisplayType.POPUP), // user health USER_HEALTH_ALREADY_EXISTS("HEALTH-0001", "해당 유저의 건강정보가 이미 존재합니다.", ErrorDisplayType.POPUP), diff --git a/src/main/java/com/sesac/boheommong/global/jwt/service/TokenProvider.java b/src/main/java/com/sesac/boheommong/global/jwt/service/TokenProvider.java index 865bde8..918c8c3 100644 --- a/src/main/java/com/sesac/boheommong/global/jwt/service/TokenProvider.java +++ b/src/main/java/com/sesac/boheommong/global/jwt/service/TokenProvider.java @@ -89,6 +89,11 @@ public String getUserLoginEmail(HttpServletRequest request) { return null; } + public String getUserLoginEmail(String token) { + Claims claims = getClaims(token); // 아래의 getClaims(token) + return claims.getSubject(); // subject = loginEmail + } + // 토큰의 클레임 반환 public Claims getClaims(String token) { return Jwts.parser() diff --git a/src/main/java/com/sesac/boheommong/infra/redis/NotificationMessage.java b/src/main/java/com/sesac/boheommong/infra/redis/NotificationMessage.java index 0888ea3..a04e31c 100644 --- a/src/main/java/com/sesac/boheommong/infra/redis/NotificationMessage.java +++ b/src/main/java/com/sesac/boheommong/infra/redis/NotificationMessage.java @@ -1,5 +1,6 @@ package com.sesac.boheommong.infra.redis; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.sesac.boheommong.domain.notification.entity.Notification; import com.sesac.boheommong.domain.notification.enums.NotificationType; import lombok.*; @@ -9,6 +10,7 @@ @NoArgsConstructor @AllArgsConstructor @Builder +@JsonIgnoreProperties(ignoreUnknown = true) public class NotificationMessage { // 알림 ID (이미 DB에 저장된 경우)