대학 상권 기반의 커뮤니티 및 상점 리뷰 플랫폼 API 서버입니다. 학생, 점주, 관리자가 상호작용하며 리뷰, 쿠폰, 즐겨찾기 기능을 제공합니다.
- 대학가 주변 상권의 정보 비대칭 해소
- 학생 혜택(쿠폰) 및 신뢰성 있는 리뷰 시스템 구축
- 상점 주인과 고객 간의 직접적인 소통 채널(단골/쿠폰) 부재 해결
- 인증/인가: JWT (Access/Refresh + RRT), OAuth2 (Kakao/Google), Firebase Auth, RBAC (Role-Based Access Control)
- 사용자 관리: 학생, 점주, 관리자 등 역할별 프로필 관리
- 상점/상품: 점주의 상점/메뉴 CRUD, 위치 기반/카테고리별 검색
- 리뷰/즐겨찾기: 실사용자 리뷰 작성, 단골 등록 및 통계 제공
- 쿠폰 시스템: 점주가 발행하고 고객이 발급/사용하는 할인 쿠폰 (재고/유효기간 관리)
- Language: Java 17
- Framework: Spring Boot 3.4.0
- Build Tool: Gradle 8.x
- RDBMS: MySQL 8.0 (JPA/Hibernate)
- NoSQL: Redis (Refresh Token, Rate Limiting)
- Migration: Flyway (Schema Management)
- Infra: Docker, Docker Compose, JCloud (Linux)
- CI/CD: GitHub Actions
- Core: Spring Security, JWT (Access/Refresh + RRT)
- OAuth2: Kakao, Google
- External: Firebase Admin SDK
- Rate Limiting: Bucket4j
- Docker & Docker Compose
- JDK 17+ (로컬 개발 시)
데이터베이스(MySQL), 캐시(Redis), API 서버를 한 번에 실행합니다. 초기 데이터(Seed Data)는 컨테이너 실행 시 Flyway를 통해 자동으로 주입됩니다.
# 1. 프로젝트 클론
git clone https://github.com/dodohy25n/wsd-termproject.git
cd wsd-termproject
# 2. Docker Compose 실행
docker-compose up -d --build
# 3. 로그 확인 (실행 완료 대기)
docker-compose logs -f appDB와 Redis는 Docker로 띄우고, Spring Boot만 로컬에서 실행할 경우:
# 1. DB/Redis 실행
docker-compose up -d mysql redis
# 2. 애플리케이션 빌드 및 실행
# Linux/macOS
./gradlew clean build
java -jar build/libs/wsd-termproject-0.0.1-SNAPSHOT.jar
# Windows
./gradlew.bat clean build
java -jar build/libs/wsd-termproject-0.0.1-SNAPSHOT.jar로컬 개발 시에는 application.yml의 기본값을 사용하거나 .env 파일을 생성하여 설정할 수 있습니다.
배포 시에는 GitHub Secrets를 통해 관리됩니다.
| 변수명 | 설명 | 비고 |
|---|---|---|
DB_ROOT_PASSWORD |
MySQL Root 비밀번호 | |
DB_USER |
MySQL 애플리케이션 계정 | |
DB_PASSWORD |
MySQL 애플리케이션 비번 | |
JWT_SECRET |
JWT 서명 비밀키 | 32자 이상 필수 |
GOOGLE_CLIENT_ID |
Google OAuth ID | |
GOOGLE_CLIENT_SECRET |
Google OAuth Secret | |
KAKAO_CLIENT_ID |
Kakao OAuth ID | |
FIREBASE_KEY_PATH |
Firebase 키 경로 |
본 프로젝트는 학교 JCloud 인프라에 배포되었습니다. CI/CD 파이프라인(GitHub Actions)을 통해 메인 브랜치 푸시 시 자동 배포됩니다.
- 배포 URL: http://175.123.55.182:3210
- API 문서 (Swagger UI): http://175.123.55.182:3210/docs
- Health Check: http://175.123.55.182:3210/health
- Container Request: GitHub Actions가 코드를 빌드하고 Docker Image를 생성하여 Docker Hub에 Push합니다.
- Deploy Trigger: 배포 서버(JCloud)에 SSH로 접속하여 최신 이미지를 Pull 받고 컨테이너를 재시작합니다.
- Service Orchestration:
docker-compose를 사용하여 Spring Boot 앱, MySQL, Redis를 하나의 네트워크로 관리합니다. - Auto Healing:
restart: always정책을 적용하여 서버 재부팅이나 예기치 않은 종료 시 컨테이너가 자동으로 복구되도록 설정했습니다.
| 단계 | 설명 |
|---|---|
| CI (Continuous Integration) | main 브랜치 Push 및 PR 시 트리거 |
| - Test | Gradle 기반 유닛/통합 테스트 수행 (Redis 서비스 컨테이너 활용) |
| - Build | 애플리케이션 빌드 검증 |
| CD (Continuous Deployment) | main 브랜치 Push 시에만 트리거 |
| - Login | Docker Hub 로그인 |
| - Push | Docker Image 빌드 및 태깅(latest) 후 레지스트리 전송 |
| - Deploy | JCloud 서버에 SSH 접속 -> docker-compose pull -> up -d 실행 |
- Base Image:
eclipse-temurin:17-jre-jammy - Multi-stage Build: 빌드(Gradle)와 실행(JRE) 단계를 분리하여 이미지 크기를 최적화했습니다.
- Logging (ELK Stack):
- Logstash: Spring Boot 앱에서 발생한 로그를 TCP(5000)로 수집합니다.
- Elasticsearch: 수집된 로그를 인덱싱하고 저장합니다.
- Kibana: 저장된 로그를 시각화하고 검색할 수 있는 대시보드를 제공합니다 (Port: 5601).
본 프로젝트는 보안 강화를 위해 **JWT (JSON Web Token)**와 RRT (Refresh Token Rotation) 방식을 채택하고 있습니다.
-
로그인 (Login)
- 사용자가 ID/PW (
/api/auth/login) 또는 소셜 계정(Google/Kakao)으로 로그인 요청을 보냅니다. - 서버는 인증 성공 시 Access Token과 Refresh Token을 생성합니다.
- Access Token: Response Body로 전달 (클라이언트 메모리 저장 권장).
- Refresh Token:
HttpOnly Secure Cookie로 전달 (XSS 공격 방지).
- 사용자가 ID/PW (
-
토큰 갱신
- Access Token이 만료되면 클라이언트는
/api/auth/refresh엔드포인트를 호출합니다. - 서버는 쿠키에 담긴 Refresh Token을 검증하고 Redis에 저장된 값과 비교합니다.
- 검증 성공 시 새로운 Access Token과 새로운 Refresh Token을 발급합니다 (RRT).
- 기존 Refresh Token은 폐기하여 탈취 시 재사용을 방지합니다.
- Access Token이 만료되면 클라이언트는
-
로그아웃 (Logout)
/api/auth/logout호출 시 Redis에서 해당 사용자의 Refresh Token을 삭제합니다.- 클라이언트의 Refresh Token 쿠키를 만료 처리합니다.
-
일반 로그인 (General)
- Client가 ID/PW 전송 → Server가 DB 검증 → 성공 시 JWT(RefreshToken/AccessToken) 발급
-
소셜 로그인 (OAuth2 - Google/Kakao)
- Client가 소셜 로그인 링크 접속 → Provider 인증 후 리다이렉트 → Server가 인가 코드로 Access Token 요청 → 사용자 정보 조회 → (첫 방문 시
GUEST로 회원가입) → JWT 발급
- Client가 소셜 로그인 링크 접속 → Provider 인증 후 리다이렉트 → Server가 인가 코드로 Access Token 요청 → 사용자 정보 조회 → (첫 방문 시
-
파이어베이스 로그인 (Firebase)
- Client가 Firebase SDK로 인증 후 ID Token 획득 → Server에 ID Token 전송 → Server가 Firebase Admin SDK로 검증 → 사용자 정보 추출 → (첫 방문 시
GUEST로 회원가입) → JWT 발급
- Client가 Firebase SDK로 인증 후 ID Token 획득 → Server에 ID Token 전송 → Server가 Firebase Admin SDK로 검증 → 사용자 정보 추출 → (첫 방문 시
역할 (Role) |
설명 | 접근 가능 범위 |
|---|---|---|
| ROLE_GUEST | 가입 대기 | 소셜 로그인 직후, 추가 정보 입력 전 상태 |
| ROLE_CUSTOMER | 일반/학생 | 상점 조회, 리뷰 작성, 쿠폰 발급/사용, 즐겨찾기 |
| ROLE_OWNER | 점주 | 본인 상점/상품/쿠폰 관리 (CRUD), 내 상점 통계 조회 |
| ROLE_ADMIN | 관리자 | 전체 사용자 관리, 토큰 강제 만료 등 시스템 관리 |
초기 데이터(V2__seed_data.sql)로 생성된 테스트 계정입니다.
모든 계정의 비밀번호는 1234 입니다.
| 역할 | Username (Email) | 비밀번호 | 비고 |
|---|---|---|---|
| 관리자 | admin@wsd.com |
1234 |
모든 권한 보유 |
| 점주 1 | owner1@wsd.com |
1234 |
'맛있는 식당' 보유 |
| 점주 2 | owner2@wsd.com |
1234 |
'분위기 좋은 카페' 보유 |
| 사용자 1 | user1@wsd.com |
1234 |
구매자 (리뷰/쿠폰 테스트용) |
| 사용자 2 | user2@wsd.com |
1234 |
구매자 |
| 도메인 | 메서드 | URL | 설명 | 권한 |
|---|---|---|---|---|
| Auth | POST |
/api/auth/signup |
회원가입 | ALL |
POST |
/api/auth/login |
로그인 (JWT 발급) | ALL | |
POST |
/api/auth/firebase |
파이어베이스 로그인 | ALL | |
POST |
/api/auth/refresh |
토큰 갱신 | ALL | |
POST |
/api/auth/logout |
로그아웃 | ALL | |
POST |
/api/auth/complete-social-signup |
소셜 회원가입 완료 | GUEST | |
| Store | POST |
/api/stores |
상점 등록 | OWNER |
GET |
/api/stores |
상점 목록 조회 (검색/필터) | ALL | |
GET |
/api/stores/{storeId} |
상점 단건 조회 | ALL | |
PATCH |
/api/stores/{storeId} |
상점 정보 수정 | OWNER (My) | |
DELETE |
/api/stores/{storeId} |
상점 삭제 | OWNER (My) | |
| Item | POST |
/api/stores/{storeId}/items |
상품 등록 | OWNER (My) |
GET |
/api/stores/{storeId}/items |
상점별 상품 목록 조회 | ALL | |
GET |
/api/items/{itemId} |
상품 단건 조회 | ALL | |
PATCH |
/api/items/{itemId} |
상품 수정 | OWNER (My) | |
DELETE |
/api/items/{itemId} |
상품 삭제 | OWNER (My) | |
| Review | POST |
/api/stores/{storeId}/reviews |
리뷰 작성 | CUSTOMER |
GET |
/api/stores/{storeId}/reviews |
상점 리뷰 목록 조회 | ALL | |
GET |
/api/stores/{storeId}/reviews/stats |
상점 리뷰 통계 조회 | ALL | |
PATCH |
/api/reviews/{reviewId} |
리뷰 수정 | CUSTOMER (My) | |
DELETE |
/api/reviews/{reviewId} |
리뷰 삭제 | CUSTOMER (My) | |
| Coupon | POST |
/api/stores/{storeId}/coupons |
쿠폰 생성 | OWNER (My) |
GET |
/api/stores/{storeId}/coupons |
상점별 쿠폰 목록 조회 | ALL | |
PATCH |
/api/coupons/{couponId} |
쿠폰 수정 | OWNER (My) | |
DELETE |
/api/coupons/{couponId} |
쿠폰 삭제 | OWNER (My) | |
GET |
/api/items/{itemId}/coupons |
상품별 적용 가능 쿠폰 조회 | ALL | |
POST |
/api/coupons/{couponId}/issue |
쿠폰 발급 | CUSTOMER | |
POST |
/api/my-coupons/{customerCouponId}/use |
쿠폰 사용 | CUSTOMER | |
GET |
/api/my-coupons |
내 쿠폰 목록 조회 | CUSTOMER | |
| Favorite | POST |
/api/stores/{storeId}/favorites |
상점 즐겨찾기 추가 | CUSTOMER |
DELETE |
/api/stores/{storeId}/favorites |
상점 즐겨찾기 취소 | CUSTOMER | |
GET |
/api/stores/{storeId}/favorites/count |
상점 즐겨찾기 수 조회 | ALL | |
GET |
/api/favorites |
내 단골 상점 목록 조회 | CUSTOMER | |
| Admin | GET |
/api/admin/users |
전체 사용자 조회 | ADMIN |
PATCH |
/api/admin/users/{userId}/role |
사용자 권한 수정 | ADMIN | |
DELETE |
/api/admin/users/{userId} |
사용자 강제 탈퇴 | ADMIN | |
GET |
/api/admin/tokens |
전체 리프레시 토큰 조회 | ADMIN | |
GET |
/api/admin/tokens/{userId} |
특정 유저 토큰 조회 | ADMIN | |
DELETE |
/api/admin/tokens/{userId} |
특정 유저 토큰 삭제 (강제 로그아웃) | ADMIN | |
| Health | GET |
/health |
서버 상태 확인 (Health Check) | ALL |
모든 API 응답은 CommonResponse로 감싸서 반환됩니다.
{
"success": true,
"data": { ... },
"error": null
}{
"success": false,
"data": null,
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "요청한 리소스를 찾을 수 없습니다.",
"path": "/api/items/999"
}
}data 필드 내부에 페이지 정보가 포함됩니다.
{
"success": true,
"data": {
"content": [ ... ],
"pageNo": 0,
"pageSize": 10,
"totalElements": 100,
"totalPages": 10,
"last": false
},
"error": null
}-
데이터베이스 인덱싱
User엔티티의username,email컬럼에@Column(unique = true)를 적용하여 중복 방지 및 조회 성능을 보장합니다.Store엔티티의name필드에@Index를 적용하여 상점 이름 검색 성능을 최적화했습니다.
-
페이지네이션
- 상점, 리뷰 등 대량의 데이터가 예상되는 조회 API에
Pageable을 적용하여 메모리 효율성을 확보하고 응답 속도를 개선했습니다.
- 상점, 리뷰 등 대량의 데이터가 예상되는 조회 API에
-
지연 로딩 (Lazy Loading)
- 모든 연관관계(
@ManyToOne,@OneToOne)에FetchType.LAZY를 적용하여 불필요한 연관 엔티티의 즉시 로딩을 방지하고 N+1 발생 가능성을 최소화했습니다.
- 모든 연관관계(
-
트랜잭션 최적화
- 모든 Service의 단순 조회 로직에는
@Transactional(readOnly = true)를 적용하여 영속성 컨텍스트의 Dirty Checking(변경 감지)을 생략, 메모리 및 CPU 자원을 절약했습니다.
- 모든 Service의 단순 조회 로직에는
-
Redis 캐싱
- Refresh Token 저장소로 In-Memory DB인 Redis를 사용하여 빠른 응답 속도를 보장하고 DB 부하를 줄였습니다.
-
JWT & RTR (Refresh Token Rotation)
- Access Token은 Body로, Refresh Token은 HttpOnly Secure Cookie로 전달하여 XSS 공격 위험을 낮췄습니다.
- 토큰 갱신 시 Refresh Token도 함께 재발급(Rotation)하여, 만약 탈취되더라도 이전 토큰을 무효화하여 재사용을 차단합니다.
-
입력값 검증 (Validation)
- DTO에서
@NotBlank,@Email등의 Bean Validation 어노테이션을 사용하여 입력값을 1차로 검증합니다. - 검증 실패 시
GlobalExceptionHandler가 예외를 포착하여 상세한 필드별 에러 리스트를 JSON으로 반환합니다.
- DTO에서
-
커스텀 예외 핸들링
AuthenticationEntryPoint와AccessDeniedHandler를 구현하여 인증/인가 실패 시 표준화된 JSON 에러 포맷(CommonResponse)을 반환합니다.
-
레이트 리밋 (Rate Limiting)
- Bucket4j를 인터셉터 단에서 사용하여 IP당 분당 100회로 요청을 제한, 공격적인 트래픽을 차단합니다.
-
패스워드 암호화
BCryptPasswordEncoder를 사용하여 사용자 비밀번호를 안전하게 해시 암호화하여 저장합니다.
-
ELK Stack (Elasticsearch, Logstash, Kibana)
- 분산 로깅: Docker 컨테이너 환경에서 발생하는 로그를 Logstash(TCP)로 수집하여 Elasticsearch에 중앙 집중식으로 저장합니다.
- 시각화: Kibana 대시보드를 통해 실시간 로그 검색 및 통계 확인이 가능하며,
http://175.123.55.182:5601포트를 통해 접근할 수 있습니다.
-
구조화된 로깅 (SLF4J & Logback)
- 주요 비즈니스 로직(
AuthService,StoreService등)에 INFO(성공), WARN(실패/예외) 레벨을 구분하여 로그를 남깁니다. logstash-logback-encoder를 사용하여 로그를 JSON 포맷으로 구조화, 필드별 검색 및 분석 용이성을 확보했습니다.
- 주요 비즈니스 로직(
- 배포 시 다운타임 발생: 현재의
docker-compose down -> up배포 방식은 컨테이너 재시작 시간 동안 서비스 중단이 발생합니다. - 인메모리 레이트 리밋: Rate Limit 정보가 각 인스턴스의 메모리(
ConcurrentHashMap)에 저장되어, 다중 서버 환경에서는 제한이 공유되지 않는 한계가 있습니다. - 단일 DB 의존성: 읽기/쓰기 트래픽이 MySQL 단일 인스턴스에 집중되어 있어 대규모 트래픽 발생 시 병목이 생길 수 있습니다.
- 동기적 처리 구조: 쿠폰 발급 등 트래픽이 몰리는 로직이 동기식으로 구현되어 있어 응답 지연 가능성이 있습니다.
-
무중단 배포 도입
- Nginx를 리버스 프록시로 두고 Blue/Green 배포 전략을 적용하여 배포 중 서비스 중단을 제거할 예정입니다.
-
비동기/이벤트 기반 아키텍처
- 쿠폰 발급 요청 등을 Kafka/RabbitMQ 메시지 큐로 버퍼링하여 처리량(Throughput)을 증대시키고 시스템 결합도를 낮출 예정입니다.
-
Redis 기반 분산 레이트 리밋
- Bucket4j의 저장소를 Redis로 확장하여 분산 환경에서도 정확한 요청 제한을 구현할 예정입니다.
-
검색 성능 고도화
- MySQL Full-Text Index 또는 Elasticsearch 도입을 통해 검색 성능 및 정확도 개선.
-
모니터링 고도화
- Prometheus + Grafana를 도입하여 JVM, Connection Pool 상태를 시각화.