Skip to content

한성재 sprint7#159

Open
seonghj wants to merge 12 commits into
codeit-bootcamp-spring:한성재from
seonghj:한성재_sprint7

Hidden character warning

The head ref may contain hidden characters: "\ud55c\uc131\uc7ac_sprint7"
Open

한성재 sprint7#159
seonghj wants to merge 12 commits into
codeit-bootcamp-spring:한성재from
seonghj:한성재_sprint7

Conversation

@seonghj
Copy link
Copy Markdown
Collaborator

@seonghj seonghj commented Apr 5, 2026

요구사항

기본

  • 프로파일 기반 설정 관리
  • 로그 관리
  • 예외 처리 고도화
  • 유효성 검사
  • Actuator
  • 단위 테스트
  • 슬라이스 테스트
  • 통합 테스트

심화

  • MDC를 활용한 로깅 고도화
  • Spring Boot Admin을 활용한 메트릭 가시화
  • 테스트 커버리지 관리 (60% 달성)

주요 변경사항

스크린샷

image

멘토에게

  • 셀프 코드 리뷰를 통해 질문 이어가겠습니다.

Copy link
Copy Markdown

@joonfluence joonfluence left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리뷰 요약

Sprint 7 요구사항(프로필 기반 설정, 로깅, 예외 처리, Validation, Actuator, 테스트)을 잘 구현했습니다. 전반적으로 구조가 깔끔하게 잡혀 있으나, 아래 이슈들을 확인해주세요.

Copy link
Copy Markdown

@joonfluence joonfluence left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

각 코드 위치에 인라인 코멘트로 리뷰를 작성했습니다. Critical → Major → Minor 순으로 확인해주세요.

@ExceptionHandler(DiscodeitException.class)
public ResponseEntity<ErrorResponse> handleDiscodeitException(DiscodeitException e){
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
ErrorResponse response = ErrorResponse.of(e, status.value());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] DiscodeitException fallback 핸들러가 항상 500 반환

새로운 커스텀 예외를 추가할 때 전용 핸들러를 빠뜨리면 이 fallback에 걸려 항상 500 Internal Server Error가 반환됩니다.

ErrorCode에 HTTP status를 매핑하거나, DiscodeitExceptiongetHttpStatus() 메서드를 추가하세요.

// ErrorCode에 status 추가 예시
USER_NOT_FOUND(2001, "존재하지 않는 사용자입니다.", HttpStatus.NOT_FOUND),

// fallback 핸들러에서 활용
HttpStatus status = e.getErrorCode().getHttpStatus();

// Message
@ExceptionHandler(MessageNotFoundException.class)
public ResponseEntity<ErrorResponse> handleChannelNotFoundException(MessageNotFoundException e){
HttpStatus status = HttpStatus.NOT_FOUND;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Minor] 메서드 이름 중복

handleChannelNotFoundException이라는 이름의 핸들러가 ChannelNotFoundExceptionMessageNotFoundException 두 곳에 정의되어 있습니다. 컴파일은 되지만 혼란을 줍니다.

// 변경
public ResponseEntity<ErrorResponse> handleMessageNotFoundException(MessageNotFoundException e) { ... }

throws IOException {

log.info("사용자 정보 수정 요청 수신: username={}, userId={}",request, userId);
Optional<BinaryContentCreateRequest> binaryContentCreateRequest = Optional.empty();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] 로그에 비밀번호 노출 위험

request 객체를 통째로 로깅하면 UserUpdateRequestnewPassword 필드가 로그에 평문으로 기록될 수 있습니다.

// 수정
log.info("사용자 정보 수정 요청 수신: username={}, userId={}", request.newUsername(), userId);

});

if (!user.getPassword().equals(request.password())){
log.warn("로그인 실패: 비밀번호 불일치 - username={}", request.username());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] 비밀번호 평문 비교

비밀번호를 평문으로 저장·비교하고 있습니다. 학습 프로젝트라도 PasswordEncoder(BCrypt 등) 사용을 권장합니다.

// 예시
if (!passwordEncoder.matches(request.password(), user.getPassword())) {
    throw new AuthenticationException(...);
}

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Major] 미사용 import

@EnableJpaAuditingJpaConfig로 이동했는데, 여기서는 사용하지 않는 import가 추가되었습니다. 제거해주세요.



import jakarta.validation.constraints.*;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Major] 와일드카드 import 사용

jakarta.validation.constraints.* 대신 필요한 애노테이션만 명시적으로 import하세요. 코드 가독성이 높아지고 IDE 지원도 더 원활합니다.

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

Comment thread .gitignore
discodeit.iml
/storage/ No newline at end of file
/storage/

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Minor] /storage/ 항목 중복

/storage/가 이 파일에 두 번 정의되어 있습니다. 하나를 제거해주세요.


// MESSAGE
MESSAGE_NOT_FOUND(4001, "메시지가 존재하지 않습니다")
;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Minor] 마침표 누락으로 일관성 깨짐

다른 ErrorCode 항목은 메시지 끝에 마침표가 있는데 MESSAGE_NOT_FOUND만 없습니다.

MESSAGE_NOT_FOUND(4001, "메시지가 존재하지 않습니다.")  // 마침표 추가

Comment thread admin/build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '4.0.5'
id 'io.spring.dependency-management' version '1.1.7'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Major] Spring Boot 버전 오류

org.springframework.boot version '4.0.5'는 존재하지 않는 버전입니다. Spring Boot는 현재 3.x 라인입니다. discodeit 모듈(3.5.10 — 이것도 확인 필요)과 동일한 버전으로 맞춰주세요.

또한 springBootAdminVersion"4.0.2"로 설정되어 있는데, 이 버전도 존재하지 않습니다.

@@ -0,0 +1,163 @@
package com.sprint.mission.discodeit.integraton;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Minor] 패키지 이름 오타

integratonintegration의 오타입니다. 패키지 이름과 디렉토리를 수정해주세요.

// 수정
package com.sprint.mission.discodeit.integration;

// custom exception
@ExceptionHandler(DiscodeitException.class)
public ResponseEntity<ErrorResponse> handleDiscodeitException(DiscodeitException e){
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] DiscodeitException fallback 핸들러가 항상 500 반환

새로운 커스텀 예외를 추가할 때 전용 핸들러를 빠뜨리면 이 fallback에 걸려 항상 500 Internal Server Error가 반환됩니다.

ErrorCode에 HTTP status를 매핑하거나, DiscodeitExceptiongetHttpStatus() 메서드를 추가하세요.

// ErrorCode에 status 추가 예시
USER_NOT_FOUND(2001, "존재하지 않는 사용자입니다.", HttpStatus.NOT_FOUND),

// fallback 핸들러에서 활용
HttpStatus status = e.getErrorCode().getHttpStatus();


// Message
@ExceptionHandler(MessageNotFoundException.class)
public ResponseEntity<ErrorResponse> handleChannelNotFoundException(MessageNotFoundException e){
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Minor] 메서드 이름 중복

handleChannelNotFoundException이라는 이름의 핸들러가 ChannelNotFoundExceptionMessageNotFoundException 두 곳에 정의되어 있습니다. 컴파일은 되지만 혼란을 줍니다.

// 변경
public ResponseEntity<ErrorResponse> handleMessageNotFoundException(MessageNotFoundException e) { ... }

, @RequestPart(value = "profile", required = false) MultipartFile imageFile)
throws IOException {

log.info("사용자 정보 수정 요청 수신: username={}, userId={}",request, userId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] 로그에 비밀번호 노출 위험

request 객체를 통째로 로깅하면 UserUpdateRequestnewPassword 필드가 로그에 평문으로 기록될 수 있습니다.

// 수정
log.info("사용자 정보 수신: username={}, userId={}", request.newUsername(), userId);

return new AuthenticationException("Not Exist User - userName: " + request.username());
});

if (!user.getPassword().equals(request.password())){
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] 비밀번호 평문 비교

비밀번호를 평문으로 저장·비교하고 있습니다. 학습 프로젝트라도 PasswordEncoder(BCrypt 등) 사용을 권장합니다.

// 예시
if (!passwordEncoder.matches(request.password(), user.getPassword())) {
    throw new AuthenticationException(...);
}


import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Major] 미사용 import

@EnableJpaAuditingJpaConfig로 이동했습니다. 이 파일에서는 사용하지 않으므로 제거해주세요.

import jakarta.persistence.criteria.CriteriaBuilder.In;
import java.util.NoSuchElementException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Major] Service 클래스에 미사용 import

@EnableJpaAuditing@Configuration 클래스(JpaConfig)에서만 사용해야 합니다. 이 Service에서는 사용되지 않으므로 제거해주세요.

log.warn("채널 삭제 실패: 존재하지 않는 채널 ID={}", id);
return new ChannelNotFoundException(id);
});
log.info("채널 삭제 완료: channelId={}", id);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Minor] 로그 순서 오류 — 삭제 전에 "완료" 로그가 찍힘

현재 channelRepository.delete(channel) 호출 전에 "채널 삭제 완료" 로그가 기록됩니다. DB 작업 실패 시 로그와 실제 상태가 불일치합니다.

channelRepository.delete(channel);
log.info("채널 삭제 완료: channelId={}", id); // 이 위치로 이동

import java.util.UUID;

public record MessageCreateRequest(
@Size(max = 100, message = "메시지 내용은 최대 100자입니다.")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Major] content 필드에 @NotBlank 누락

@Size(max = 100)만으로는 null 또는 빈 문자열("")을 막지 못합니다. 빈 메시지 전송을 방지하려면 @NotBlank를 함께 사용해야 합니다.

@NotBlank(message = "메시지 내용은 필수입니다.")
@Size(max = 100, message = "메시지 내용은 최대 100자입니다.")
String content,

package com.sprint.mission.discodeit.dto.request;


import jakarta.validation.constraints.*;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Major] 와일드카드 import 사용

jakarta.validation.constraints.* 대신 필요한 애노테이션만 명시적으로 import하세요.

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

PRIVATE_CHANNEL_UPDATE(3002, "PRIVATE 채널은 수정할 수 없습니다."),

// MESSAGE
MESSAGE_NOT_FOUND(4001, "메시지가 존재하지 않습니다")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Minor] 마침표 누락으로 일관성 깨짐

다른 ErrorCode 항목은 메시지 끝에 마침표가 있는데 MESSAGE_NOT_FOUND만 없습니다.

MESSAGE_NOT_FOUND(4001, "메시지가 존재하지 않습니다.")  // 마침표 추가

Comment thread admin/build.gradle
@@ -0,0 +1,39 @@
plugins {
id 'java'
id 'org.springframework.boot' version '4.0.5'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Major] 존재하지 않는 Spring Boot 버전

org.springframework.boot version '4.0.5'는 존재하지 않는 버전입니다. Spring Boot는 현재 3.x 라인입니다. springBootAdminVersion "4.0.2"도 마찬가지로 확인이 필요합니다. discodeit 모듈과 동일한 버전으로 맞춰주세요.

@@ -0,0 +1,163 @@
package com.sprint.mission.discodeit.integraton;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Minor] 패키지 이름 오타

integratonintegration으로 수정이 필요합니다. 디렉토리명도 함께 변경해주세요.

package com.sprint.mission.discodeit.integration;

Comment thread .gitignore
discodeit.iml
/storage/ No newline at end of file
/storage/

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Minor] /storage/ 항목 중복

/storage/가 두 번 정의되어 있습니다. 하나를 제거해주세요.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants