NBA Feed
팀 뉴스와 경기 정보를 제공, 점수는 직접 확인할 때까지 숨겨져 있습니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
벌금 기반 운동 인증 서비스(백엔드)
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
"헬창마을" — 벌금 기반 운동 인증 서비스의 Spring Boot 4.1 / Java 25 백엔드 API 서버입니다. 사용자는 운동방에 참여해 운동 인증을 올리고, 실시간으로 채팅하며, 매주 운동 횟수를 못 채우면 벌금을 냅니다. 그 외에 한국투자증권 주식 시세 연동과 FCM 푸시 알림 기능도 포함되어 있습니다.README 전문 보기 ↗
"헬창마을" — 벌금 기반 운동 인증 서비스의 Spring Boot 4.1 / Java 25 백엔드 API 서버입니다. 사용자는 운동방에 참여해 운동 인증을 올리고, 실시간으로 채팅하며, 매주 운동 횟수를 못 채우면 벌금을 냅니다. 그 외에 한국투자증권 주식 시세 연동과 FCM 푸시 알림 기능도 포함되어 있습니다.
| 구분 | 기술 |
|---|---|
| 언어 | (Gradle Toolchain으로 고정, 런타임 이미지는 Amazon Corretto 25 alpine) |
| 프레임워크 | |
| 빌드 도구 | (Groovy DSL, Gradle Wrapper 포함) |
| 데이터 접근 | Spring Data JPA(Hibernate) — default_batch_fetch_size: 500, open-in-view: false, ddl-auto: validate (스키마는 Flyway가 단독 관리) |
| 데이터베이스 | (dev/prod, mysql-connector-j), 로컬은 mariadb-java-client로 접속 |
| 캐시 | (spring-boot-starter-data-redis) + Spring Cache + Caffeine |
| 인증/보안 | Spring Security(Stateless JWT), io.jsonwebtoken:jjwt 0.11.5, OAuth2 Client(Google / Kakao / Naver 소셜 로그인) |
| 실시간 통신 | spring-boot-starter-websocket + spring-messaging (STOMP, /wss, SockJS 폴백) |
| 이메일 | spring-boot-starter-mail (회원가입 이메일 인증) |
| 외부 연동 | Firebase Admin SDK 9.10.0(FCM 푸시), 한국투자증권 Open API(주식 시세, RestTemplateConfig/httpclient5), AWS SDK v2(S3) |
| API 문서화 | springdoc-openapi-starter-webmvc-ui 3.0.3 (Swagger UI: /swagger-ui.html, OpenAPI: /v3/api-docs) |
| 비밀값 암호화 | Jasypt(jasypt-spring-boot-starter 4.0.4) — YAML에 ENC(...) 형식으로 암호화 저장 |
| 컨테이너 | |
| 로그 수집 | Logback → logstash-logback-encoder 9.0(JSON/TCP) → Logstash → Elasticsearch 9.5 (조회: Kibana). 로컬 스택은 docker-compose.yml |
| CI/CD | GitHub Actions → Docker Hub → EC2 SSH 배포(docker-compose) |
WorkoutRoom(그룹) 생성/참여, WorkoutRoomMember(멤버십) 관리.WorkoutRecord로 매일의 운동 인증(이미지 포함)을 기록합니다.WorkoutSchedulingService가 매주 월요일 자정(0 0 0 * * MON)에 @Scheduled
크론 잡으로 실행되어 주간 마감 및 벌금 계산을 트리거합니다. Penalty / PenaltyAccount 엔티티로 벌금
부과·납부 이력을 추적합니다./wss, SockJS 폴백)로 운동방 단위 실시간 채팅을 제공하며, HTTP 인증과
별개로 JwtChannelInterceptor가 WebSocket 채널의 JWT 인증을 담당합니다./api/stats/**는 인증 없이(permitAll) 접근 가능한 통계/랜딩 페이지용 집계 API를
제공합니다./api/admin/** 하위에 ROLE_ADMIN 권한이 필요한 회원/운동방 관리 API를 제공합니다.BE-HCM/
├── src/main/java/com/behcm/
│ ├── domain/ # 도메인 주도 구조 (레이어 우선 아님)
│ │ ├── auth/ # 회원가입/로그인, JWT 발급, OAuth2(Google/Kakao/Naver), 이메일 인증
│ │ ├── member/ # 회원 프로필/계정
│ │ ├── workout/ # WorkoutRoom, WorkoutRoomMember, WorkoutRecord, 주간 마감 스케줄러
│ │ ├── penalty/ # Penalty / PenaltyAccount (벌금 추적/납부)
│ │ ├── chat/ # WebSocket/STOMP 실시간 방 채팅
│ │ ├── notification/ # FCM 푸시 알림
│ │ ├── stats/ # 통계/랜딩 페이지용 집계 데이터 (permitAll)
│ │ ├── stock/ # 한국투자증권 API 주식/시세 연동
│ │ ├── rest/ # 기타 REST 엔드포인트
│ │ ├── admin/ # 관리자 전용 API (admin/member, admin/workout 등, ROLE_ADMIN)
│ │ └── common/ # 도메인 간 공유 요소 (HealthCheckController 등)
│ │ # 각 도메인 모듈: controller/ → service/ → repository/ (+ dto/, entity/)
│ └── global/
│ ├── config/ # SecurityConfig, RedisConfig, CacheConfig(Caffeine), AsyncConfig,
│ │ # RestTemplateConfig, JasyptConfig, FcmConfig
│ │ ├── aws/ # S3 설정
│ │ ├── stock/ # 한국투자증권 클라이언트 설정
│ │ ├── swagger/ # OpenAPI/Springdoc 설정
│ │ └── websocket/ # STOMP 브로커 + JWT 채널 인터셉터
│ ├── security/ # JWT provider/filter/entry point, OAuth2 성공/실패 핸들러
│ ├── exception/ # CustomException, ErrorCode(도메인별 그룹화), GlobalExceptionHandler
│ └── common/ # ApiResponse<T>, BaseTimeEntity(생성/수정 시각 감사)
├── src/main/resources/
│ ├── application.yml # 공통 설정 (Jasypt, FCM, Swagger, mail, OAuth2 등)
│ ├── application-local.yml # local 프로필 (MariaDB/Redis localhost)
│ ├── application-dev.yml # dev 프로필
│ ├── application-prod.yml # prod 프로필
│ └── application-secret.yml # Jasypt로 암호화된 시크릿 (spring.profiles.include: secret)
├── src/test/resources/ # 테스트 전용 설정 (application-local.yml: localhost:3306 root/1234, localhost:6379)
├── .github/workflows/deploy.yml # CI(빌드/테스트) + CD(Docker 빌드/푸시 + EC2 배포)
├── Dockerfile # amazoncorretto:25-alpine-jdk 런타임 이미지
├── docker-compose.yml # 로컬 로그 검색 스택(Elasticsearch/Logstash/Kibana) — MySQL/Redis 는 포함하지 않음
├── docker/logstash/ # Logstash 파이프라인(tcp:5044 → data stream logs-hcm-<env>) 및 logstash.yml
├── build.gradle
├── settings.gradle
└── gradlew / gradlew.bat
| 도구 | 버전 | 비고 |
|---|---|---|
| Java (JDK) | 25 | Gradle Toolchain으로 자동 지정 (build.gradle) |
| Docker / Docker Compose | 최신 버전 | 로컬 MySQL/Redis·로그 검색 스택(ELK) 실행 또는 컨테이너 빌드 시 필요 |
| MySQL 또는 MariaDB | 8.x / 호환 버전 | 로컬 프로필은 mariadb-java-client로 localhost:3306 접속 |
| Redis | 7.x | 캐시 용도 |
Gradle은 저장소에 포함된 Wrapper(
./gradlew)를 사용하므로 별도 설치가 필요 없습니다.
Spring 프로필은 spring.profiles.active로 선택하며 기본값은 local입니다(application.yml). 사용 가능한
프로필: local, dev, prod. application-secret.yml은 spring.profiles.include: secret로 자동
포함되며, 안에 담긴 값은 모두 Jasypt로 암호화(ENC(...))되어 있어 실제 비밀값이 커밋되어 있지는 않습니다.
로컬 실행 시 필요한 환경 변수:
export JASYPT_ENCRYPTOR_PASSWORD=[이곳에 정보 입력] # application-secret.yml 복호화 키 (필수)
export FCM_KEY_PATH=[이곳에 정보 입력] # 기본값: /app/config/fcm-key.json (Firebase 서비스 계정 키 경로)
application.yml / application-{profile}.yml의 주요 키:
| 키 | 설명 |
|---|---|
server.port | API 서버 포트 (기본 8080) |
spring.profiles.active | 사용할 프로필 (local / dev / prod) |
spring.profiles.include: secret | application-secret.yml(암호화된 값) 자동 포함 |
spring.datasource.* | DB 접속 정보 — local: jdbc:mariadb://localhost:3306/hcm?..., root/1234; dev/prod는 암호화됨 |
spring.jpa.hibernate.ddl-auto | local/dev/prod 모두 validate — 스키마 변경은 Flyway가 담당하고 Hibernate는 검증만 한다 |
spring.data.redis.* | Redis 접속 정보 — local: localhost:6379; dev/prod는 암호화(호스트/포트/비밀번호) |
spring.mail.* | 이메일 인증 발송용 SMTP 설정 (host/username/password 모두 암호화) |
spring.security.oauth2.client.registration.{google,kakao,naver}.* | 소셜 로그인 클라이언트 ID/Secret (암호화) |
cloud.aws.credentials.*, cloud.aws.s3.bucket | S3 업로드용 AWS 자격 증명 및 버킷 (암호화, 리전은 ap-northeast-2 고정) |
jasypt.encryptor.password | ${JASYPT_ENCRYPTOR_PASSWORD} 환경 변수 참조 |
fcm.key-path | Firebase Admin SDK 서비스 계정 키 파일 경로 (${FCM_KEY_PATH:/app/config/fcm-key.json}) |
app.frontend-url | OAuth2 로그인 성공 후 리다이렉트할 프론트엔드 URL (local: http://localhost:3000, prod: https://www.bellwin.co.kr) |
springdoc.swagger-ui.path | Swagger UI 경로 (/swagger-ui.html) |
local프로필은localhost:3306MySQL(root/1234)과localhost:6379Redis에 바로 접속하도록 구성되어 있어, 별도 시크릿 없이도JASYPT_ENCRYPTOR_PASSWORD만 설정하면 로컬 구동이 가능합니다.dev/prod프로필의 DB·Redis·AWS·메일·OAuth2 값은 모두 암호화되어 있어 저장소만으로는 실제 값을 알 수 없습니다.
MySQL 8.4 + Redis 7 을 localhost:3306(root/1234, DB hcm) / localhost:6379 로 먼저 띄웁니다
(compose 에 포함되어 있지 않으므로 docker run 등으로 직접 실행).
docker run -d --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=1234 -e MYSQL_DATABASE=hcm -e TZ=Asia/Seoul mysql:8.4
docker run -d --name redis -p 6379:6379 redis:7-alpine
로그 검색 스택(Elasticsearch + Logstash + Kibana)은 선택 사항입니다. BE-HCM/docker-compose.yml 로 띄우면
local 프로필의 앱이 localhost:5044 로 JSON 로그를 보내고, Kibana(http://localhost:5601)에서
data view logs-hcm-* 를 만들어 requestId·memberId·level·logger_name 으로 검색할 수 있습니다.
띄우지 않아도 앱은 접속 실패를 무시하고 정상 동작합니다.
docker compose up -d # ES(9200) / Logstash(5044, 9600) / Kibana(5601)
docker compose ps # elasticsearch 가 healthy 가 된 뒤 logstash/kibana 가 올라온다
curl "localhost:9200/logs-hcm-local/_search?q=requestId:<X-Request-Id 응답 헤더 값>&pretty"
docker compose down # 종료 (-v: 적재된 로그 데이터까지 삭제)
export JASYPT_ENCRYPTOR_PASSWORD=[이곳에 정보 입력]
./gradlew build # 전체 빌드 (컴파일 + 테스트 실행)
./gradlew bootRun # 로컬 실행 (기본적으로 local 프로필 사용, http://localhost:8080)
주요 Gradle 명령어:
./gradlew build -x test # 테스트 없이 빌드 (CI에서 사용하는 방식과 동일한 스킵 옵션)
./gradlew test # 전체 테스트 실행
./gradlew test --tests "com.behcm.domain.workout.service.WorkoutRoomServiceTest" # 특정 테스트 클래스만 실행
./gradlew test --tests "com.behcm.domain.workout.service.WorkoutRoomServiceTest.methodName" # 특정 테스트 메서드만 실행
별도로 설정된 lint 태스크는 없으며, ./gradlew build(컴파일 + 테스트)로 코드 정합성을 검증합니다.
정상 기동 후 확인 가능한 주소:
http://localhost:8080/apihttp://localhost:8080/api/healthhttp://localhost:8080/swagger-ui.html이미지 빌드 전 먼저 ./gradlew build로 build/libs/*.jar를 생성해야 합니다.
./gradlew build
docker build -t hcm-backend .
docker run -d \
-p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=local \
-e JASYPT_ENCRYPTOR_PASSWORD=[이곳에 정보 입력] \
--name hcm-backend \
hcm-backend
Dockerfile의 기본SPRING_PROFILES_ACTIVE는prod입니다. 로컬에서 컨테이너로 실행할 경우 위처럼local로 재정의하고, 컨테이너가 호스트의localhost:3306/localhost:6379에 접속할 수 있도록 네트워크 설정(--network host등)이 필요합니다.
.github/workflows/deploy.yml — main, dev 브랜치에 대한 push/PR 시 다음 파이프라인이 실행됩니다.
~/.gradle/caches, ~/.gradle/wrapper)mysql:8.4, redis:7-alpine 서비스 컨테이너를 띄운 상태에서 ./gradlew clean build --no-daemon 실행
(@SpringBootTest가 src/test/resources/application-local.yml의 로컬 인프라 구성을 기대하므로, CI에서도
동일하게 서비스 컨테이너로 구성).build/reports/tests/test)를 아티팩트로 업로드.main 브랜치는 latest 태그, 그 외 브랜치는 dev 태그(+ 커밋 SHA 태그)로 이미지를
빌드/푸시(PR 이벤트에서는 push 생략, GHA 캐시 사용).ci Job 성공 + PR 이벤트가 아닐 때만 실행)
main이면 production, 그 외에는 development GitHub Environment의 시크릿을 사용.appleboy/ssh-action으로 EC2에 SSH 접속 → 기존 hcm 컨테이너 중지/정리(exited 컨테이너 prune 포함) →
신규 이미지 pull → sudo docker-compose -f $DOCKER_COMPOSE_PATH up -d 실행.필요한 GitHub Secrets(값은 저장소에서 알 수 없음): DOCKER_REPO, DOCKER_USERNAME, DOCKER_PASSWORD,
EC2_HOST, EC2_USERNAME, EC2_SSH_KEY, DOCKER_COMPOSE_PATH — production / development 두
GitHub Environment에 각각 별도로 설정됩니다(Secret 이름은 동일하지만 값은 환경별로 다름).
feature/issue-{issue_number} 또는 fix/issue-{issue_number}(#{issue_number})로 끝맺음dev를 거치지 않고 항상 main으로 바로 병합title, body는 한국어로 작성 (자세한 절차는 .cursor/commands/github-issue-pr-command.md 참고)