-
Notifications
You must be signed in to change notification settings - Fork 0
[deploy] v1.3.1 운영서버로 배포 #324
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…k-unread-one [feat] 안읽은 알림 여부 조회 api 구현
[Feat] 어플리케이션 단위 500 에러 디스코드로 알림 전송기능 개발
Walkthrough여러 예외 타입을 InternalServerException/ExternalApiException 중심으로 정리하고, MDC 기반 요청/사용자 ID 로깅과 Discord 웹훅 오류 알림을 도입했다. WebFlux/Logstash 의존성과 Logback 설정을 추가했다. 알림의 미확인 존재 여부 조회 API를 신규 추가하고 관련 포트/어댑터/서비스/테스트를 구현했다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant C as Client
participant Sec as SecurityFilterChain
participant MDC as MdcLoggingFilter
participant JWT as JwtAuthenticationFilter
participant Cn as NotificationQueryController
participant Svc as NotificationExistsUncheckedService
participant Port as NotificationQueryPort
participant Repo as NotificationQueryRepository
C->>Sec: HTTP GET /notifications/exists-unchecked
Sec->>MDC: doFilterInternal (set request_id)
MDC-->>Sec: proceed
Sec->>JWT: doFilterInternal (validate token, set auth, MDC user_id)
JWT-->>Sec: proceed
Sec-->>Cn: dispatch
Cn->>Svc: existsUnchecked(userId)
Svc->>Port: existsUnchecked(userId)
Port->>Repo: existsByUserIdAndIsCheckedFalse(userId)
Repo-->>Port: boolean
Port-->>Svc: boolean
Svc-->>Cn: boolean
Cn-->>C: 200 BaseResponse{requestId, data.exists}
sequenceDiagram
autonumber
participant C as Client
participant App as App Layer
participant GEH as GlobalExceptionHandler
participant MDC as MDC
participant DC as DiscordClient
participant DH as Discord Webhook
C->>App: HTTP Request
App--x App: Exception thrown
App->>GEH: handleServerErrors(e)
GEH->>MDC: get(request_id, user_id)
GEH->>DC: sendErrorMessage(message, stackTrace, requestId, userId)
alt env != test
DC->>DH: POST webhook (embed payload)
DH-->>DC: 204/2xx
else env == test
DC-->>GEH: skipped
end
GEH-->>C: 500 ErrorResponse{requestId}
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
Test Results481 tests 481 ✅ 44s ⏱️ Results for commit fc8ff1a. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/main/java/konkuk/thip/book/adapter/out/persistence/BookRedisAdapter.java (2)
57-67: topN=0일 때 전체 반환되는 경계 버그end 인덱스가 topN-1이므로 topN=0이면 end=-1 → 전체 범위가 반환될 수 있습니다. 사전 가드 필요.
private List<Map.Entry<String, Double>> getTopNFromZSet(String prefix, LocalDate date, int topN) { + if (topN <= 0) return Collections.emptyList(); String dateStr = date.format(DAILY_KEY_FORMATTER); String redisKey = prefix + dateStr; Set<ZSetOperations.TypedTuple<String>> topNSet = redisTemplate.opsForZSet() .reverseRangeWithScores(redisKey, 0, topN - 1);
103-112: 상세 랭크 키 TTL 누락 — Redis 메모리/보존 리스크rank(zset)는 7일 만료지만 detail(value)은 만료가 없습니다. 동일한 보존정책(예: 7일) 적용을 권장합니다.
redisTemplate.opsForValue().set(redisKey, detailJson); + redisTemplate.expire(redisKey, Duration.ofDays(7));src/main/java/konkuk/thip/common/security/filter/JwtAuthenticationFilter.java (1)
76-81: MDC에 "null" 문자열 저장 방지userId가 null일 때 String.valueOf로 "null"이 기록됩니다. null일 때는 넣지 않도록 가드해 주세요. DiscordClient의 "N/A" 처리 로직도 정상 동작합니다.
- MDC.put(USER_ID.getValue(), String.valueOf(loginUser.userId())); + if (loginUser.userId() != null) { + MDC.put(USER_ID.getValue(), String.valueOf(loginUser.userId())); + }src/main/java/konkuk/thip/book/adapter/out/api/naver/NaverApiUtil.java (1)
81-86: HTTP 오류 응답 처리 시 NPE 위험 (errorStream == null)HTTP 오류에서 getErrorStream이 null일 수 있어 readBody 호출 시 NPE가 납니다. null 처리와 명시적 예외 전환을 권장합니다.
- int responseCode = con.getResponseCode(); - if (responseCode == HttpURLConnection.HTTP_OK) { // 정상 호출 - return readBody(con.getInputStream()); - } else { // 오류 발생 - return readBody(con.getErrorStream()); - } + int responseCode = con.getResponseCode(); + InputStream responseStream = (responseCode == HttpURLConnection.HTTP_OK) + ? con.getInputStream() + : con.getErrorStream(); + if (responseStream == null) { + throw new ExternalApiException(BOOK_NAVER_API_RESPONSE_ERROR); + } + return readBody(responseStream);
🧹 Nitpick comments (16)
src/main/java/konkuk/thip/notification/adapter/out/persistence/repository/NotificationQueryRepositoryImpl.java (1)
50-59: 효율적인 존재 여부 확인 구현입니다.
selectOne()+fetchFirst()패턴을 사용하여 하나의 레코드만 조회하므로 성능상 효율적입니다.참고로, QueryDSL의 다른 존재 확인 패턴도 고려할 수 있습니다:
대안 1: 명시적 필드 선택
Long result = queryFactory .select(notification.notificationId) .from(notification) .where(notification.userJpaEntity.userId.eq(userId) .and(notification.isChecked.eq(false))) .fetchFirst(); return result != null;대안 2: count 사용 (단, 성능상 현재 방식이 더 효율적일 수 있음)
long count = queryFactory .selectFrom(notification) .where(notification.userJpaEntity.userId.eq(userId) .and(notification.isChecked.eq(false))) .fetchCount(); return count > 0;다만 현재 구현이 이미 충분히 효율적이므로 변경이 필수적이지는 않습니다.
src/test/java/konkuk/thip/notification/adapter/in/web/NotificationExistsUncheckedApiTest.java (2)
53-71: JdbcTemplate 직접 업데이트보다 엔티티 메서드 사용을 권장합니다.현재
JdbcTemplate을 사용해 직접 DB를 업데이트하고 있는데, 이는 JPA 엔티티 관리를 우회하여 잠재적으로 캐시 불일치를 유발할 수 있습니다. 테스트 컨텍스트에서는 동작하지만, 더 명시적인 접근을 위해NotificationJpaEntity의 메서드나 리포지토리를 통한 업데이트를 고려해보세요.예시:
- jdbcTemplate.update( - "UPDATE notifications SET is_checked = TRUE WHERE notification_id = ?", - n1.getNotificationId() - ); + n1.markAsChecked(); // 또는 적절한 엔티티 메서드 + notificationJpaRepository.save(n1);또는 전용 체크 처리 메서드가 있다면:
notificationJpaRepository.markAsChecked(n1.getNotificationId());
37-71: 추가 엣지 케이스 테스트를 고려해보세요.현재 테스트는 기본 시나리오를 잘 커버하고 있습니다. 아래 시나리오들도 추가하면 더 견고한 테스트가 될 수 있습니다:
- 알림이 전혀 없는 사용자의 경우
- 여러 알림 중 일부만 읽은 경우 (읽지 않은 알림이 여전히 존재)
- 모든 알림을 읽은 경우
src/main/resources/logback-spring.xml (1)
36-40: 롤링 정책 설정을 재검토하세요.
maxHistory가 10으로 설정되어 있어 최대 10일치 로그만 보관됩니다. 운영 환경에서 장애 분석이나 규정 준수를 위해 더 긴 보관 기간이 필요한지 확인하시기 바랍니다. 또한totalSizeCap을 추가하여 전체 로그 디스크 사용량을 제한하는 것을 권장합니다.<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>${LOG_PATH}/info/info.%d{yyyy-MM-dd}.%i.log</fileNamePattern> <maxFileSize>10MB</maxFileSize> - <maxHistory>10</maxHistory> + <maxHistory>30</maxHistory> + <totalSizeCap>1GB</totalSizeCap> </rollingPolicy>src/main/java/konkuk/thip/config/AwsS3ImageUrlInitializer.java (1)
21-24: 빈 값 체크 강화 및 메시지 개선 제안null/empty 대신 hasText로 공백만 있는 값도 차단하세요. 또한 환경키를 로그/메시지에 포함하면 원인 파악이 빨라집니다.
- if (baseUrl == null || baseUrl.isEmpty()) { - throw new InternalServerException(AWS_BUCKET_BASE_URL_NOT_CONFIGURED); + if (!org.springframework.util.StringUtils.hasText(baseUrl)) { + throw new InternalServerException(AWS_BUCKET_BASE_URL_NOT_CONFIGURED); }src/main/java/konkuk/thip/notification/domain/value/NotificationRedirectSpecConverter.java (1)
17-18: ObjectMapper 정합성 유지 제안전역 모듈(예: JavaTime, Kotlin, custom mixin) 설정을 공유하려면 static ObjectMapper 대신 Spring 빈을 주입받는 방식으로 전환을 고려해 주세요.
src/main/java/konkuk/thip/common/logging/MdcLoggingFilter.java (1)
24-33: X-Request-ID를 응답 헤더로도 전파하세요클라이언트/로그 상관관계를 위해 response 헤더 설정을 권장합니다.
MDC.put(REQUEST_ID.getValue(), requestId); + response.setHeader("X-Request-ID", requestId); filterChain.doFilter(request, response);src/main/java/konkuk/thip/message/adapter/out/firebase/FirebaseAdapter.java (1)
86-88: 프로파일 판별 로직 개선 제안server.profile 문자열 대신 Spring Environment의 활성 프로파일을 활용하면 오탑재를 줄일 수 있습니다(예: Arrays.asList(env.getActiveProfiles()).contains("dev")).
src/main/java/konkuk/thip/common/exception/FirebaseException.java (1)
5-16: errorCode 게터 추가 제안전역 예외 핸들러/디스코드 알림에서 코드 접근이 필요할 수 있습니다. 간단한 게터를 추가해 두면 활용성이 높습니다.
public class FirebaseException extends RuntimeException { private final ErrorCode errorCode; public FirebaseException(ErrorCode errorCode) { super(errorCode.getMessage()); this.errorCode = errorCode; } public FirebaseException(ErrorCode errorCode, Exception e) { super(errorCode.getMessage(), e); this.errorCode = errorCode; } + + public ErrorCode getErrorCode() { + return errorCode; + } }src/main/java/konkuk/thip/common/dto/BaseResponse.java (2)
11-14: requestId 직렬화 최적화 제안 (null 생략)requestId가 없는 경우 응답 JSON에서 필드를 생략하면 노이즈가 줄어듭니다. 클래스에 NON_NULL 적용을 권장합니다.
import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonInclude; @@ -@JsonPropertyOrder({"success", "code", "message", "requestId", "data"}) +@JsonPropertyOrder({"success", "code", "message", "requestId", "data"}) +@JsonInclude(JsonInclude.Include.NON_NULL) public class BaseResponse<T> {Also applies to: 23-27
6-11: 사용되지 않는 로거 제거 제안이 클래스에서 log를 사용하지 않습니다. @slf4j 어노테이션과 import 제거로 바이트코드/필드 생성 최소화가 가능합니다.
src/main/java/konkuk/thip/common/security/filter/JwtAuthenticationFilter.java (1)
116-133: Bearer 토큰 파싱 안전성 개선 및 로그 레벨 조정"Bearer"만 온 경우 split 인덱스 오류 가능성이 있습니다. 접두사 길이 기반으로 안전하게 파싱하고 토큰 부재 로그는 debug로 낮추는 것을 권장합니다(필터 미적용 엔드포인트 외 401 요청에서 로그 오염 방지).
- String authorization = request.getHeader(JWT_HEADER_KEY.getValue()); - if (authorization != null && authorization.startsWith(JWT_PREFIX.getValue())) { - return authorization.split(" ")[1]; - } + String authorization = request.getHeader(JWT_HEADER_KEY.getValue()); + String prefix = JWT_PREFIX.getValue() + " "; + if (authorization != null && authorization.startsWith(prefix) && authorization.length() > prefix.length()) { + return authorization.substring(prefix.length()).trim(); + } @@ - log.info("토큰이 없습니다."); + log.debug("토큰이 없습니다."); return null;src/main/java/konkuk/thip/common/exception/InternalServerException.java (1)
5-17: errorCode 접근자 추가 제안핸들러/로그에서 errorCode 조회가 필요할 수 있습니다. Getter 추가를 권장합니다.
package konkuk.thip.common.exception; import konkuk.thip.common.exception.code.ErrorCode; +import lombok.Getter; +@Getter public class InternalServerException extends RuntimeException { private final ErrorCode errorCode;src/main/java/konkuk/thip/book/adapter/out/api/naver/NaverApiUtil.java (1)
95-104: 외부 호출 안정성: 연결/읽기 타임아웃 설정기본 타임아웃이 무제한일 수 있어 장애 전파 위험이 큽니다. 적절한 타임아웃을 설정해 주세요(예: 2~5초).
- URL url = new URL(apiUrl); - return (HttpURLConnection)url.openConnection(); + URL url = new URL(apiUrl); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setConnectTimeout(3000); + conn.setReadTimeout(5000); + return conn;Based on learnings
src/main/java/konkuk/thip/common/dto/ErrorResponse.java (1)
10-12: requestId null 생략으로 응답 간결화requestId가 없을 때 필드 생략을 권장합니다. NON_NULL을 추가해 주세요.
import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonInclude; @@ -@JsonPropertyOrder({"success", "code", "message", "requestId"}) +@JsonPropertyOrder({"success", "code, "message", "requestId"}) +@JsonInclude(JsonInclude.Include.NON_NULL) public class ErrorResponse {Also applies to: 21-28
src/main/java/konkuk/thip/common/exception/handler/GlobalExceptionHandler.java (1)
134-144: 500 알림 운영 측면: 샘플링/레이트 리밋 고려트래픽 급증 시 동일 오류가 Discord로 폭주할 수 있습니다. 동일 예외/엔드포인트 기준 샘플링(예: 1분당 N건) 또는 버퍼링을 고려해 주세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (34)
build.gradle(1 hunks)src/main/java/konkuk/thip/TestController.java(1 hunks)src/main/java/konkuk/thip/book/adapter/out/api/aladin/AladinApiUtil.java(2 hunks)src/main/java/konkuk/thip/book/adapter/out/api/naver/NaverApiUtil.java(5 hunks)src/main/java/konkuk/thip/book/adapter/out/persistence/BookRedisAdapter.java(3 hunks)src/main/java/konkuk/thip/common/aop/StatusFilterAspect.java(2 hunks)src/main/java/konkuk/thip/common/discord/DiscordClient.java(1 hunks)src/main/java/konkuk/thip/common/dto/BaseResponse.java(2 hunks)src/main/java/konkuk/thip/common/dto/ErrorResponse.java(3 hunks)src/main/java/konkuk/thip/common/exception/FirebaseException.java(1 hunks)src/main/java/konkuk/thip/common/exception/InternalServerException.java(1 hunks)src/main/java/konkuk/thip/common/exception/code/ErrorCode.java(2 hunks)src/main/java/konkuk/thip/common/exception/handler/GlobalExceptionHandler.java(3 hunks)src/main/java/konkuk/thip/common/logging/LoggingConstant.java(1 hunks)src/main/java/konkuk/thip/common/logging/MdcLoggingFilter.java(1 hunks)src/main/java/konkuk/thip/common/security/filter/JwtAuthenticationFilter.java(4 hunks)src/main/java/konkuk/thip/common/security/oauth2/CustomSuccessHandler.java(2 hunks)src/main/java/konkuk/thip/common/security/oauth2/tokenstorage/RedisLoginTokenStorage.java(2 hunks)src/main/java/konkuk/thip/config/AwsS3ImageUrlInitializer.java(2 hunks)src/main/java/konkuk/thip/config/SecurityConfig.java(3 hunks)src/main/java/konkuk/thip/message/adapter/out/firebase/FirebaseAdapter.java(3 hunks)src/main/java/konkuk/thip/notification/adapter/in/web/NotificationQueryController.java(3 hunks)src/main/java/konkuk/thip/notification/adapter/in/web/response/NotificationUncheckedExistsResponse.java(1 hunks)src/main/java/konkuk/thip/notification/adapter/out/persistence/NotificationQueryPersistenceAdapter.java(1 hunks)src/main/java/konkuk/thip/notification/adapter/out/persistence/repository/NotificationQueryRepository.java(1 hunks)src/main/java/konkuk/thip/notification/adapter/out/persistence/repository/NotificationQueryRepositoryImpl.java(1 hunks)src/main/java/konkuk/thip/notification/application/port/in/NotificationExistsUncheckedUseCase.java(1 hunks)src/main/java/konkuk/thip/notification/application/port/out/NotificationQueryPort.java(1 hunks)src/main/java/konkuk/thip/notification/application/service/NotificationExistsUncheckedService.java(1 hunks)src/main/java/konkuk/thip/notification/domain/value/NotificationRedirectSpecConverter.java(3 hunks)src/main/java/konkuk/thip/user/adapter/out/persistence/UserTokenBlacklistRedisAdapter.java(2 hunks)src/main/java/konkuk/thip/user/domain/User.java(2 hunks)src/main/resources/logback-spring.xml(1 hunks)src/test/java/konkuk/thip/notification/adapter/in/web/NotificationExistsUncheckedApiTest.java(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#113
File: src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java:38-44
Timestamp: 2025-07-30T14:05:04.945Z
Learning: seongjunnoh는 코드 최적화 제안에 대해 구체적인 기술적 근거와 효율성 차이를 이해하고 싶어하며, 성능 개선 방식에 대한 상세한 설명을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#93
File: src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java:49-114
Timestamp: 2025-07-28T16:44:31.224Z
Learning: seongjunnoh는 코드 중복 문제에 대한 리팩토링 제안을 적극적으로 수용하고 함수형 인터페이스를 활용한 해결책을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#285
File: src/main/java/konkuk/thip/room/adapter/out/jpa/RoomStatus.java:1-7
Timestamp: 2025-08-31T05:25:14.845Z
Learning: seongjunnoh는 enum 의존성에 대해 유연한 접근을 선호하며, 도메인→어댑터 레이어 참조와 같은 아키텍처 layering 원칙보다 실용적인 구현을 우선시한다.
🧬 Code graph analysis (11)
src/main/java/konkuk/thip/config/AwsS3ImageUrlInitializer.java (1)
src/main/java/konkuk/thip/common/exception/InternalServerException.java (1)
InternalServerException(5-17)
src/main/java/konkuk/thip/common/security/oauth2/tokenstorage/RedisLoginTokenStorage.java (1)
src/main/java/konkuk/thip/common/exception/InternalServerException.java (1)
InternalServerException(5-17)
src/main/java/konkuk/thip/book/adapter/out/persistence/BookRedisAdapter.java (1)
src/main/java/konkuk/thip/common/exception/InternalServerException.java (1)
InternalServerException(5-17)
src/main/java/konkuk/thip/notification/domain/value/NotificationRedirectSpecConverter.java (1)
src/main/java/konkuk/thip/common/exception/InternalServerException.java (1)
InternalServerException(5-17)
src/main/java/konkuk/thip/book/adapter/out/api/naver/NaverApiUtil.java (1)
src/main/java/konkuk/thip/common/exception/InternalServerException.java (1)
InternalServerException(5-17)
src/main/java/konkuk/thip/common/aop/StatusFilterAspect.java (1)
src/main/java/konkuk/thip/common/exception/InternalServerException.java (1)
InternalServerException(5-17)
src/test/java/konkuk/thip/notification/adapter/in/web/NotificationExistsUncheckedApiTest.java (1)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
TestEntityFactory(35-403)
src/main/java/konkuk/thip/user/adapter/out/persistence/UserTokenBlacklistRedisAdapter.java (1)
src/main/java/konkuk/thip/common/exception/InternalServerException.java (1)
InternalServerException(5-17)
src/main/java/konkuk/thip/common/dto/BaseResponse.java (1)
src/main/java/konkuk/thip/common/dto/ErrorResponse.java (1)
Getter(10-43)
src/main/java/konkuk/thip/user/domain/User.java (1)
src/main/java/konkuk/thip/common/exception/InternalServerException.java (1)
InternalServerException(5-17)
src/main/java/konkuk/thip/common/security/oauth2/CustomSuccessHandler.java (1)
src/main/java/konkuk/thip/common/exception/InternalServerException.java (1)
InternalServerException(5-17)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (24)
src/main/java/konkuk/thip/TestController.java (2)
13-13: 클래스명 변경이 적절합니다.
TestTokenController에서TestController로의 이름 변경은 컨트롤러의 책임 범위가 확장된 것을 반영하여 더 일반적인 이름을 사용한 것으로 보입니다.
22-25: 테스트용 500 에러 엔드포인트 구현 및 설정 확인
- 운영 환경 설정에
thip.test-api.enabled가 명시되지 않아 기본값(false)로 비활성화됨을 확인했습니다.- 해당 엔드포인트가 GlobalExceptionHandler를 통해 Discord 알림을 정상적으로 트리거하는지 검증 부탁드립니다.
src/main/java/konkuk/thip/notification/adapter/in/web/response/NotificationUncheckedExistsResponse.java (1)
1-9: LGTM!불변 응답 DTO로 record를 사용한 것이 적절하며, 정적 팩토리 메서드를 통한 인스턴스 생성 방식도 명확합니다.
src/main/java/konkuk/thip/notification/adapter/out/persistence/NotificationQueryPersistenceAdapter.java (1)
43-46: LGTM!포트 인터페이스 구현이 올바르며, 리포지토리로의 위임 로직이 명확합니다.
src/main/java/konkuk/thip/notification/adapter/out/persistence/repository/NotificationQueryRepository.java (1)
15-15: LGTM!메서드 시그니처가 명확하며, Spring Data 네이밍 컨벤션을 잘 따르고 있습니다.
src/main/java/konkuk/thip/notification/application/port/out/NotificationQueryPort.java (1)
15-15: LGTM!포트 인터페이스 정의가 명확하며, 헥사고날 아키텍처 패턴을 잘 준수하고 있습니다.
src/main/java/konkuk/thip/notification/application/service/NotificationExistsUncheckedService.java (1)
1-20: LGTM!조회 전용 트랜잭션 설정(
@Transactional(readOnly = true))이 적절하게 적용되었으며, 유스케이스 구현이 깔끔합니다.src/main/java/konkuk/thip/notification/application/port/in/NotificationExistsUncheckedUseCase.java (1)
1-6: LGTM!단일 책임 원칙을 잘 준수한 유스케이스 인터페이스입니다.
src/main/java/konkuk/thip/notification/adapter/in/web/NotificationQueryController.java (1)
63-71: LGTM!새로운 API 엔드포인트가 잘 구현되었습니다:
@UserId어노테이션을 통한 인증 처리가 적절합니다- Swagger 문서화가 명확합니다
- 응답 래핑 패턴이 기존 엔드포인트들과 일관성 있게 유지되고 있습니다
src/test/java/konkuk/thip/notification/adapter/in/web/NotificationExistsUncheckedApiTest.java (2)
25-35: 통합 테스트 구성이 적절합니다.
@AutoConfigureMockMvc(addFilters = false)를 통해 보안 필터를 비활성화하고 있는데, 이는 통합 테스트에서 인증 흐름을 우회하는 일반적인 패턴입니다. 다만, 실제 프로덕션 환경의 인증 메커니즘과requestAttr("userId", ...)를 통한 사용자 컨텍스트 전달 방식이 일치하는지 확인해주세요.
37-51: 기본 시나리오 테스트 LGTM!읽지 않은 알림이 존재할 때
true를 반환하는 핵심 시나리오를 정확하게 검증하고 있습니다.build.gradle (1)
96-100: 의존성 추가가 적절합니다.WebFlux는 Discord 웹훅 클라이언트 구현에 필요하고, Logstash 인코더는 MDC 기반 구조화된 로깅을 지원합니다. Spring Boot 3.5.0과 호환되는 버전들입니다.
src/main/resources/logback-spring.xml (2)
8-8: 로그 디렉토리 생성을 확인하세요.
LOG_PATH로 지정된logs디렉토리가 애플리케이션 시작 시 자동으로 생성되는지 확인하시기 바랍니다. Logback은 자동으로 생성하지만, 권한 문제가 발생할 수 있습니다.
79-84: 로그 레벨별 파일 분리가 적절합니다.INFO, WARN, ERROR를 별도 파일로 분리하여 로그 분석이 용이하고, LevelFilter로 중복을 방지한 구성이 좋습니다.
src/main/java/konkuk/thip/common/security/oauth2/CustomSuccessHandler.java (1)
54-54: 예외 타입 변경이 적절합니다.웹 도메인 origin이 비어있는 것은 서버 설정 오류이므로
InternalServerException을 사용하는 것이AuthException보다 의미적으로 더 정확합니다.src/main/java/konkuk/thip/user/domain/User.java (1)
84-84: 예외 타입 변경이 적절합니다.
oauth2Id가 null인 상태에서markAsDeleted가 호출되는 것은 내부 불변성 위반이므로InternalServerException이InvalidStateException보다 적합합니다.src/main/java/konkuk/thip/user/adapter/out/persistence/UserTokenBlacklistRedisAdapter.java (1)
55-55: 예외 타입 변경이 적절합니다.내부 데이터 구조를 JSON으로 직렬화하는 과정에서 발생하는 오류는 외부 API 오류가 아닌 내부 서버 오류이므로
InternalServerException이ExternalApiException보다 정확합니다.src/main/java/konkuk/thip/common/security/oauth2/tokenstorage/RedisLoginTokenStorage.java (1)
42-42: 예외 타입 변경이 적절합니다.Redis에서 예상과 다른 타입을 반환하는 것은 인증 오류가 아닌 내부 데이터 일관성 문제이므로
InternalServerException이AuthException보다 적합합니다.src/main/java/konkuk/thip/common/aop/StatusFilterAspect.java (1)
33-33: 예외 타입 변경이 적절합니다.트랜잭션이 활성화되지 않은 상태에서 Hibernate 세션에 접근하는 것은 프로그래밍 오류이므로
InternalServerException이InvalidStateException보다 적합합니다. 이는 개발 단계에서 발견되어야 하는 내부 불변성 위반입니다.src/main/java/konkuk/thip/config/SecurityConfig.java (1)
50-50: MDC 클린업 로직 확인됨
MdcLoggingFilter의doFilterInternal에서MDC.clear()를 호출해 필터 완료 후 MDC를 적절히 정리하고 있습니다. 추가 조치 불필요합니다.src/main/java/konkuk/thip/book/adapter/out/api/aladin/AladinApiUtil.java (1)
44-47: 네트워크/HTTP 예외 매핑 누락 가능성RestTemplate.getForObject(...)의 RestClientException 계열을 ExternalApiException으로 매핑하지 않으면 외부 API 장애가 적절히 분류되지 않을 수 있습니다. 전역 핸들러 정책과 맞춰 RestClientException 캐치 → ExternalApiException 변환을 검토해 주세요.
src/main/java/konkuk/thip/common/logging/MdcLoggingFilter.java (1)
16-35: 필터 실행 순서 정상 확인
SecurityConfig.filterChain에서 이미addFilterBefore(mdcLoggingFilter, JwtAuthenticationFilter.class)로 MDC 로깅 필터가 JWT 인증 필터 이전에 등록되어 있습니다.src/main/java/konkuk/thip/common/logging/LoggingConstant.java (1)
1-16: MDC 키 사용 일치 확인 및 설정 지점 검증 요청
logback-spring.xml의 file.format 패턴에서 request_id·user_id 키가 일관되게 반영됨을 확인했습니다. USER_ID를 MDC에 설정하는 인증 필터 또는 인터셉터 구현부도 올바르게 동작하는지 함께 검증해 주세요.src/main/java/konkuk/thip/common/security/filter/JwtAuthenticationFilter.java (1)
90-95: 예외 전파 전략 확인 요청catch에서 request 속성 "exception"만 설정하고 체인을 계속 진행합니다. 이후(AuthenticationEntryPoint/HandlerExceptionResolver 등)에서 이 속성을 사용해 401/500으로 매핑되는지 확인해 주세요. 미처리 시 컨트롤러까지 진행될 수 있습니다.
| JsonNode subInfo = items.get(0).path(SUB_INFO_PARSING_KEY.getValue()); | ||
|
|
||
| return subInfo.path(PAGE_COUNT_PARSING_KEY.getValue()).asInt(); | ||
| } catch (IOException e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
페이지 수 미존재(0) 반환은 모호합니다 — 명시적으로 예외 처리 권장
item/subInfo가 존재하더라도 pageCount 필드가 없으면 asInt()가 0을 반환해 성공처럼 보입니다. 사용자 안내 시나리오와 일관되게 0인 경우도 ExternalApiException으로 처리하는 편이 안전합니다.
적용 예:
- return subInfo.path(PAGE_COUNT_PARSING_KEY.getValue()).asInt();
+ int pageCount = subInfo.path(PAGE_COUNT_PARSING_KEY.getValue()).asInt();
+ if (pageCount <= 0) {
+ throw new ExternalApiException(BOOK_ALADIN_API_ISBN_NOT_FOUND);
+ }
+ return pageCount;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| JsonNode subInfo = items.get(0).path(SUB_INFO_PARSING_KEY.getValue()); | |
| return subInfo.path(PAGE_COUNT_PARSING_KEY.getValue()).asInt(); | |
| } catch (IOException e) { | |
| JsonNode subInfo = items.get(0).path(SUB_INFO_PARSING_KEY.getValue()); | |
| int pageCount = subInfo.path(PAGE_COUNT_PARSING_KEY.getValue()).asInt(); | |
| if (pageCount <= 0) { | |
| throw new ExternalApiException(BOOK_ALADIN_API_ISBN_NOT_FOUND); | |
| } | |
| return pageCount; | |
| } catch (IOException e) { |
| throw new ExternalApiException(BOOK_ALADIN_API_PARSING_ERROR); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
파싱 예외의 원인(cause) 보존
스택트레이스를 유지하려면 cause를 포함해 던지세요.
- } catch (IOException e) {
- throw new ExternalApiException(BOOK_ALADIN_API_PARSING_ERROR);
+ } catch (IOException e) {
+ throw new ExternalApiException(BOOK_ALADIN_API_PARSING_ERROR, e);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| throw new ExternalApiException(BOOK_ALADIN_API_PARSING_ERROR); | |
| } | |
| } catch (IOException e) { | |
| throw new ExternalApiException(BOOK_ALADIN_API_PARSING_ERROR, e); | |
| } |
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/book/adapter/out/api/aladin/AladinApiUtil.java
around lines 64-65, the code throws ExternalApiException without preserving the
original parsing exception; modify the throw to include the caught exception as
the cause (pass the caught Throwable/Exception into ExternalApiException via its
constructor or a factory method) so the stack trace and root cause are preserved
when rethrowing.
| throw new InternalServerException(BOOK_KEYWORD_ENCODING_FAILED); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
원인 예외(cause) 보존하여 진단력 향상
재래핑 시 원인 예외를 포함하지 않아 스택트레이스 손실이 있습니다. 두 번째 생성자 사용으로 cause를 보존해 주세요.
- throw new InternalServerException(BOOK_KEYWORD_ENCODING_FAILED);
+ throw new InternalServerException(BOOK_KEYWORD_ENCODING_FAILED, e);
@@
- throw new ExternalApiException(BOOK_NAVER_API_REQUEST_ERROR);
+ throw new ExternalApiException(BOOK_NAVER_API_REQUEST_ERROR, e);
@@
- throw new InternalServerException(BOOK_NAVER_API_URL_ERROR);
+ throw new InternalServerException(BOOK_NAVER_API_URL_ERROR, e);
@@
- throw new InternalServerException(BOOK_NAVER_API_URL_HTTP_CONNECT_FAILED);
+ throw new InternalServerException(BOOK_NAVER_API_URL_HTTP_CONNECT_FAILED, e);
@@
- throw new ExternalApiException(BOOK_NAVER_API_RESPONSE_ERROR);
+ throw new ExternalApiException(BOOK_NAVER_API_RESPONSE_ERROR, e);ExternalApiException에 (ErrorCode, Throwable) 생성자가 없다면 동일 시그니처를 추가하는 것도 고려해 주세요.
Based on learnings
Also applies to: 88-89, 100-103, 120-121
🏁 Script executed:
#!/bin/bash
# InternalServerException 생성자 시그니처 확인
rg -nP "class InternalServerException\b" -g "src/main/java/**/*.java" -C5
rg -nP "InternalServerException\s*\(" -g "src/main/java/**/*.java" -C3
# ExternalApiException 생성자 시그니처 확인
rg -nP "class ExternalApiException\b" -g "src/main/java/**/*.java" -C5
rg -nP "ExternalApiException\s*\(" -g "src/main/java/**/*.java" -C3Length of output: 19126
예외 래핑 시 원인 예외 보존
src/main/java/konkuk/thip/book/adapter/out/api/naver/NaverApiUtil.java의 catch 블록(67-68, 88-89, 100-103, 120-121)에서 throw 시 두 번째 인자로 e를 전달해 스택 트레이스를 보존하세요.
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/book/adapter/out/api/naver/NaverApiUtil.java around
lines 67-68, 88-89, 100-103 and 120-121, the catch blocks throw
InternalServerException without preserving the original exception; update each
throw to pass the caught exception (e) as the cause/second argument when
constructing InternalServerException so the original stack trace is retained
(e.g., throw new InternalServerException(BOOK_KEYWORD_ENCODING_FAILED, e));
ensure all four catch sites are changed similarly.
| throw new InternalServerException(ErrorCode.JSON_PROCESSING_ERROR); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
JSON 처리 예외의 원인(cause) 보존
스택 추적을 유지하세요.
- throw new InternalServerException(ErrorCode.JSON_PROCESSING_ERROR);
+ throw new InternalServerException(ErrorCode.JSON_PROCESSING_ERROR, e);- throw new InternalServerException(JSON_PROCESSING_ERROR);
+ throw new InternalServerException(JSON_PROCESSING_ERROR, e);Also applies to: 109-110
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/book/adapter/out/persistence/BookRedisAdapter.java
around lines 83-84 and 109-110, the catch blocks that throw new
InternalServerException(ErrorCode.JSON_PROCESSING_ERROR) discard the original
exception and its stack trace; update those throws to preserve the cause by
passing the caught exception as the cause (e.g., throw new
InternalServerException(ErrorCode.JSON_PROCESSING_ERROR, e)) or otherwise attach
the original throwable so the stack trace and root cause are retained.
| webClient.post() | ||
| .uri(webhookUrl) | ||
| .contentType(MediaType.APPLICATION_JSON) | ||
| .bodyValue(payload) | ||
| .retrieve() | ||
| .bodyToMono(Void.class) | ||
| .block(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요청 스레드에서 block() 호출 및 타임아웃 부재 → 신뢰성 저하
예외 처리 중 Discord 호출이 무기한 블록될 수 있습니다. 타임아웃 + 비동기 fire-and-forget로 전환을 권장합니다.
- webClient.post()
- .uri(webhookUrl)
- .contentType(MediaType.APPLICATION_JSON)
- .bodyValue(payload)
- .retrieve()
- .bodyToMono(Void.class)
- .block();
+ webClient.post()
+ .uri(webhookUrl)
+ .contentType(MediaType.APPLICATION_JSON)
+ .bodyValue(payload)
+ .retrieve()
+ .toBodilessEntity()
+ .timeout(java.time.Duration.ofSeconds(3))
+ .onErrorResume(ex -> reactor.core.publisher.Mono.empty())
+ .subscribe();추가로, WebClient는 매 호출 생성 대신 빈으로 주입해 재사용하면 연결 풀/성능 측면에서 유리합니다.
Based on learnings
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| webClient.post() | |
| .uri(webhookUrl) | |
| .contentType(MediaType.APPLICATION_JSON) | |
| .bodyValue(payload) | |
| .retrieve() | |
| .bodyToMono(Void.class) | |
| .block(); | |
| } | |
| webClient.post() | |
| .uri(webhookUrl) | |
| .contentType(MediaType.APPLICATION_JSON) | |
| .bodyValue(payload) | |
| .retrieve() | |
| .toBodilessEntity() | |
| .timeout(java.time.Duration.ofSeconds(3)) | |
| .onErrorResume(ex -> reactor.core.publisher.Mono.empty()) | |
| .subscribe(); | |
| } |
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/common/discord/DiscordClient.java around lines
56-63, the current code blocks the calling thread with .block() and has no
timeout; change to use a reusable injected WebClient bean (do not create
per-call), send the request asynchronously and fire-and-forget by using the
reactive chain without .block(), add a timeout (e.g., Duration.ofSeconds(5)) on
the Mono, and subscribe with error handling to log failures (or
.doOnError(...).subscribe()). Ensure WebClient is injected as a @Bean and reused
to benefit from connection pooling.
| BOOK_ALADIN_API_ISBN_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, 80016, "알라딘 API 에서 ISBN으로 검색한 결과가 존재하지 않습니다."), | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
외부 API ‘ISBN 미존재’의 500 매핑 재검토
알라딘 ISBN 미존재를 500으로 올린 것은 사용자/클라이언트 오류 성격과 맞지 않습니다. 네이버(80009)는 BAD_REQUEST입니다. 일관성 및 UX를 위해 400(또는 404)로 되돌리는 것을 권장합니다.
- BOOK_ALADIN_API_ISBN_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, 80016, "알라딘 API 에서 ISBN으로 검색한 결과가 존재하지 않습니다."),
+ BOOK_ALADIN_API_ISBN_NOT_FOUND(HttpStatus.BAD_REQUEST, 80016, "알라딘 API 에서 ISBN으로 검색한 결과가 존재하지 않습니다."),도메인 의도상 404가 더 적절하다면 404도 검토해 주세요.
Based on learnings
🏁 Script executed:
#!/bin/bash
rg -nP --type=java 'NAVER.*ISBN_NOT_FOUND' -C3 src/main/java/konkuk/thip/common/exception/code/ErrorCode.javaLength of output: 625
외부 API ISBN 미존재의 HTTP 상태코드 500 → 400(또는 404)로 변경
알라딘 API에서 ISBN 미존재는 클라이언트 입력 오류이므로 HttpStatus.INTERNAL_SERVER_ERROR 대신 HttpStatus.BAD_REQUEST(또는 도메인 의도에 따라 HttpStatus.NOT_FOUND)로 수정하세요.
- BOOK_ALADIN_API_ISBN_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, 80016, "알라딘 API 에서 ISBN으로 검색한 결과가 존재하지 않습니다."),
+ BOOK_ALADIN_API_ISBN_NOT_FOUND(HttpStatus.BAD_REQUEST, 80016, "알라딘 API 에서 ISBN으로 검색한 결과가 존재하지 않습니다."),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| BOOK_ALADIN_API_ISBN_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, 80016, "알라딘 API 에서 ISBN으로 검색한 결과가 존재하지 않습니다."), | |
| BOOK_ALADIN_API_ISBN_NOT_FOUND(HttpStatus.BAD_REQUEST, 80016, "알라딘 API 에서 ISBN으로 검색한 결과가 존재하지 않습니다."), |
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/common/exception/code/ErrorCode.java around lines
84-85, the BOOK_ALADIN_API_ISBN_NOT_FOUND entry uses
HttpStatus.INTERNAL_SERVER_ERROR but should represent a client-side error;
change the HttpStatus to HttpStatus.BAD_REQUEST (or HttpStatus.NOT_FOUND if you
prefer domain semantics) while keeping the error code and message intact so the
enum reflects a 4xx status for ISBN-not-found cases.
| } catch (FirebaseMessagingException e) { | ||
| log.warn("[FCM:SEND] fail token={} device={} code={} msg={}", maskDependingProfile(fcmToken), maskDependingProfile(deviceId), e.getMessagingErrorCode(), e.getMessage()); | ||
| throw new FirebaseException(e); | ||
| throw new FirebaseException(FIREBASE_SEND_ERROR); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
원인(cause) 없이 예외 래핑 → 추적성 저하
catch 블록에서 원인을 포함해 던지세요.
- } catch (FirebaseMessagingException e) {
+ } catch (FirebaseMessagingException e) {
log.warn("[FCM:SEND] fail token={} device={} code={} msg={}", maskDependingProfile(fcmToken), maskDependingProfile(deviceId), e.getMessagingErrorCode(), e.getMessage());
- throw new FirebaseException(FIREBASE_SEND_ERROR);
+ throw new FirebaseException(FIREBASE_SEND_ERROR, e);
}🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/message/adapter/out/firebase/FirebaseAdapter.java
around lines 33 to 36, the catch block wraps FirebaseMessagingException without
preserving the original cause; change the throw to include the caught exception
as the cause (e.g., throw new FirebaseException(FIREBASE_SEND_ERROR, e)); if
FirebaseException lacks a constructor that accepts a cause, add one (or call
initCause(e)) so the original stacktrace is preserved for traceability.
| } catch (FirebaseMessagingException e) { | ||
| log.warn("[FCM:BATCH] 메시지 전송 실패: code={} msg={}", e.getMessagingErrorCode(), e.getMessage()); | ||
| throw new FirebaseException(e); | ||
| throw new FirebaseException(FIREBASE_SEND_ERROR); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
배치 전송 실패 예외도 cause 포함
루트 원인 보존을 권장합니다.
- } catch (FirebaseMessagingException e) {
+ } catch (FirebaseMessagingException e) {
log.warn("[FCM:BATCH] 메시지 전송 실패: code={} msg={}", e.getMessagingErrorCode(), e.getMessage());
- throw new FirebaseException(FIREBASE_SEND_ERROR);
+ throw new FirebaseException(FIREBASE_SEND_ERROR, e);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| } catch (FirebaseMessagingException e) { | |
| log.warn("[FCM:BATCH] 메시지 전송 실패: code={} msg={}", e.getMessagingErrorCode(), e.getMessage()); | |
| throw new FirebaseException(e); | |
| throw new FirebaseException(FIREBASE_SEND_ERROR); | |
| } | |
| } catch (FirebaseMessagingException e) { | |
| log.warn("[FCM:BATCH] 메시지 전송 실패: code={} msg={}", e.getMessagingErrorCode(), e.getMessage()); | |
| throw new FirebaseException(FIREBASE_SEND_ERROR, e); | |
| } |
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/message/adapter/out/firebase/FirebaseAdapter.java
around lines 70 to 73, the catch block for FirebaseMessagingException logs the
error but throws a new FirebaseException without preserving the original
exception; update the throw to include the caught exception as the cause (use
the FirebaseException constructor that accepts a cause or otherwise
wrap/preserve e) so the root cause is retained when rethrowing.
| throw new InternalServerException(NOTIFICATION_REDIRECT_DATA_SERIALIZE_FAILED); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
직렬화 실패 시 원인(cause) 보존
원인 예외를 포함해 던지면 추적이 쉬워집니다.
- } catch (JsonProcessingException e) {
- throw new InternalServerException(NOTIFICATION_REDIRECT_DATA_SERIALIZE_FAILED);
+ } catch (JsonProcessingException e) {
+ throw new InternalServerException(NOTIFICATION_REDIRECT_DATA_SERIALIZE_FAILED, e);
}🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/notification/domain/value/NotificationRedirectSpecConverter.java
around lines 25-26, the catch block throws InternalServerException without
preserving the original exception; modify the code to capture the serialization
exception (e.g., catch Exception e) and rethrow InternalServerException with the
caught exception as the cause by passing it into the InternalServerException
constructor (or calling initCause) so the original stacktrace and message are
preserved for debugging.
| throw new InternalServerException(NOTIFICATION_REDIRECT_DATA_DESERIALIZE_FAILED); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
역직렬화 실패 시 원인(cause) 보존
동일하게 cause 포함 권장.
- } catch (IOException e) {
- throw new InternalServerException(NOTIFICATION_REDIRECT_DATA_DESERIALIZE_FAILED);
+ } catch (IOException e) {
+ throw new InternalServerException(NOTIFICATION_REDIRECT_DATA_DESERIALIZE_FAILED, e);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| throw new InternalServerException(NOTIFICATION_REDIRECT_DATA_DESERIALIZE_FAILED); | |
| } | |
| } catch (IOException e) { | |
| throw new InternalServerException(NOTIFICATION_REDIRECT_DATA_DESERIALIZE_FAILED, e); | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/notification/domain/value/NotificationRedirectSpecConverter.java
around lines 35-36, the InternalServerException thrown on deserialization
failure currently omits the original exception; modify the throw to preserve the
cause by passing the caught exception as the cause (e.g., use the
InternalServerException constructor or builder variant that accepts a Throwable)
so the original exception is attached to the new InternalServerException for
debugging and logging.
- 게시글 좋아요 로직이 레디스 원자적 연산으로 수정되면서 게시글 엔티티의 좋아요 정보(likeCount)는 더이상 실시간정보가 아니므로 db 정합성을 확인하는 코드 주석 처리 (#324)
- updateLikeCount()의newLikeCount 매개변수가 추가되면서 테스트코드 수정 (#324)
#️⃣ 연관된 이슈
📝 작업 내용
📸 스크린샷
💬 리뷰 요구사항
📌 PR 진행 시 이러한 점들을 참고해 주세요
Summary by CodeRabbit