Skip to content

Commit 136de6c

Browse files
authored
Merge pull request #171 from IT-Cotato/feature/170
refactor: 할인 알림 및 SSE 알림 버그 수정
2 parents 7e39a29 + f21badc commit 136de6c

File tree

5 files changed

+28
-11
lines changed

5 files changed

+28
-11
lines changed

src/main/java/com/ongil/backend/domain/notification/repository/NotificationRepository.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package com.ongil.backend.domain.notification.repository;
22

3+
import java.time.LocalDateTime;
34
import java.util.List;
45

56
import org.springframework.data.jpa.repository.JpaRepository;
7+
import org.springframework.data.jpa.repository.Modifying;
8+
import org.springframework.data.jpa.repository.Query;
9+
import org.springframework.data.repository.query.Param;
610

711
import com.ongil.backend.domain.notification.entity.Notification;
812

@@ -13,4 +17,9 @@ public interface NotificationRepository extends JpaRepository<Notification, Long
1317

1418
// 해당 사용자의 읽지 않은 알림 개수 조회 (알림 아이콘 뱅지용)
1519
long countByUserIdAndIsReadFalse(Long userId);
20+
21+
// 해당 사용자의 읽지 않은 알림 전체 읽음 처리 (벌크 UPDATE)
22+
@Modifying
23+
@Query("UPDATE Notification n SET n.isRead = true, n.readAt = :now WHERE n.user.id = :userId AND n.isRead = false")
24+
void markAllAsRead(@Param("userId") Long userId, @Param("now") LocalDateTime now);
1625
}

src/main/java/com/ongil/backend/domain/notification/service/NotificationService.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.ongil.backend.domain.notification.service;
22

3+
import java.time.LocalDateTime;
34
import java.util.List;
45

56
import org.springframework.stereotype.Service;
@@ -51,12 +52,9 @@ public void markAsRead(Long userId, Long notificationId) {
5152
notification.markAsRead();
5253
}
5354

54-
// 모든 알림 읽음 처리
55+
// 모든 알림 읽음 처리 (벌크 UPDATE로 N+1 방지)
5556
@Transactional
5657
public void markAllAsRead(Long userId) {
57-
List<Notification> notifications = notificationRepository
58-
.findByUserIdAndIsReadFalseOrderByNotifiedAtDesc(userId);
59-
60-
notifications.forEach(Notification::markAsRead);
58+
notificationRepository.markAllAsRead(userId, LocalDateTime.now());
6159
}
6260
}

src/main/java/com/ongil/backend/domain/notification/service/NotificationSseService.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,19 @@ public SseEmitter subscribe(Long userId) {
3131
SseEmitter emitter = new SseEmitter(SSE_TIMEOUT);
3232
emitters.put(userId, emitter);
3333

34-
// 연결 종료 시 제거
34+
// 연결 종료 시 제거 (value 지정으로 새로 등록된 emitter를 잘못 제거하는 race condition 방지)
3535
emitter.onCompletion(() -> {
36-
emitters.remove(userId);
36+
emitters.remove(userId, emitter);
3737
log.info("SSE 연결 종료 - userId: {}", userId);
3838
});
3939

4040
emitter.onTimeout(() -> {
41-
emitters.remove(userId);
41+
emitters.remove(userId, emitter);
4242
log.info("SSE 타임아웃 - userId: {}", userId);
4343
});
4444

4545
emitter.onError(e -> {
46-
emitters.remove(userId);
46+
emitters.remove(userId, emitter);
4747
log.error("SSE 에러 - userId: {}", userId, e);
4848
});
4949

@@ -53,7 +53,7 @@ public SseEmitter subscribe(Long userId) {
5353
.name("connect")
5454
.data("SSE 연결 성공"));
5555
} catch (IOException e) {
56-
emitters.remove(userId);
56+
emitters.remove(userId, emitter);
5757
log.error("SSE 초기 이벤트 전송 실패 - userId: {}", userId, e);
5858
}
5959

@@ -76,7 +76,7 @@ public void sendNotification(Long userId, NotificationResponse notification) {
7676
.data(notification));
7777
log.info("SSE 알림 전송 성공 - userId: {}, notificationId: {}", userId, notification.getNotificationId());
7878
} catch (IOException e) {
79-
emitters.remove(userId);
79+
emitters.remove(userId, emitter);
8080
log.error("SSE 알림 전송 실패 - userId: {}", userId, e);
8181
}
8282
}

src/main/java/com/ongil/backend/domain/pricealert/service/PriceAlertService.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.ongil.backend.domain.pricealert.service;
22

3+
import java.util.List;
4+
35
import org.springframework.stereotype.Service;
46
import org.springframework.transaction.annotation.Transactional;
57

@@ -14,6 +16,7 @@
1416
import com.ongil.backend.domain.user.repository.UserRepository;
1517
import com.ongil.backend.global.common.exception.EntityNotFoundException;
1618
import com.ongil.backend.global.common.exception.ErrorCode;
19+
import com.ongil.backend.global.common.exception.ValidationException;
1720

1821
import lombok.RequiredArgsConstructor;
1922

@@ -30,9 +33,15 @@ public class PriceAlertService {
3033
* 사용자가 상품 상세 화면에서 원하는 할인가를 선택하여 DB에 저장
3134
* 실제 알림 발송은 PriceAlertScheduler가 주기적으로 가격을 확인하여 처리
3235
*/
36+
private static final List<Integer> ALLOWED_DISCOUNT_RATES = List.of(10, 20, 30, 40);
37+
3338
@Transactional
3439
public PriceAlert createOrUpdatePriceAlert(Long userId, PriceAlertRequest request) {
3540

41+
if (!ALLOWED_DISCOUNT_RATES.contains(request.getDiscountRate())) {
42+
throw new ValidationException(ErrorCode.INVALID_DISCOUNT_RATE);
43+
}
44+
3645
User user = userRepository.findById(userId)
3746
.orElseThrow(() -> new EntityNotFoundException(ErrorCode.USER_NOT_FOUND));
3847

src/main/java/com/ongil/backend/global/common/exception/ErrorCode.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public enum ErrorCode {
8888

8989
// PRICE_ALERT
9090
PRICE_ALERT_NOT_FOUND(HttpStatus.NOT_FOUND, "설정된 가격 알림이 없습니다.", "ALERT-001"),
91+
INVALID_DISCOUNT_RATE(HttpStatus.BAD_REQUEST, "할인율은 10, 20, 30, 40 중 하나여야 합니다.", "ALERT-002"),
9192

9293
// FILE / S3
9394
FILE_IS_EMPTY(HttpStatus.BAD_REQUEST, "파일이 비어 있습니다.", "FILE-001"),

0 commit comments

Comments
 (0)