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>
280 lines
9.1 KiB
Markdown
280 lines
9.1 KiB
Markdown
# 07_INFRA_SPEC — Docker / Nginx / 문서
|
|
|
|
## 1. docker-compose.yml
|
|
|
|
루트에 위치. 5 서비스: `postgres / redis / api / batch / frontend`.
|
|
|
|
```yaml
|
|
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.
|
|
|
|
```dockerfile
|
|
# 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
|
|
|
|
```dockerfile
|
|
# 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
|
|
```
|
|
|
|
```nginx
|
|
# 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` 오버라이드
|
|
- **시나리오 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.6`
|
|
- `Failed 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.css` import 확인
|
|
|
|
---
|
|
|
|
## 6. 통합 검증 체크리스트
|
|
|
|
```bash
|
|
# 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`)
|