골프 회원권 시세 추적 SaaS. 실운영 중 → membershipflow.site
회원권 거래소 여덟 곳의 시세를 매일 자동 수집해 차트 시각화, 목표가 도달 시 실시간 WebSocket 알림. 구독(월 10,000원 / 연 90,000원)으로 전체 기간 데이터 및 관심 종목 무제한.
Spring Boot 3.5 · Java 21 · MySQL · Spring WebSocket/STOMP · Jsoup · TossPayments
Next.js 14 · TypeScript · Tailwind CSS · SWR
Docker Compose · GitHub Actions · nginx · Prometheus · Grafana · Loki · Grafana Alloy · AWS EC2
nginx (HTTPS)
├── /api, /ws, /admin → Spring Boot :8081
└── /* → Next.js :3000
Scheduler (매일 07:00)
└── Jsoup 크롤링 (동부회원권, 동아골프, 시세닷컴, 에이스회원권, 프리미엄회원권, 회원권 쿨거래, KB회원권거래소, 회원권SEARCH)
└── afterCommit() → AlertService → WebSocket 알림
Application EC2(t3.small)에 프론트, 백엔드, DB, node-exporter, mysqld-exporter, Alloy를 배치하고 Prometheus·Grafana·Loki는 독립 Observability EC2로 분리.
시세 수집
Jsoup으로 여덟 소스 크롤링. 프리미엄회원권·회원권 쿨거래·KB회원권거래소·회원권SEARCH는 기존 종목과 정확히 매칭되는 시세만 저장해 단일 출처 신규 종목 증가를 방지.
동아회원권은 Java 21이 기본 차단하는 구형 DH 키 사이즈 사용. JVM 전체 보안 정책 대신 @PostConstruct에서 해당 항목만 런타임 제거로 최소 범위 처리.
Jsoup HTML 파싱 시 & → & 디코딩 탓에 regex URL 추출 0건. select("a[href*=...]").attr("href") 방식으로 전환 해결.
1년치 히스토리 수집은 수십 초 소요로 nginx 504 발생. @Async + 즉시 202 반환으로 분리. CoinFlow HTTP 응답 분리 패턴 재적용.
구독 결제
TossPayments 빌링키 방식. 카드 1회 등록 후 선택한 월간·연간 주기로 자동결제. BillingScheduler가 매일 자정 만료 구독 재청구, 연속 3회 실패 시 SUSPENDED. 빌링키 AES-256 암호화 저장.
구독 상태에 따라 차트 기간(비구독자 7일 clamp)과 관심 종목 한도(비구독자 3개) 게이팅.
알림
수집 완료 후 afterCommit() 트리거. 커밋 전 알림 발송 시 DB에 없는 데이터 기준 알림 발생 방지. STOMP /user/queue/alert push, alert_log로 24시간 중복 방지.
CI/CD
main push → 테스트 통과 → Docker 이미지 빌드 → scp로 nginx·Docker Compose·Alloy 설정 EC2 자동 복사 → 백엔드 health gate와 telemetry 상태 검증. 수동 SSH 없이 코드 변경 즉시 반영.
Application EC2의 node-exporter·mysqld-exporter가 호스트와 MySQL 메트릭을 노출하고, Alloy가 백엔드 JSON 로그와 MySQL lock 데이터를 독립 Observability EC2의 Loki로 전송. Prometheus·Grafana·Loki는 Observability EC2에서 운영.
상세 배포 구조와 환경변수는 docs/DEPLOYMENT.md 참고.
cp .env.example .env # DB, JWT, Google OAuth, TossPayments 키 설정
docker compose up -d- CoinFlow — 비동기 응답 분리, STOMP 구조 원본
- HomeSweetHome — TossPayments 결제 정합성 원본