HUFJOK 백엔드 프로젝트 개발 과정에서 발생한 주요 기술적 문제와 해결 과정을 기록합니다.
- 문제 상황: VS Code가
ConfigError: The project 'todolist-BE' is not a valid java project에러를 발생시켰다. - 원인 분석:
settings.gradle파일의rootProject.name이 이전 프로젝트명(todolist)으로 설정되어, Java 확장 프로그램이 프로젝트 구조를 잘못 인식했다. - 최종 해결:
settings.gradle의rootProject.name을hufjok으로 수정하고,Java: Clean Java Language Server Workspace를 실행하여 VS Code의 Java 캐시를 초기화했다.
- 문제 상황: 로컬 서버(
localhost:8080)의 Swagger UI에서 API 테스트 시Failed to fetch에러가 발생했다. - 원인 분석:
SwaggerConfig.java에 배포 서버 주소(https://hufjok.lion.it.kr)만 하드 코딩되어, 로컬 요청이 배포 서버로 전송되다 CORS 정책 위반으로 차단되었다. - 최종 해결:
SwaggerConfig.java에.addServersItem(new Server().url("http://localhost:8080"))를 추가하고, Swagger UI의 [Servers] 드롭다운에서 로컬 주소를 선택하여 테스트 환경을 분리했다.
- 문제 상황: 서버 실행 시
Application run failed (BeanCreationException)가 발생했다. - 원인 분석:
CustomOAuth2UserService.java파일이config패키지에 잘못 위치했다. 파일 상단의package security.oauth2선언과 실제 파일 시스템 경로가 불일치했다. - 최종 해결: 해당 파일을
config/폴더에서security/oauth2/폴더로 이동시켰다.
- 문제 상황: 서버 실행 시
PlaceholderResolutionException이 발생했다. - 원인 분석:
AttachmentService의@Value("${file.dir}")에 주입될 설정값이application.yml에 정의되지 않았다. - 최종 해결:
application.yml파일에file: dir: ./files/설정을 추가했다.
- 문제 상황: 신규 회원 로그인 시 500 에러가 발생하거나, 500 포인트가 지급되어도
GET /api/v1/users/mypage/points/history응답이 빈 배열[]로 반환되었다. - 원인 분석:
CustomOAuth2UserService와UserService에서 유저 생성을 중복 시도했다.UserService.saveFirstLogin의 User 저장 트랜잭션이 커밋되기 전에PointService.awardSignupBonus가 호출되어,PointHistory저장 시 DB 외래 키 참조(FK) 에러가 발생했다.
- 최종 해결:
CustomOAuth2UserService의saveOrUpdate호출을 제거하여 유저 생성 로직을UserService로 일원화했다.PointService의awardSignupBonus메소드에@Transactional(propagation = Propagation.REQUIRES_NEW)을 추가하여 트랜잭션을 분리, User 저장이 먼저 커밋되도록 보장했다.- (이후 리팩토링)
UserService에서 가입 시points(500)을 직접 설정하고,PointService에서는updatePoints()호출 없이PointHistory만 기록하도록 수정했다.
- 문제 상황: 자료 업로드 후
GET /api/v1/me/materialsAPI가 정상(200 OK) 응답에도 불구하고{ materials: [], ... }빈 목록을 반환했다. - 원인 분석:
MaterialRepository의 JPA 메서드 네이밍(findByUserIdAndIsDeletedFalse)이 중첩된 객체 경로(m.user.id)를 올바르게 해석하지 못했다. - 최종 해결:
MaterialRepository의 해당 메서드에@Query어노테이션을 사용하여"SELECT m FROM Material m WHERE m.user.id = :userId AND m.isDeleted = FALSE"JPQL 쿼리를 명시적으로 작성했다.
- 문제 상황: 자료 논리적 삭제(
isDeleted = true) 후에도, 상세 조회(GET /.../{materialId}) 및 구매 목록(GET /.../me/downloads) API에서 삭제된 자료가 계속 조회되었다. - 원인 분석: 조회 로직(Service)에서
isDeleted플래그를 체크하지 않았다. - 최종 해결:
MaterialService.getMaterial():material.getIsDeleted()를 체크하여true일 경우NotFoundException을 발생시켰다.MaterialService.getMyDownloadedMaterials(): DTO 변환 스트림에.filter(material -> !material.getIsDeleted())를 추가하여 삭제된 자료를 필터링했다.
- 문제 상황: 리뷰를 1개 작성하면 API 응답에
reviewCount: 2로 표시되었다. - 원인 분석:
Review엔티티 내 불필요한reviewCount필드가 존재했으며,ReviewService에서 엔티티 생성 시점과 별도 메서드(increaseReviewCount()) 호출을 통해 카운트를 중복 증가시켰다. - 최종 해결:
Review엔티티에서reviewCount필드 및 관련 로직을 모두 제거했다.ReviewService에서reviewCount관련 로직을 모두 삭제했다.MaterialGetResponseDto에서material.getReviews().size()를 사용해 동적으로 리뷰 개수를 계산하도록 수정했다.
- 문제 상황: 프론트엔드에서 Google 로그인 시
400 오류: redirect_uri_mismatch가 발생했다. - 원인 분석: 프론트엔드가 백엔드의 인증 엔드포인트(
/oauth2/authorization/google)를 호출하지 않고, Google 인증 서버로 직접 요청을 보내면서redirect_uri를 프론트엔드 주소(...:5173)로 잘못 설정했다. - 최종 해결:
- Google Cloud Console의 '승인된 리디렉션 URI'에 백엔드 엔드포인트(
.../login/oauth2/code/google)를 로컬 및 배포용으로 등록했다. - 프론트엔드에 로그인 버튼 클릭 시, 백엔드가 제공하는 인증 주소로
window.location.href를 변경하도록 요청했다.
- Google Cloud Console의 '승인된 리디렉션 URI'에 백엔드 엔드포인트(