Spring Boot 기반의 중고 거래 플랫폼으로, 4명의 팀원이 역할을 분담하여 개발한 팀 프로젝트입니다.
MSA(마이크로서비스 아키텍처)로 설계하여 finalProject_3(회원·마이페이지·채팅·챗봇·결제)와
board_service(게시판)를 독립 서비스로 분리하고, API Gateway를 통해 단일 진입점으로 연결했습니다.
회원 인증부터 상품 등록/거래, 실시간 채팅, 결제, AI 챗봇, 관리자 기능까지 실제 서비스 수준의 다양한 기능을 구현하였으며,
Spring Security, WebSocket, Firebase, Gemini AI 등 여러 기술을 실무적으로 적용해 보았습니다.
실시간 메시지 전송은 WebSocket(STOMP)으로 처리하고, 메시지 영구 저장 및 이전 대화 조회는 Firebase Realtime Database를 활용하는 이중 구조로 설계했습니다. Oracle DB에는 채팅방 정보만 관리하여 DB 부하를 줄이고, 빠른 메시지 조회가 가능하도록 구성했습니다.
일반 로그인(Spring Security + JWT)과 소셜 로그인(OAuth2 - Google, Kakao, Naver)을 하나의 Security 설정으로 통합하는 것이 복잡했습니다. 각 소셜 로그인의 사용자 정보 형식이 달라 CustomOAuth2UserService에서 공통 처리 로직을 설계하여 해결했습니다.
회원가입과 계정 찾기 두 가지 흐름에서 각각 다른 세션 키(smsAuthCode, smsFindCode)를 사용하여 인증번호를 관리했습니다. 인증번호 유효 시간을 3분으로 제한하고, 인증 완료 시 세션에서 즉시 제거하는 방식으로 보안을 강화했습니다.
마이페이지의 계좌 정보는 민감 데이터이기 때문에 AES-256 양방향 암호화를 적용하여 DB에 저장하고, 조회 시 복호화하는 방식으로 처리했습니다.
Google · Kakao · Naver가 각각 다른 필드명으로 이메일·닉네임을 반환하여 공통 처리 로직 적용 불가
Provider별 Service 클래스(NaverOAuth2UserService, GoogleOAuth2UserService 등)로 분리하고, 각각 파싱한 뒤 공통 MemberDTO로 변환하는 방식으로 통합
Firebase 비동기 응답을 CompletableFuture.get()으로 대기할 때 타임아웃 미설정으로, 연결 불안정 시 스레드가 무한 블로킹 상태에 빠짐
future.get(10, TimeUnit.SECONDS)로 타임아웃을 지정하여 일정 시간 내 응답이 없으면 예외를 발생시키고 사용자에게 오류를 반환하도록 처리
WebSocket 엔드포인트가 permitAll()로 인증 없이 열려 있어, 인증되지 않은 사용자도 채팅 연결 시도 가능
ChannelInterceptor를 구현하여 STOMP 연결 헤더에서 JWT를 추출·검증하면 WebSocket 단계에서도 인증 적용 가능
AccessToken 만료 시 자동 재발급 엔드포인트가 없어 토큰이 만료되면 사용자가 다시 로그인해야 함
/api/auth/refresh 엔드포인트를 구현하여 RefreshToken으로 AccessToken을 자동 갱신하면 UX 개선 및 토큰 만료 시간 단축으로 보안 강화 가능
SonarCloud 정적 분석을 도입하여 코드 품질 이슈를 체계적으로 개선했습니다. Claude Code를 활용하여 다수의 이슈를 분석하고 일괄 제거했습니다.
주요 개선 사항: 접근성(aria 속성, alt 텍스트), 보안(전역 변수 → globalThis),
코드 스멜(for-of, 함수 스코프), 중복 코드(sell.html·share.html 통합, SQL 중복 제거)
Before
Issues ~1,900 · Duplications 2.5% · Quality Gate Not computed
After
New Issues 2 · Duplications 1.26% · Quality Gate Passed
| 이슈 유형 | 발견된 문제 | 해결 방법 |
|---|---|---|
| 보안 | SmartEditor에 포함된 미사용 샘플 파일(file_uploader.php 등 14개)이 임의 파일 업로드 취약점으로 탐지 |
sample/photo_uploader/ 디렉터리 전체 삭제 (14개 파일, 14,073줄 제거) |
| 접근성 | 이미지 alt 누락, 버튼·입력 필드 aria-label 미적용, <label for> 연결 누락, 빈 <h*> 태그, <title> 없는 페이지 |
전체 템플릿에 alt·aria-label·aria-hidden 추가, label for-input id 연결, 빈 heading 제거, <title> 일괄 추가 (17개 배치에 걸쳐 수정) |
| JS 품질 | getAttribute()로 data-* 접근, parseFloat/parseInt/isNaN 전역 함수 사용, window 직접 참조, for 인덱스 순회, 중첩 삼항·template literal |
getAttribute → dataset.* 교체, 전역 함수 → Number.parseFloat/Number.isNaN 등으로 변경, window → globalThis, for-of 루프 적용, 중첩 표현식 단순화 |
| 인지 복잡도 | product_detail.js·product_form.js·chat_popup.html 등에서 함수 중첩 깊이 초과, 복잡한 분기 로직으로 SonarCloud 임계값 위반 |
긴 함수를 역할별로 분리, 조건 로직을 early return 패턴으로 단순화, 중첩 콜백을 외부 함수로 이동 |
| CSS 품질 | 색상 대비 불충분 (#22c55e, #ff6a00 등이 WCAG 기준 미달), CSS 파일 내 중복 선택자 |
저대비 색상을 #15803d·#a84400 등 진한 색으로 교체, 중복 선택자 병합 (product_form.css, product_list.css 등) |
| 중복 코드 | SQL 파일에 CREATE TABLE REPORTS 블록 2회 정의, sell.html·share.html이 거의 동일한 HTML 구조를 중복 보유 |
SQL 중복 블록 63줄 제거, sell.html·share.html을 product_form.html 하나로 통합 (duplication 2.5% → 1.26%) |
| SQL 품질 | SELECT * 사용으로 불필요한 컬럼 전체 조회, CHAR 타입 사용으로 공백 패딩 발생 가능성 |
SELECT *를 필요한 컬럼 명시로 교체, CHAR → VARCHAR2로 변경 |
Spring Boot Actuator + Prometheus + Grafana를 연동하여 서버 상태(CPU, Memory, HTTP 요청 수 등)를 대시보드로 실시간 모니터링했습니다.
SpringDoc OpenAPI(Swagger UI)를 적용하여 REST API 명세를 자동으로 문서화하고, 브라우저에서 직접 API를 테스트할 수 있도록 했습니다.
GitHub에 git push하면 Webhook이 Jenkins를 트리거하여 자동으로 빌드·테스트·Docker 이미지 빌드·푸시·배포까지 이어지는 CI/CD 파이프라인을 구축했습니다.
finalProject_3와 board_service 각 서비스별로 독립적인 파이프라인을 구성하여 서비스 단위 배포가 가능하도록 했습니다.
Jenkins 파이프라인으로 빌드된 Docker 이미지를 Portainer.io를 통해 배포하고, 각 서비스 컨테이너 상태를 GUI로 모니터링했습니다.