전체 프로젝트
Team Project

HUFJOK

@hufs.ac.kr 이메일 인증으로 외대생만 접근할 수 있는 시험자료 공유 플랫폼입니다. 자료를 업로드하면 포인트를 받고, 포인트로 다른 자료를 구매할 수 있습니다.

기간
2025.09 – 2025.12
팀 구성
6명 (BE 3 · FE 3)
역할
백엔드 개발
완성 여부
완성 ✓
JavaSpring BootMySQL AWS EC2JPAGoogle OAuth2Spring Security
01

기획 의도

외대생이 시험 전 자료를 구하려면 에브리타임, 오픈채팅, 선배 개별 연락에 의존해야 하는 비효율이 있었습니다.

@hufs.ac.kr 이메일 인증으로 외대 학생만 접근할 수 있는 시험자료 공유 플랫폼을 개발했습니다. 자료를 업로드하면 포인트를 지급하고, 포인트로 다른 자료를 구매하는 구조로 업로드 유인을 설계했습니다.

핵심 가치 — 외대 학생 전용 폐쇄 생태계 + 포인트 기반 선순환 구조
02

시연 영상

03

시스템 구조

DomainUser, Material, Attachment, Point, PointHistory, Review, UserMaterial
ControllerAuth, Material, MyPage, Review, User
ServiceMaterial, Attachment, Point, Review, User
SecurityGoogle OAuth2, CustomOAuth2UserService, Spring Security
FileFileStore (로컬 절대경로 기반)
04

담당 역할 · 주요 구현

1

소셜 로그인

Google Workspace @hufs.ac.kr 도메인만 허용하도록 CustomOAuth2UserService에서 도메인을 검증하고, 인증 기반으로 파일 다운로드·구매 API 접근을 제어했습니다.

2

자료 구매 로직 전면 재설계

초기 파일 단위 구매에서 게시물 단위 구매로 설계를 바꾸고, 구매 API와 다운로드 API를 분리해 더 유연한 확장 구조로 개선했습니다.

3

자료 목록 고도화

다운로드 수·리뷰 수를 동적으로 집계하고 전공(Major) 필터를 추가하는 등, Response DTO를 여러 차례 반복 개선했습니다.

4

포인트 시스템

가입 시 500포인트를 자동 지급하고, PointHistory 엔티티로 포인트 이력 전체를 관리했습니다.

5

파일 저장 · 다운로드

FileStore에서 절대경로로 파일을 저장하고 multipart 업로드를 처리했습니다. 배포 환경에서 상대경로로 파일을 찾지 못하는 문제를 겪은 뒤 절대경로 방식으로 전환했습니다.

6

배포

AWS EC2에 배포하며 CORS 설정, OAuth2 리다이렉트 URI, 배포 환경 연동, 프론트 배포 주소 적용을 진행했습니다.

05

트러블슈팅

포인트 이력 누락 에러 (5번의 이터레이션 끝에 해결)

문제

신규 회원가입 시 500 에러가 발생하거나, 500포인트가 지급돼도 포인트 이력 조회 API가 빈 배열을 반환했습니다.

원인

CustomOAuth2UserService와 UserService에서 유저 생성이 중복 시도되는 문제를 먼저 고쳤지만, 근본 원인은 User 저장 트랜잭션이 커밋되기 전에 PointHistory 저장을 시도해 FK 제약을 위반하는 것이었습니다. saveAndFlush로도 해결되지 않았습니다.

해결

@Transactional(propagation = REQUIRES_NEW)로 트랜잭션을 분리해 User 저장이 먼저 커밋되도록 보장했습니다. 단순히 save() 순서를 바꾸는 게 아니라 트랜잭션 경계 자체를 분리해야 하는 문제였다는 걸 배웠습니다.

내 자료 목록 빈 배열 반환

문제

자료 업로드 후 GET /api/v1/me/materials가 200 OK임에도 빈 배열을 반환했습니다.

원인

findByUserIdAndIsDeletedFalse 메서드 네이밍이 중첩 경로(m.user.id)를 인식하지 못했습니다.

해결

@Query로 JPQL을 명시해 SELECT m FROM Material m WHERE m.user.id = :userId AND m.isDeleted = FALSE로 해결했습니다.

삭제된 자료가 계속 노출됨

문제

논리 삭제(isDeleted = true) 후에도 상세 조회·구매 목록에서 자료가 계속 조회됐습니다.

원인

서비스 레이어에서 isDeleted 체크가 누락되어 있었습니다.

해결

getMaterial()에 삭제 체크를 추가하고, getMyDownloadedMaterials()에는 스트림 필터를 추가했습니다.

리뷰 수 2배 집계

문제

리뷰 1개를 작성했는데 reviewCount가 2로 반환됐습니다.

원인

Review 엔티티의 reviewCount 필드와 increaseReviewCount() 메서드에서 이중으로 증가시키고 있었습니다.

해결

reviewCount 필드를 제거하고 getReviews().size()로 동적 계산하도록 전환했습니다.

OAuth2 리다이렉트 불일치

문제

프론트 배포 후 로그인 시 Google에서 redirect_uri_mismatch 400 에러가 발생했습니다.

원인

프론트가 /oauth2/authorization/google 대신 서버로 직접 요청하면서, redirect_uri를 프론트 주소로 잘못 설정하고 있었습니다.

해결

Google Cloud Console에 백엔드 엔드포인트를 등록하고, SwaggerConfig에 환경별 서버 URL을 분리했습니다.

06

성과

82 / 154
백엔드 커밋 (약 53%)
2등
멋쟁이사자처럼 동아리 내 미니프로젝트
배포 완료
hufjok.lion.it.kr에 배포했습니다
← 이전 프로젝트Roomfit