035175e895
00_PROMPTS.md — Claude Code 실행 프롬프트 1~6 01_DB_SCHEMA.md — V1~V17 Flyway 스키마 + 프로젝트 뼈대 02_COMMON_SPEC.md — ga-common 공통 프레임워크 (모델/예외/MyBatis/AOP/엑셀/공통컴포넌트) 03_CORE_SPEC.md — ga-core VO 설계 원칙 + Mapper 패턴 04_BATCH_SPEC.md — ga-batch 정산 8 Step + DataReceiver/MappingEngine 05_API_SPEC.md — ga-api Controller/Service 표준 패턴 + 엔드포인트 목록 06_FRONTEND_SPEC.md — ga-frontend 13개 화면 + 공통 컴포넌트 사용법 07_INFRA_SPEC.md — Docker/Nginx + README + DEVELOPER_GUIDE 08_DBA_SPEC.md — V13~V15 인덱스/파티셔닝/뷰 실제 구현된 ga-commission-system 기준으로 작성. 코드는 변경 없음. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9.1 KiB
9.1 KiB
07_INFRA_SPEC — Docker / Nginx / 문서
1. docker-compose.yml
루트에 위치. 5 서비스: postgres / redis / api / batch / frontend.
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: ga
POSTGRES_USER: ga
POSTGRES_PASSWORD: ga
TZ: Asia/Seoul
ports: ["5432:5432"]
volumes: [ga-postgres-data:/var/lib/postgresql/data]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ga -d ga"]
interval: 10s
redis:
image: redis:7-alpine
ports: ["6379:6379"]
volumes: [ga-redis-data:/data]
api:
build:
context: .
dockerfile: Dockerfile
args: { MODULE: ga-api }
depends_on:
postgres: { condition: service_healthy }
redis: { condition: service_started }
environment:
SPRING_PROFILES_ACTIVE: docker
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/ga
SPRING_DATASOURCE_USERNAME: ga
SPRING_DATASOURCE_PASSWORD: ga
SPRING_DATA_REDIS_HOST: redis
JWT_SECRET: ${JWT_SECRET:-please-change-this-jwt-secret-must-be-256-bits}
ENCRYPT_KEY: ${ENCRYPT_KEY:-please-change-this-32-byte-key!}
ports: ["8080:8080"]
batch:
build: { context: ., dockerfile: Dockerfile, args: { MODULE: ga-batch } }
depends_on:
postgres: { condition: service_healthy }
redis: { condition: service_started }
api: { condition: service_started }
environment: # api 와 동일
...
ports: ["8081:8081"]
frontend:
build: { context: ./ga-frontend, dockerfile: Dockerfile }
depends_on: [api]
ports: ["3000:80"]
volumes: { ga-postgres-data: , ga-redis-data: }
2. Dockerfile (루트, ga-api/ga-batch 공용)
멀티스테이지: Gradle 빌드 → 슬림 JRE.
# stage 1: builder
FROM gradle:8.6-jdk17 AS builder
WORKDIR /workspace
COPY settings.gradle build.gradle gradle.properties ./
COPY ga-common/build.gradle ga-common/
COPY ga-core/build.gradle ga-core/
COPY ga-api/build.gradle ga-api/
COPY ga-batch/build.gradle ga-batch/
COPY ga-admin/build.gradle ga-admin/
RUN gradle dependencies --no-daemon || true
COPY . .
ARG MODULE=ga-api
RUN gradle :${MODULE}:bootJar --no-daemon -x test
RUN cp /workspace/${MODULE}/build/libs/${MODULE}.jar /workspace/app.jar
# stage 2: runtime
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
RUN apk add --no-cache tzdata && cp /usr/share/zoneinfo/Asia/Seoul /etc/localtime
COPY --from=builder /workspace/app.jar /app/app.jar
EXPOSE 8080 8081
ENTRYPOINT ["java","-XX:+UseContainerSupport","-XX:MaxRAMPercentage=75","-jar","/app/app.jar"]
3. ga-frontend/Dockerfile + nginx.conf
# ga-frontend/Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci || npm install
COPY . .
RUN npm run build # → /app/dist
FROM nginx:1.27-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
# ga-frontend/nginx.conf
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# SPA fallback
location / {
try_files $uri $uri/ /index.html;
}
# API 프록시
location /api/ {
proxy_pass http://api:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 100m;
proxy_read_timeout 300s; # 엑셀 대용량 다운로드 대비
}
# 정적 자원 캐시
location ~* \.(?:css|js|woff2?|ttf|svg|png|jpg|jpeg|gif|ico)$ {
expires 7d;
add_header Cache-Control "public, max-age=604800, immutable";
}
}
4. README.md (루트)
작성 항목 (현재 README.md 가 이미 갖춘 형태):
- 기술 스택 요약 표
- 모듈 구조 다이어그램
- 시나리오 A — 운영 DB(
192.168.0.60:55432/trading_ai/ga) 연결- Gradle wrapper 생성 →
bootJar→bootRun --args='--spring.profiles.active=trading_ai' - 환경변수
DB_PASSWORD,REDIS_HOST오버라이드
- Gradle wrapper 생성 →
- 시나리오 B — 로컬 PostgreSQL (개발용)
docker compose up -d postgres redis→bootRun
- 시나리오 C — Docker Compose 전체 (운영 흐름)
docker compose up -d --build
- DB 마이그레이션 직접 실행 (Python
migrate.py) - 데이터 확인 표 (보험사 5 / 상품 20 / 설계사 50 / 계약 200 / ...)
- 트러블슈팅 FAQ
gradle wrapper --gradle-version 8.6Failed to determine a suitable driver class→ 프로파일 확인NoSuchKey: ga→currentSchema=ga- 401 토큰 만료 → 재로그인
- AG Grid 빈 화면 → CSS import 확인
- 주요 기능 (백엔드/도메인/배치 8 Step 요약)
- 보안 (JWT/AES/RBAC/감사로그)
- 진행 상황 체크리스트
- 향후 확장
- 외부 시스템 연결 정보
5. DEVELOPER_GUIDE.md (신입 개발자 온보딩)
1. 환경 설정
- JDK 17 (
java -version확인) - Node 20 + npm
- PostgreSQL 14+ (운영 또는 Docker)
- Redis 7+ (선택)
- IntelliJ IDEA Community + Lombok 플러그인 (자동 설치 권장)
2. 프로젝트 구조 한 장 다이어그램
ga-common ─┬─> ga-core ─┬─> ga-api (REST API, 8080)
│ ├─> ga-batch (Spring Batch, 8081)
│ └─> ga-admin (예약 모듈)
└─> 다른 모듈 모두 의존 (web/security/aop/cache/mybatis/excel...)
ga-frontend (독립 빌드, dist → nginx)
3. 새 화면 만들기 Step-by-Step (예: "공지사항")
Step 1 — DB: notice 테이블 + 메뉴/권한 등록 (V18 마이그레이션)
Step 2 — VO (ga-core/vo/notice/):
NoticeVO.java(테이블 1:1)NoticeResp.java(조인 결과)NoticeSaveReq.java(@Valid검증)NoticeSearchParam.java extends SearchParam
Step 3 — Mapper:
mapper/notice/NoticeMapper.java(@Mapper)resources/mapper/notice/NoticeMapper.xml(기존 AgentMapper.xml 복사 후 수정)
Step 4 — Service (ga-api/service/notice/):
NoticeService.java(단순 CRUD 패턴 +@Transactional)
Step 5 — Controller:
NoticeController.java—@RequirePermission(menu="NOTICE", perm="...")- CUD 메서드에
@DataChangeLog
Step 6 — 프론트 (ga-frontend/src/):
api/notice.ts— 함수 5개 (list/detail/create/update/delete)pages/notice/NoticeList.tsx—06_FRONTEND_SPEC.md §7패턴pages/notice/NoticeForm.tsx— Ant Form
Step 7 — 메뉴 등록:
system/menus화면에서 메뉴/권한 등록 → 사용자 역할에 권한 부여
4. 자주 쓰는 패턴
- 공통코드 드롭다운:
<CodeSelect groupCode="..." /> - 엑셀 다운로드:
<ExcelExportButton url={...} fileName={...} showProgress /> - 상태 뱃지:
<CodeBadge groupCode="..." value={status} /> - 권한 버튼:
<PermissionButton menuCode permCode>...</PermissionButton> - AG Grid 서버사이드:
<DataGrid dataSource={p => api.list(p)} />
5. 코드 컨벤션
- VO 네이밍:
XxxVO / XxxSearchParam / XxxSaveReq / XxxResp / XxxExcelVO - Mapper XML:
XxxMapper.xml(resources/mapper/{도메인}/) - API URL:
/api/{리소스}s(복수형, kebab-case) - 금액: 항상
BigDecimal+MoneyUtil헬퍼 - 날짜:
LocalDate / LocalDateTime(java.util.Date 금지) - 예외:
BizException(ErrorCode.XXX)(RuntimeException 직접 throw 금지) - 로그:
lombok @Slf4j+log.info("msg, key={}", value)(한글 메시지 OK)
6. 트러블슈팅 FAQ
- 권한 에러:
@RequirePermission의 menuCode 가menu_permission에 등록되어 있는지 + 사용자 역할에 권한 부여됐는지 - 공통코드 캐시 안 맞음:
CommonCodeService.refreshCache(groupCode)또는 Redis FLUSH - MyBatis 매핑 에러: ResultMap 의 column / property 명 +
mapUnderscoreToCamelCase확인 - 암호화 컬럼 평문 노출:
EncryptTypeHandler가 컬럼별 명시(typeHandler 속성)되어 있는지 - JSONB null:
JsonTypeHandler가@MappedTypes(JsonNode.class)로 등록됐는지 - PageHelper 안 먹음:
param.startPage()호출 후 SELECT 1회만 + 같은 트랜잭션 - AG Grid 빈 화면:
@ag-grid-community/styles/ag-theme-quartz.cssimport 확인
6. 통합 검증 체크리스트
# 1) 빌드
./gradlew clean build # 모든 모듈 컴파일 + 단위 테스트 통과
cd ga-frontend && npm install && npm run build # → dist/
# 2) Docker 전체 기동
docker compose up -d --build
# 3) Smoke
curl http://localhost:8080/api/health # {"status":"UP"}
curl http://localhost:3000/ # nginx 200
# 브라우저: admin / admin1234! 로그인 → 메뉴 트리 보임 → 설계사 50 행 표시
# 4) 정산 배치
curl -X POST -H "Authorization: Bearer $TOKEN" \
"http://localhost:8081/api/batch/settlement/run?settleMonth=202605"
# 5) 태그
git tag v1.0.0-alpha
기대 결과:
- Flyway V1~V17 자동 적용
- ga-api 8080 / ga-batch 8081 / frontend 3000 모두 OK
- 단위테스트 76+ 건 통과
- 운영 DB 시나리오 A 도 동일 동작 (
--spring.profiles.active=trading_ai)