컴퓨터 비전 기반 옥외광고(OOH) 효과 측정 플랫폼의 백엔드다. 팀·캠페인·매체를 관리하고, 매체에 설치된 비전 디바이스가 SQS로 보내는 유동/노출/주목 인구 데이터를 받아 캠페인별 홈 대시보드(깔대기 그래프, 시간대별 노출도, 시청시간, 성별·연령 통계 등)로 보여준다.
- Backend: 이 저장소
- Frontend: Shinhan-KLLJS/frontend (배포)
- AI/Vision: Shinhan-KLLJS/ai
- 사업자등록증 OCR: Shinhan-KLLJS/biz-ocr
| 도메인 | 기능 |
|---|---|
| 인증 | 카카오 소셜 로그인(OAuth2), Access/Refresh Token 발급·회전·재사용 탐지, 로그아웃 |
| 사용자 | 내 정보 조회 |
| 팀 | 사업자등록증 업로드(OCR) 기반 팀 생성, 팀명 수정 |
| 팀원 관리 | 초대 코드 발급/합류, 역할(OWNER/ADMIN/MEMBER) 변경, 팀원 삭제·나가기 |
| 매체 | 옥외광고 매체(전광판 등) 등록·검색·지역 필터 |
| 캠페인 등록 | 소재(이미지/영상) 업로드, 매체·기간 선택, 최종 등록 |
| 캠페인 페이지 | 팀 캠페인 목록/상세 조회, 캠페인명 수정, 삭제 |
| 홈 대시보드 | 캠페인 목록/상세/송출정보, 실시간·시간별 노출·주목 흐름 그래프, 평균 시청시간, 성별·연령 시청 비율, 시간대별 노출도 히트맵, 깔대기 그래프(유동→노출→주목→전환) |
| 유동인구 적재(관리자) | 서울시 250m 격자 생활인구 데이터 수동 적재 |
전체 API 목록과 상세 설명은 API 문서(Swagger)에서 도메인 단위로 확인할 수 있다.
| 구분 | 내용 |
|---|---|
| Language / Runtime | Java 21 |
| Framework | Spring Boot 4.1.0 (Web, Data JPA, Security, Validation, OAuth2 Client/Resource Server, Actuator) |
| 인증 | 카카오 OAuth2 로그인 + 자체 JWT(Access) / HttpOnly 쿠키(Refresh, 1회용 회전) |
| DB | MySQL 8 (운영, RDS) / H2 in-memory (로컬) |
| 마이그레이션 | Flyway (운영만 적용, 로컬은 Hibernate ddl-auto=create-drop) |
| API 문서 | springdoc-openapi (Swagger UI) |
| AWS | S3(소재·사업자등록증 업로드), SQS(비전 데이터 수신), Lambda(OCR 직접 invoke), CloudWatch(로그·대시보드), EC2/ALB/RDS |
| 기타 라이브러리 | PDFBox(사업자등록증 PDF 검증), proj4j(위경도 ↔ 서울시 250m 격자좌표 변환) |
| 빌드 | Gradle |
| 배포 | Docker → Docker Hub → EC2(SSM Run Command) |
| CI/CD | GitHub Actions |
main 브랜치에 푸시되면 GitHub Actions가 Docker 이미지를 빌드해 Docker Hub에 올리고,
private subnet에 있는 EC2에 SSH 대신 AWS SSM Run Command로 배포 스크립트를 전달한다.
배포 스크립트는 /actuator/health가 정상 응답할 때까지 기다렸다가, 실패하면 직전에 성공했던
이미지로 자동 롤백한다(단, 되돌릴 수 없는 컬럼/테이블 삭제 마이그레이션이 낀 배포는 자동
롤백이 안전하지 않아 수동 대응이 필요하다).
마크다운으로 작성된 최신 ERD와 설계 결정 배경은 docs/mvp-database-erd.md에 있다 (Mermaid로 그려져 있어 GitHub에서 별도 이미지 없이도 바로 렌더링된다).
- 로컬: 서버 실행 후 http://localhost:8080/swagger-ui/index.html
- 운영: 배포된 서버의
/swagger-ui/index.html
인증/사용자/팀/팀원 관리/사업자등록증/매체/캠페인 등록/캠페인 페이지/홈 대시보드/유동인구 적재(관리자) 순서로 도메인 단위로 정리되어 있다.
사전 준비물은 JDK 21뿐이다. 로컬 프로필은 H2 인메모리 DB를 쓰므로 Docker나 별도 DB 서버가 필요 없다.
git clone https://github.com/Shinhan-KLLJS/backend.git
cd backend
./gradlew bootRun # Windows CMD: gradlew.bat bootRun기본적으로 local 프로필로 뜨며(application.yml), 뜰 때 다음이 자동으로 이뤄진다.
- H2 인메모리 스키마 자동 생성(
ddl-auto=create-drop) — Flyway는 로컬에서 비활성화 - 테스트용 시드 데이터 생성: 사용자 1명(OWNER) + 팀 1개 + 매체 1개 + 캠페인 1개
- 그 사용자로 즉시 쓸 수 있는 Access Token을 애플리케이션 로그에 출력
(
Local dashboard test Authorization header: Bearer ...) - H2 콘솔: http://localhost:8080/h2-console (JDBC URL
jdbc:h2:mem:klljsdb;MODE=MySQL, usersa, 비밀번호 없음)
로그에 출력된 토큰을 Authorization: Bearer {token} 헤더에 넣으면 Swagger UI나 curl로 바로
API를 호출해볼 수 있다. 시드 데이터가 필요 없으면 LOCAL_DASHBOARD_MOCK_DATA_ENABLED=false
환경변수로 끌 수 있다.
카카오 로그인 자체(/oauth2/authorization/kakao)를 로컬에서 끝까지 테스트하려면
KAKAO_REST_API_KEY/KAKAO_CLIENT_SECRET/KAKAO_REDIRECT_URI 환경변수로 실제 카카오
앱 값을 넣어야 한다. 넣지 않아도 서버는 정상적으로 뜬다(플레이스홀더 값 사용).
로컬은 application-local.yml에 실행 가능한 기본값이 이미 채워져 있어 별도 설정이 필요
없다. 아래는 prod 프로필(운영 배포)에 주입해야 하는 값들이다 — 실제 배포는 GitHub
Actions CD가 AWS SSM Parameter Store에서 읽어와 컨테이너 환경변수로 주입한다
(.github/workflows/cd.yml 참고).
| 변수 | 용도 |
|---|---|
SPRING_DATASOURCE_URL / _USERNAME / _PASSWORD |
RDS MySQL 접속 정보 |
KAKAO_REST_API_KEY / KAKAO_CLIENT_SECRET / KAKAO_REDIRECT_URI |
카카오 OAuth2 앱 정보 |
APP_FRONTEND_URL / ADDITIONAL_ALLOWED_ORIGINS |
CORS·리다이렉트 허용 오리진 |
APP_SWAGGER_ORIGIN |
Swagger UI 자신의 오리진(CSRF 신뢰 목록에 자동 포함) |
REFRESH_COOKIE_SAME_SITE |
Refresh Token 쿠키 SameSite (기본 Lax) |
JWT_SECRET |
자체 Access/Refresh Token 서명 키(HMAC) |
VISION_SQS_QUEUE_URL |
비전 디바이스 데이터 수신 SQS 큐 |
CAMPAIGN_CREATIVE_BUCKET / _PUBLIC_BASE_URL / _UPLOAD_TOKEN_SECRET |
캠페인 소재 업로드용 S3 |
BUSINESS_REGISTRATION_BUCKET / _UPLOAD_TOKEN_SECRET / _OCR_FUNCTION |
사업자등록증 업로드·OCR용 S3/Lambda |
민감한 값(시크릿·비밀번호)은 전부 SSM SecureString으로 저장하고, 코드에는 기본값을 두지
않아 값이 없으면 기동 자체가 실패하도록 되어 있다.
./gradlew test일반 테스트는 H2로 돈다. 잠금(락) 동작을 검증하는 동시성 테스트는 H2(READ COMMITTED)와
MySQL InnoDB(REPEATABLE READ)의 격리 수준 차이로 H2에서는 재현되지 않는 버그가 있어
mysql-concurrency 태그로 분리되어 있고, 기본 test 실행에서는 제외된다. 실제 MySQL로
검증하려면:
docker run -d --name klljs-mysql-test -p 13308:3306 \
-e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_DATABASE=klljs_concurrency_test \
mysql:8.0.46
./gradlew mysqlConcurrencyTest
docker rm -f klljs-mysql-test운영 DB 스키마는 src/main/resources/db/migration의 Flyway 마이그레이션(V1~V13)으로 관리하고,
애플리케이션은 ddl-auto=validate로 스키마와의 불일치만 검증할 뿐 직접 바꾸지 않는다. 로컬은
반대로 Flyway 없이 Hibernate ddl-auto=create-drop으로 매번 새로 만든다.
검증은 반드시 실제 MySQL로 한다 — 로컬 프로필은 Flyway 자체를 안 타고, H2는 잠금/제약
동작이 MySQL과 달라 Flyway·FK·동시성 관련 버그를 못 잡는다. 새 마이그레이션을 추가하면
mysql:8.0.46 Docker 컨테이너에 V1부터 전체 이력을 처음부터 적용해보고 통과하는지 확인할 것
(신규 스테이징 환경, MySQL 기반 로컬 셋업, 마이그레이션만으로 하는 DR 복구가 모두 이 경로를
탄다 - V12__recreate_vision_summary_5s.sql가 한동안 이 경로에서 "Table already exists"로
막혀 있었던 사례가 있다).
이미 운영에 적용된 마이그레이션 파일은 원칙적으로 수정하지 않는다. 불가피하게 수정하면
(예: V12처럼 사고 복구용으로 작성된 마이그레이션을 신규 환경에서도 안전하게 만들 때)
파일 내용이 바뀌어 checksum이 바뀌므로, 그 버전이 이미 적용된 환경(운영)에서는 다음 배포 시
Flyway validate가 checksum mismatch로 실패해 앱이 기동하지 않는다. 이런 수정을 배포하기 전에는
운영 DB에 대해 먼저 flyway repair(또는 동등하게 flyway_schema_history의 해당 버전 checksum을
새 파일 기준으로 수동 갱신)를 실행해야 한다 - repair는 메타데이터만 갱신하고 SQL을 실행하지
않으므로 운영 스키마 자체에는 영향이 없다.
- CI (
.github/workflows/ci.yml):main/dev로의 push·PR마다./gradlew test실행 - CD (
.github/workflows/cd.yml):mainpush 시 Docker 이미지 빌드·푸시 → EC2에 SSM Run Command로 배포 → 헬스체크 실패 시 직전 이미지로 자동 롤백
| 문서 | 내용 |
|---|---|
| docs/mvp-database-erd.md | 전체 ERD와 테이블별 설계 결정 |
| docs/team-creation-api-spec.md | 팀 생성(사업자등록증 업로드·OCR) API 설계 |
| docs/campaign-registration-api-spec.md | 캠페인 등록(소재 업로드·최종 등록) API 설계 |
| docs/campaign-page-api-spec.md | 캠페인 페이지(목록/상세/수정/삭제) API 설계 |
| docs/server-verification-spec.md | 사업자등록증 진위확인 명세 (MVP 범위 제외, 보류) |
| docs/dv-112-open-decisions.md | 구현 중 발생한 팀 결정 필요 안건 모음 |


