README.md 수정

This commit is contained in:
2026-03-11 08:30:47 +09:00
parent f45041d46c
commit 197d451d5f

576
README.md
View File

@@ -1,262 +1,334 @@
# 🏠 Home NAS Web Platform (webpage) # web-backend
Synology NAS 기반 개인 웹 플랫폼으로, 로또 분석/추천 서비스와 여행 사진 지도 서비스를 포함합니다. Synology NAS 기반 개인 웹 플랫폼 백엔드 모노레포.
Frontend, Backend, Travel Proxy, Auto Deployer를 Docker Compose로 통합하여 운영합니다. 로또 분석, 주식 포트폴리오, 여행 앨범, 블로그, 투두리스트를 하나의 서비스로 운영한다.
--- ---
## 🖥️ NAS 환경 정보 ## 서비스 구성
```
┌─────────────────────────────────────────────────────────────┐
│ lotto-frontend (Nginx:8080) │
│ ├── 정적 SPA 서빙 (React + Vite) │
│ └── API 리버스 프록시 │
│ ├── /api/ → lotto-backend:8000 │
│ ├── /api/stock/ → stock-lab:8000 │
│ ├── /api/trade/ → stock-lab:8000 │
│ ├── /api/portfolio → stock-lab:8000 │
│ ├── /api/travel/ → travel-proxy:8000 │
│ └── /webhook → deployer:9000 │
└─────────────────────────────────────────────────────────────┘
```
| 컨테이너 | 포트 | 역할 |
|---------|------|------|
| `lotto-backend` | 18000 | 로또·블로그·투두 API |
| `stock-lab` | 18500 | 주식 뉴스·포트폴리오·자산 추적 |
| `travel-proxy` | 19000 | 여행 사진 API + 썸네일 생성 |
| `lotto-frontend` | 8080 | SPA 서빙 + 리버스 프록시 |
| `webpage-deployer` | 19010 | Gitea Webhook → 자동 배포 |
---
## 디렉토리 구조
```
web-backend/
├── backend/ # lotto-backend 서비스 (Python/FastAPI)
│ ├── app/
│ │ ├── main.py # 라우터, 스케줄러
│ │ ├── db.py # SQLite CRUD (7개 테이블)
│ │ ├── generator.py # 몬테카를로 시뮬레이션 엔진
│ │ ├── analyzer.py # 5가지 통계 분석
│ │ ├── checker.py # 당첨 결과 채점
│ │ ├── collector.py # 로또 데이터 수집
│ │ ├── recommender.py # 추천 알고리즘
│ │ └── utils.py # 메트릭 계산
│ └── Dockerfile
├── stock-lab/ # stock-lab 서비스 (Python/FastAPI)
│ ├── app/
│ │ ├── main.py # 라우터, 스케줄러
│ │ ├── db.py # SQLite CRUD (4개 테이블)
│ │ ├── scraper.py # 네이버 금융 뉴스 크롤링
│ │ ├── price_fetcher.py # 현재가 조회 (3분 캐시)
│ │ └── holidays.json # 한국 주식시장 휴장일
│ └── Dockerfile
├── travel-proxy/ # travel-proxy 서비스 (Python/FastAPI)
│ ├── app/
│ │ └── main.py # 사진 API, 썸네일 생성 (Pillow)
│ └── Dockerfile
├── deployer/ # Gitea Webhook 수신 → 자동 배포
│ ├── app.py # HMAC SHA256 검증 + 배포 트리거
│ └── Dockerfile
├── nginx/
│ └── default.conf # 리버스 프록시 + SPA + 캐시
├── scripts/
│ ├── deploy.sh # 운영 배포 (git pull → rsync → compose up)
│ ├── deploy-nas.sh # rsync 전용 스크립트
│ └── healthcheck.sh # 전체 서비스 헬스 체크
├── docker-compose.yml
├── .env.example
└── CLAUDE.md
```
---
## 빠른 시작 (로컬 개발)
```bash
# 1. 환경변수 설정
cp .env.example .env
# 2. 컨테이너 실행 (.env 기본값으로 즉시 실행 가능)
docker compose up -d
# 3. 확인
curl http://localhost:18000/health
curl http://localhost:18500/health
```
| 서비스 | 로컬 URL |
|--------|----------|
| Frontend + API | http://localhost:8080 |
| lotto-backend | http://localhost:18000 |
| stock-lab | http://localhost:18500 |
| travel-proxy | http://localhost:19000 |
---
## API 목록
### lotto-backend (`/api/`)
#### 로또
| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/lotto/latest` | 최신 당첨번호 |
| GET | `/api/lotto/{drw_no}` | 특정 회차 |
| GET | `/api/lotto/stats` | 번호 빈도 통계 |
| GET | `/api/lotto/analysis` | 5가지 통계 분석 리포트 |
| GET | `/api/lotto/best` | 시뮬레이션 최적 번호 (기본 20쌍) |
| GET | `/api/lotto/simulation` | 시뮬레이션 상세 결과 |
| GET | `/api/lotto/recommend` | 통계 기반 추천 |
| GET | `/api/lotto/recommend/heatmap` | 히트맵 기반 추천 |
| GET | `/api/lotto/recommend/batch` | 배치 추천 |
| POST | `/api/admin/simulate` | 시뮬레이션 수동 실행 |
| POST | `/api/admin/sync_latest` | 당첨번호 수동 동기화 |
#### 추천 이력
| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/history` | 목록 (limit, offset, favorite, tag, sort) |
| PATCH | `/api/history/{id}` | 즐겨찾기·메모·태그 수정 |
| DELETE | `/api/history/{id}` | 삭제 |
#### 투두리스트
| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/todos` | 전체 목록 |
| POST | `/api/todos` | 생성 (status: todo\|in_progress\|done) |
| PUT | `/api/todos/{id}` | 수정 |
| DELETE | `/api/todos/done` | 완료 항목 일괄 삭제 |
| DELETE | `/api/todos/{id}` | 개별 삭제 |
> ⚠️ `/done` 라우트는 반드시 `/{id}` 보다 먼저 등록해야 함
#### 블로그
| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/blog/posts` | 글 목록 (`{"posts": [...]}`, date DESC) |
| POST | `/api/blog/posts` | 글 생성 (date 미입력 시 오늘 날짜) |
| PUT | `/api/blog/posts/{id}` | 글 수정 |
| DELETE | `/api/blog/posts/{id}` | 글 삭제 |
블로그 포스트 구조: `{ id, title, tags[], body, date, excerpt, created_at, updated_at }`
---
### stock-lab (`/api/stock/`, `/api/trade/`, `/api/portfolio`)
#### 뉴스 & 지표
| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/stock/news` | 뉴스 목록 (limit, category) |
| GET | `/api/stock/indices` | 주요 지표 (KOSPI 등) |
| POST | `/api/stock/scrap` | 뉴스 수동 스크랩 |
#### 실계좌 (Windows AI 서버 프록시)
| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/trade/balance` | 실계좌 잔고 조회 |
| POST | `/api/trade/order` | 주문 (BUY\|SELL, price=0이면 시장가) |
#### 포트폴리오
| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/portfolio` | 전체 조회 (현재가·손익·예수금 포함) |
| POST | `/api/portfolio` | 종목 추가 |
| PUT | `/api/portfolio/{id}` | 종목 수정 |
| DELETE | `/api/portfolio/{id}` | 종목 삭제 |
| GET | `/api/portfolio/cash` | 예수금 전체 조회 |
| PUT | `/api/portfolio/cash` | 예수금 upsert |
| DELETE | `/api/portfolio/cash/{broker}` | 예수금 삭제 |
| POST | `/api/portfolio/snapshot` | 총 자산 스냅샷 수동 저장 |
| GET | `/api/portfolio/snapshot/history` | 자산 변화 이력 (days=0: 전체) |
---
### travel-proxy (`/api/travel/`)
| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/travel/regions` | 지역 GeoJSON |
| GET | `/api/travel/photos` | 사진 목록 (region, page, size) |
| POST | `/api/travel/reload` | 캐시 초기화 |
- 썸네일: `/media/travel/.thumb/{album}/{file}` (nginx 직접 서빙, 30일 캐시)
- 원본: `/media/travel/{album}/{file}` (nginx 직접 서빙, 7일 캐시)
---
## 핵심 로직
### 몬테카를로 시뮬레이션 (lotto-backend)
```
역대 당첨번호 분석 → 번호별 가중치 산출
→ 가중 확률 샘플링으로 후보 20,000개 생성
→ 5가지 기법으로 각 조합 점수화
→ 상위 100개 DB 저장 → best_picks 20개 교체
```
**5가지 채점 기법:**
| 기법 | 가중치 | 내용 |
|------|--------|------|
| 빈도 Z-score | 25% | 번호 출현 빈도의 표준편차 |
| 조합 지문 | 30% | 합계 정규분포 + 홀짝 비율 + 구간분포 |
| 갭 분석 | 20% | 마지막 출현 이후 경과 회차 |
| 공동 출현 | 15% | 번호 쌍 동시 출현 빈도 |
| 다양성 | 10% | 연속번호·범위·구간 커버리지 |
**스케줄:** 매일 0, 4, 8, 12, 16, 20시 (하루 6회, 각 5분)
### 총 자산 스냅샷 (stock-lab)
```
평일 15:40 자동 실행 → holidays.json으로 공휴일 스킵
→ 포트폴리오 현재가 조회 → total_eval
→ 예수금 합계 → total_cash
→ asset_snapshots upsert (date UNIQUE, 같은 날 중복 시 덮어씀)
```
### 현재가 조회 (stock-lab)
- 네이버 모바일 API 우선 (`m.stock.naver.com/api/stock/{ticker}/basic`)
- 실패 시 네이버 금융 HTML 파싱 폴백
- 3분 TTL 메모리 캐시
### 여행 사진 썸네일 (travel-proxy)
- 480×480 리사이징 (Pillow), 확장자 유지 (JPEG/PNG/WEBP)
- 온디맨드 생성 후 `/data/thumbs/` 영구 캐시
- 원자성 보장: tmp 파일 작성 후 rename
---
## 자동 배포
```
git push → Gitea → X-Gitea-Signature (HMAC SHA256)
→ deployer:9000/webhook (서명 검증, compare_digest 사용)
→ BackgroundTask: scripts/deploy.sh (10분 타임아웃)
1. git pull
2. .releases/{timestamp}/ 백업
3. rsync (repo → runtime)
4. docker compose up -d --build
5. chown PUID:PGID
```
> 프론트엔드는 **자동 배포 안 됨** — 로컬 빌드 후 NAS에 수동 업로드
---
## 데이터베이스
### lotto.db (`/app/data/lotto.db`)
| 테이블 | 설명 |
|--------|------|
| `draws` | 로또 당첨번호 |
| `recommendations` | 추천 이력 (즐겨찾기·태그·채점 포함) |
| `simulation_runs` | 시뮬레이션 실행 기록 |
| `simulation_candidates` | 시뮬레이션 후보 (점수 5종) |
| `best_picks` | 현재 활성 최적 번호 20개 (is_active 플래그) |
| `todos` | 투두리스트 (UUID PK) |
| `blog_posts` | 블로그 글 (tags: JSON 배열) |
### stock.db (`/app/data/stock.db`)
| 테이블 | 설명 |
|--------|------|
| `articles` | 뉴스 기사 (hash UNIQUE, category: domestic\|overseas) |
| `portfolio` | 보유 종목 (broker, ticker, quantity, avg_price) |
| `broker_cash` | 증권사별 예수금 (broker UNIQUE) |
| `asset_snapshots` | 일별 총 자산 스냅샷 (date UNIQUE) |
---
## 환경변수
```env
# 경로 설정
RUNTIME_PATH=.
REPO_PATH=.
FRONTEND_PATH=./frontend/dist
PHOTO_PATH=./mock_data/photos
# NAS 파일 권한
PUID=1000
PGID=1000
# 외부 서비스
WINDOWS_AI_SERVER_URL=http://192.168.45.59:8000
WEBHOOK_SECRET=your_secret_here
```
---
## 인프라
| 항목 | 값 | | 항목 | 값 |
| --------------- | ------------------------------------- | |------|----|
| **NAS** | Synology NAS | | 장비 | Synology NAS (Intel Celeron J4025, 18GB RAM) |
| **OS** | Synology DSM | | Docker | Synology Container Manager |
| **CPU** | Intel Celeron J4025 (2 Core, 2.0GHz) | | Git 서버 | Gitea (NAS 내부 self-hosted) |
| **메모리** | 18 GB | | AI 서버 | Windows PC (192.168.45.59:8000) — RTX 3070 Ti + Ollama |
| **Docker** | Synology Container Manager | | Python | 3.12 (`slim` / `alpine` 기반 이미지) |
| **Reverse Proxy** | Nginx (컨테이너) | | DB | SQLite (볼륨 마운트로 영속 저장) |
| **Git Server** | Gitea (self-hosted) |
--- ---
## 📁 디렉토리 구조 ## 주의사항
``` - **`.env` 파일** — 절대 커밋 금지. `.env.example`만 레포에 포함
/volume1 - **Nginx trailing slash** — `/api/portfolio`는 두 location 블록으로 처리 (trailing slash 유무 모두 매칭)
├── docker/ - **라우트 순서** — `/api/todos/done``/api/todos/{id}` 보다 먼저 등록 필수
│ └── webpage/ # 🚀 운영 런타임 (Docker Compose 기준점) - **캐시 전략** — `index.html`: no-store / `assets/`: 1년 immutable
│ ├── backend/ # lotto-backend - **PUID/PGID** — travel-proxy는 NAS 파일 권한을 위해 환경변수 주입 필수
│ ├── stock-lab/ # 🟪 stock-lab (Stock + AI) - **공휴일 목록** — `stock-lab/app/holidays.json` 매년 수동 갱신 필요 (KRX 기준)
│ ├── travel-proxy/ # travel API + thumbnail generator - **Windows AI 서버** — IP 192.168.45.59 (공유기 DHCP 고정 예약)
│ ├── deployer/ # webhook 기반 자동 배포 컨테이너
│ ├── frontend/ # 정적 파일 (Vite build 결과)
│ ├── nginx/
│ │ └── default.conf
│ ├── scripts/
│ │ └── deploy.sh # webhook이 호출하는 실행기
│ ├── docker-compose.yml
│ └── data/
│ └── lotto.db
├── workspace/
│ └── web-page-backend/ # 🧠 Git 레포 (backend + infra)
│ ├── backend/
│ ├── travel-proxy/
│ ├── deployer/
│ ├── nginx/
│ ├── scripts/
│ │ └── deploy-nas.sh # 실제 운영 반영 로직
│ ├── docker-compose.yml
│ ├── .env.example
│ └── README.md
└── web/
└── images/
└── webPage/
└── travel/ # 📷 원본 여행 사진 (RO)
```
---
## 🧩 서비스 구성 개요
```mermaid
graph LR
User[User Browser] -->|HTTP| Nginx
subgraph NAS [Synology NAS]
Nginx -->|/api/lotto| Lotto[Lotto Backend]
Nginx -->|/api/travel| Travel[Travel Proxy]
Nginx -->|/api/stock| Stock[Stock Lab]
Stock -->|Trading/Balance| KIS[KIS API (Korea Inv.)]
Stock -->|Market News| News[News Sites]
end
subgraph Windows [High-Performance PC]
Stock -->|Analyze Request| WinServer[Windows AI Server]
WinServer -->|LLM Inference| Ollama[Ollama (Llama 3.1)]
WinServer -->|GPU| GPU[NVIDIA 3070 Ti]
end
```
---
## 🛠️ 개발 환경 설정 (Local Development)
이 프로젝트는 **Windows/Mac 로컬 환경**과 **Synology NAS 운영 환경**을 모두 지원하도록 구성되었습니다.
### 1. 환경 변수 설정
`docker-compose.yml`은 환경 변수에 의존합니다.
1. `.env.example` 파일을 복사하여 `.env` 파일을 생성하세요.
```bash
cp .env.example .env
```
2. `.env` 파일의 경로(`RUNTIME_PATH`, `PHOTO_PATH` 등)를 로컬 환경에 맞게 수정하세요.
- 기본값은 현재 디렉토리(`.`) 기준으로 설정되어 있어 바로 실행 가능합니다.
### 2. 로컬 실행
```bash
docker compose up -d
```
- Frontend: http://localhost:8080
- Backend API: http://localhost:18000
- Travel API: http://localhost:19000
- Stock Lab API: http://localhost:18500
---
### 🟦 Frontend (lotto-frontend)
- **역할**: React + Vite 기반 SPA로, 로또 추천 및 여행 지도 UI를 제공합니다.
- **특징**:
- 정적 파일만 운영 서버에 배포합니다.
- 장기 캐시(`assets/`)와 `index.html` 캐시 무효화 전략을 사용합니다.
- Backend / Travel API는 Nginx에서 Reverse Proxy로 연결됩니다.
- **배포 방식**:
1. **로컬 개발**:
- `.env` 파일 설정 후 `docker compose up`으로 전체 스택 실행 가능
2. **운영 배포**:
- Code를 Git에 Push
- Webhook이 트리거되어 NAS가 자동 Pull & Deploy
- (Frontend 빌드 산출물은 별도 업로드 혹은 CI 연동 필요)
---
### 🟩 Backend (lotto-backend)
- **역할**: 로또 데이터 수집, 분석, 추천 API를 제공하며 SQLite로 데이터를 관리합니다.
- **주요 기능**:
- 최신 및 특정 회차 조회
- 추천 번호 생성 및 히스토리 관리 (중복 제거)
- 즐겨찾기, 메모, 태그 관리
- 배치 추천 기능
- **기술 스택**: FastAPI, SQLite, APScheduler (정기 수집)
- **주요 엔드포인트**:
```http
GET /api/lotto/latest
GET /api/lotto/{drw_no}
GET /api/lotto/recommend
GET /api/lotto/recommend/batch
GET /api/history
PATCH /api/history/{id}
DELETE /api/history/{id}
```
---
### 🟪 Stock Lab (stock-lab)
- **역할**: 주식 시장 분석 및 AI 기반 투자 조언 제공. NAS의 편의성과 Windows PC의 고성능을 결합한 하이브리드 아키텍처입니다.
- **주요 기능**:
- **시장 뉴스 스크랩**: 네이버 증권, 해외 주요 뉴스 사이트 크롤링
- **자산 관리**: 한국투자증권(KIS) Open API 연동 (잔고 조회, 매수/매도)
- **AI 분석**: 고성능 PC의 로컬 LLM(Llama 3.1)을 활용하여 뉴스+포트폴리오 종합 분석
- **기술 스택**: Python, FastAPI, Ollama (Lava/Llama3), Docker
- **연동 구조**:
```
[NAS Stock-Lab] <--(HTTP)--> [Windows AI Server] <--(Localhost)--> [Ollama GPU]
```
- **주요 엔드포인트**:
```http
GET /api/stock/analyze # AI 시장 분석 요청
GET /api/stock/news # 최신 뉴스 데이터
GET /api/trade/balance # 계좌 잔고 조회
```
---
### 🟨 Travel Proxy (travel-proxy)
- **역할**: 여행 사진 API, 지역별 사진 매칭, 썸네일 자동 생성 및 캐시를 담당합니다.
- **설계 포인트**:
- 원본 사진은 읽기 전용(RO)으로 마운트합니다.
- 썸네일은 쓰기/읽기(RW) 전용 캐시 디렉토리를 사용합니다.
- 사진 메타데이터 변경 시 캐시가 자동으로 무효화됩니다.
- **데이터 구조**:
```
/data/travel/ # 원본 사진 (RO)
├── 24.09.jeju/
├── 25.07.maldives/
└── _meta/
├── region_map.json
└── regions.geojson
/data/thumbs/ # 썸네일 캐시 (RW)
├── 24.09.jeju/
└── 25.07.maldives/
```
- **주요 엔드포인트**:
```http
GET /api/travel/regions
GET /api/travel/photos?region=jeju
GET /media/travel/.thumb/{album}/{file}
```
---
### 🟥 Deployer (webpage-deployer)
- **역할**: Gitea Webhook을 수신하여 Git pull 및 Docker 재기동을 자동화합니다.
- **흐름**: `Gitea Push` → `Webhook` → `deployer` → `/scripts/deploy.sh` → `docker compose up -d --build`
- **보안**: HMAC SHA256 서명(`X-Gitea-Signature`)을 `WEBHOOK_SECRET` 환경변수로 검증합니다.
- **특징**:
- Docker socket을 마운트하여 사용합니다.
- 롤백을 위해 `.releases/` 디렉토리에 자동 백업을 수행합니다.
---
## 🔁 배포 플로우 요약
- **Backend / Travel / Infra 변경**: `git push`를 통해 Gitea로 푸시하면 Webhook이 트리거되어 자동으로 배포됩니다.
- **Frontend 변경**: 로컬에서 빌드 후, 생성된 정적 파일만 NAS로 업로드합니다.
---
## 🧪 운영 체크 포인트
- `/health`: Backend 서비스 상태 확인
- `/api/travel/photos`: 응답 속도 확인
- `/media/travel/.thumb`: 썸네일 생성 여부 확인
- `deployer` 컨테이너 로그 확인
---
## 📝 TODO
### 🔥 로또 서비스 고도화
- [ ] 추천 결과 통계 시각화 (분포, 합계, 홀짝)
- [ ] 추천 히스토리 필터 및 검색 기능
- [ ] 추천 결과 즐겨찾기 UI
- [ ] 회차 대비 추천 성능 분석
### 🗺️ 여행 지도 UI
- [ ] 지도 영역 클릭 시 해당 지역 사진 로딩
- [ ] 사진 지연 로딩 (Lazy Load)
- [ ] 앨범 및 연도별 필터 기능
- [ ] 모바일 UX 개선
### ⚙️ 운영/인프라
- [ ] Docker 이미지 버전 태깅 자동화
- [ ] 배포 실패 시 자동 롤백 기능
- [ ] Health check 기반 배포 성공 판단 로직
- [ ] 로그 수집 및 관리 체계 개선
---
## ✨ 철학
> “NAS는 서버가 아니라 집이다.”
>
> 그래서 안전하고, 단순하며, 복구 가능한 구조를 최우선으로 합니다.
---
## Makefile 설정 사용 예시
- **배포**: `make deploy`
- **백엔드 로그**: `make logs S=backend`
- **전체 로그**: `make logs`
- **상태**: `make status`