From 98d633ead803e9d8d6a51d21732f1ccc770d80c2 Mon Sep 17 00:00:00 2001 From: KWON Date: Fri, 26 Dec 2025 17:42:20 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20Dockerfile=20chmod=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=ED=99=98=EA=B2=BD=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EC=A7=80=EC=9B=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Dockerfile: chmod 명령어에 RUN 추가 - .env.example: 모든 설정 항목 및 자세한 주석 추가 - config.yaml: 각 설정 항목에 대한 상세 주석 추가 - config.sample.yaml: 샘플 파일 주석 개선 - conf/db.py: 환경변수 우선 적용 기능 추가 - lib/common.py: load_config에 환경변수 오버라이드 지원 - 환경변수로 모든 설정값 제어 가능 (DB, API, POS 등) --- .env.example | 79 +++++++++++++++++++++------ build/Dockerfile | 3 +- conf/config.sample.yaml | 118 ++++++++++++++++++++++++++++------------ conf/db.py | 12 +++- lib/common.py | 56 +++++++++++++++++++ 5 files changed, 214 insertions(+), 54 deletions(-) diff --git a/.env.example b/.env.example index b9f3d99..63c79a6 100644 --- a/.env.example +++ b/.env.example @@ -1,21 +1,66 @@ -# Database Configuration -DB_HOST=mariadb -DB_PORT=3306 -DB_NAME=firstgarden -DB_USER=firstgarden -DB_PASSWORD=Fg9576861! -DB_ROOT_PASSWORD=rootpassword +# ===== Database Configuration ===== +# MariaDB 데이터베이스 연결 정보 +DB_HOST=mariadb # 데이터베이스 호스트명 (Docker 서비스명 또는 localhost) +DB_PORT=3306 # MariaDB 포트 (기본값: 3306) +DB_NAME=firstgarden # 데이터베이스 이름 +DB_USER=firstgarden # 데이터베이스 사용자명 +DB_PASSWORD=Fg9576861! # 데이터베이스 비밀번호 +DB_ROOT_PASSWORD=rootpassword # MariaDB root 비밀번호 (Docker 컨테이너용) -# Logging -LOG_LEVEL=INFO +# ===== Database Table Configuration ===== +TABLE_PREFIX=fg_manager_static_ # 테이블명 접두사 -# Timezone -TZ=Asia/Seoul +# ===== Data.go.kr API Configuration ===== +# 공공데이터포털 API 키 (대기질, 날씨 데이터 수집용) +DATA_API_SERVICE_KEY=mHrZoSnzVc+2S4dpCe3A1CgI9cAu1BRttqRdoEy9RGbnKAKyQT4sqcESDqqY3grgBGQMuLeEgWIS3Qxi8rcDVA== +DATA_API_START_DATE=20170101 # 데이터 수집 시작 날짜 (YYYYMMDD) +DATA_API_END_DATE=20250701 # 데이터 수집 종료 날짜 (YYYYMMDD) -# Python Configuration -PYTHONUNBUFFERED=1 -PYTHONDONTWRITEBYTECODE=1 +# 대기질 측정소 (쉼표로 구분) +AIR_STATION_NAMES=운정 -# API Keys (keep secure, use actual values in production) -# DATA_API_SERVICE_KEY=your_service_key_here -# GA4_API_TOKEN=your_ga4_token_here +# 날씨 관측소 ID (쉼표로 구분) +WEATHER_STN_IDS=99 + +# ===== Google Analytics 4 Configuration ===== +# GA4 API 설정 (방문자 데이터 수집용) +GA4_API_TOKEN=AIzaSyCceJkv02KvwRKzU0IdBRlQ2zHh2yzkLkA +GA4_PROPERTY_ID=384052726 # GA4 속성 ID +GA4_SERVICE_ACCOUNT_FILE=./conf/service-account-credentials.json +GA4_START_DATE=20170101 # GA4 데이터 수집 시작 날짜 +GA4_END_DATE=20990731 # GA4 데이터 수집 종료 날짜 +GA4_MAX_ROWS_PER_REQUEST=10000 # 한 번에 가져올 최대 행 수 + +# ===== POS Configuration ===== +# UPSolution POS 시스템 연동 정보 +UPSOLUTION_ID=firstgarden # UPSolution 계정 ID +UPSOLUTION_CODE=1112 # UPSolution 점포 코드 +UPSOLUTION_PW=9999 # UPSolution 계정 비밀번호 + +# 방문객 카테고리 (쉼표로 구분) +VISITOR_CATEGORIES=입장료,티켓,기업제휴 + +# ===== Forecast Weight Configuration ===== +# 방문객 예측 모델 가중치 설정 +FORECAST_VISITOR_MULTIPLIER=0.5 # 최종 예측 방문객 가중치 +FORECAST_WEIGHT_MIN_TEMP=1.0 # 최저기온 가중치 +FORECAST_WEIGHT_MAX_TEMP=1.0 # 최고기온 가중치 +FORECAST_WEIGHT_PRECIPITATION=10.0 # 강수량 가중치 +FORECAST_WEIGHT_HUMIDITY=1.0 # 습도 가중치 +FORECAST_WEIGHT_PM25=1.0 # 미세먼지(PM2.5) 가중치 +FORECAST_WEIGHT_HOLIDAY=20 # 휴일 여부 가중치 + +# ===== Application Configuration ===== +MAX_WORKERS=4 # 병렬 처리 worker 수 +DEBUG=false # 디버그 모드 (true/false) +FORCE_UPDATE=false # 중복 데이터 덮어쓰기 여부 (true/false) + +# ===== Logging Configuration ===== +LOG_LEVEL=INFO # 로그 레벨 (DEBUG, INFO, WARNING, ERROR, CRITICAL) + +# ===== Timezone Configuration ===== +TZ=Asia/Seoul # 시스템 타임존 + +# ===== Python Configuration ===== +PYTHONUNBUFFERED=1 # Python 출력 버퍼링 비활성화 +PYTHONDONTWRITEBYTECODE=1 # .pyc 파일 생성 비활성화 diff --git a/build/Dockerfile b/build/Dockerfile index d578db5..0a64171 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -69,7 +69,8 @@ except Exception as e: sys.exit(1) " && exit 0 || exit 1 EOF -chmod +x /app/healthcheck.sh + +RUN chmod +x /app/healthcheck.sh # 컨테이너 시작 스크립트 생성 # Flask 웹 서버(포트 8889) + 크론 + 파일 감시 서비스 병렬 실행 diff --git a/conf/config.sample.yaml b/conf/config.sample.yaml index 0879dbb..5b4a71c 100644 --- a/conf/config.sample.yaml +++ b/conf/config.sample.yaml @@ -1,42 +1,90 @@ -# 데이터베이스 접속 정보 +# =================================================================== +# First Garden 정적 데이터 관리 시스템 설정 파일 (샘플) +# =================================================================== +# 이 파일을 config.yaml로 복사한 후 실제 값으로 수정하세요. +# 민감한 정보(비밀번호, API 키)는 .env 파일 사용을 권장합니다. +# =================================================================== + +# ===== 데이터베이스 접속 정보 ===== +# MariaDB/MySQL 데이터베이스 연결 설정 +# 환경변수로 덮어쓰기 가능: DB_HOST, DB_USER, DB_PASSWORD, DB_NAME database: - host: # DB 호스트명 (docker-compose에서 사용하는 서비스명 mariadb) - user: # DB 사용자명 - password: # DB 비밀번호 - name: # 사용할 데이터베이스 이름 + host: localhost # DB 호스트명 (Docker: mariadb, 로컬: localhost) + user: your_db_user # DB 사용자명 + password: your_db_password # DB 비밀번호 + name: your_db_name # 사용할 데이터베이스 이름 -# table 이름 정의 -table_prefix: DB 접두어 +# ===== 테이블 설정 ===== +# 모든 테이블명 앞에 붙는 접두사 +table_prefix: fg_manager_static_ +# 사용되는 테이블 목록 (참고용) tables: - air: 대기정보 테이블 - weather: 종관기상관측 테이블 - ga4: GA4 테이블 - pos: POS 데이터 테이블 - pos_deactivate: 입장처리에서 반영하지 않을 데이터를 관리할 목록 테이블 - holiday: holiday + air: 대기정보 테이블 # 미세먼지 등 대기질 데이터 + weather: 종관기상관측 테이블 # 기온, 강수량 등 날씨 데이터 + ga4: GA4 테이블 # Google Analytics 방문자 데이터 + pos: POS 데이터 테이블 # 매출 및 상품 데이터 + pos_deactivate: 비활성 데이터 목록 # 입장 처리에서 제외할 데이터 + holiday: 휴일 정보 테이블 # 공휴일 및 휴무일 정보 -# 대기환경 API 설정 +# ===== 공공데이터포털 API 설정 ===== +# Data.go.kr 에서 발급받은 API 키 +# 환경변수: DATA_API_SERVICE_KEY DATA_API: - serviceKey: "API_KEY" - startDt: "20170101" - endDt: "20250701" - air: - station_name: - - "운정" - weather: - stnIds: - - 99 - -# GA4 설정 -ga4: - token: TOKEN - property_id: PROPERTY_ID - service_account_file: "./service-account-credentials.json" - startDt: "20230101" - endDt: "20250701" - max_rows_per_request: 10000 + serviceKey: "YOUR_API_KEY_HERE" # 공공데이터포털 API 인증키 + startDt: "20170101" # 데이터 수집 시작 날짜 (YYYYMMDD) + endDt: "20250701" # 데이터 수집 종료 날짜 (YYYYMMDD) -max_workers: 4 # 병렬 처리할 worker 수 -debug: true # 디버그 모드 여부 (true/false) -force_update: false # 중복된 날짜의 데이터를 덮어씌우려면 true, 아니면 false + # 대기질 측정소 설정 + air: + station_name: # 측정소명 리스트 + - "운정" # 예: 운정, 일산, 고양 등 + + # 날씨 관측소 설정 + weather: + stnIds: # 기상청 관측소 ID + - 99 # 예: 99 (파주), 108 (서울) 등 + +# ===== Google Analytics 4 설정 ===== +# GA4 API를 통한 방문자 데이터 수집 +# 환경변수: GA4_API_TOKEN, GA4_PROPERTY_ID +ga4: + token: YOUR_GA4_TOKEN # GA4 API 토큰 + property_id: 12345678 # GA4 속성 ID (숫자) + service_account_file: "./conf/service-account-credentials.json" # 서비스 계정 JSON + startDt: "20230101" # 데이터 수집 시작 날짜 + endDt: "20250701" # 데이터 수집 종료 날짜 + max_rows_per_request: 10000 # API 요청당 최대 행 수 + +# ===== POS 시스템 설정 ===== +POS: + # 방문객으로 분류할 매출 카테고리 + # 환경변수: VISITOR_CATEGORIES (쉼표 구분) + VISITOR_CA: + - 입장료 # 일반 입장료 + - 티켓 # 각종 티켓 + - 기업제휴 # 기업 제휴 티켓 + +# ===== 방문객 예측 모델 가중치 ===== +# 날씨 요소가 방문객 수에 미치는 영향도 +FORECAST_WEIGHT: + visitor_forecast_multiplier: 0.5 # 최종 예측값 조정 (0.0 ~ 1.0) + minTa: 1.0 # 최저기온 영향도 + maxTa: 1.0 # 최고기온 영향도 + sumRn: 10.0 # 강수량 영향도 (높을수록 큰 영향) + avgRhm: 1.0 # 습도 영향도 + pm25: 1.0 # 미세먼지 영향도 + is_holiday: 20 # 휴일 가중치 (휴일 시 방문객 증가) + +# ===== 시스템 설정 ===== +max_workers: 4 # 병렬 처리 워커 수 (CPU 코어 수 권장) +debug: false # 디버그 모드 (개발: true, 운영: false) +force_update: false # 기존 데이터 덮어쓰기 여부 + +# ===== UPSolution POS 연동 ===== +# UPSolution API 접속 정보 +# 환경변수: UPSOLUTION_ID, UPSOLUTION_CODE, UPSOLUTION_PW +upsolution: + id: "your_upsolution_id" # UPSolution 계정 ID + code: "your_store_code" # 점포 코드 + pw: "your_password" # 계정 비밀번호 diff --git a/conf/db.py b/conf/db.py index 0259c96..d3fb77e 100644 --- a/conf/db.py +++ b/conf/db.py @@ -12,12 +12,22 @@ BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) CONFIG_PATH = os.path.join(BASE_DIR, 'conf', 'config.yaml') def load_config(path=CONFIG_PATH): - """설정 파일 로드""" + """설정 파일 로드 (환경변수 우선)""" try: + # config.yaml 로드 with open(path, 'r', encoding='utf-8') as f: config = yaml.safe_load(f) if not config: raise ValueError(f"설정 파일이 비어있음: {path}") + + # 환경변수로 데이터베이스 설정 덮어쓰기 + if os.getenv('DB_HOST'): + config.setdefault('database', {}) + config['database']['host'] = os.getenv('DB_HOST', config['database'].get('host')) + config['database']['user'] = os.getenv('DB_USER', config['database'].get('user')) + config['database']['password'] = os.getenv('DB_PASSWORD', config['database'].get('password')) + config['database']['name'] = os.getenv('DB_NAME', config['database'].get('name')) + return config except FileNotFoundError: logger.error(f"설정 파일을 찾을 수 없음: {path}") diff --git a/lib/common.py b/lib/common.py index dfb5723..6230e95 100644 --- a/lib/common.py +++ b/lib/common.py @@ -40,6 +40,7 @@ def get_logger(name: str) -> logging.Logger: def load_config(config_path: str = None) -> dict: """ conf/config.yaml 파일을 UTF-8로 읽어 파이썬 dict로 반환 + 환경변수가 있으면 우선 적용 Args: config_path: 설정 파일 경로 (없으면 기본값 사용) @@ -59,12 +60,67 @@ def load_config(config_path: str = None) -> dict: config = yaml.safe_load(f) if config is None: raise ValueError(f"설정 파일이 비어있음: {config_path}") + + # 환경변수로 설정 덮어쓰기 + _apply_env_overrides(config) + return config except FileNotFoundError: raise FileNotFoundError(f"설정 파일을 찾을 수 없음: {config_path}") except yaml.YAMLError as e: raise yaml.YAMLError(f"YAML 파싱 오류: {e}") +def _apply_env_overrides(config: dict) -> None: + """환경변수로 설정값 덮어쓰기""" + # 데이터베이스 설정 + if os.getenv('DB_HOST'): + config.setdefault('database', {}) + config['database']['host'] = os.getenv('DB_HOST', config['database'].get('host')) + config['database']['user'] = os.getenv('DB_USER', config['database'].get('user')) + config['database']['password'] = os.getenv('DB_PASSWORD', config['database'].get('password')) + config['database']['name'] = os.getenv('DB_NAME', config['database'].get('name')) + + # 테이블 접두사 + if os.getenv('TABLE_PREFIX'): + config['table_prefix'] = os.getenv('TABLE_PREFIX') + + # API 설정 + if os.getenv('DATA_API_SERVICE_KEY'): + config.setdefault('DATA_API', {}) + config['DATA_API']['serviceKey'] = os.getenv('DATA_API_SERVICE_KEY') + if os.getenv('DATA_API_START_DATE'): + config.setdefault('DATA_API', {}) + config['DATA_API']['startDt'] = os.getenv('DATA_API_START_DATE') + if os.getenv('DATA_API_END_DATE'): + config.setdefault('DATA_API', {}) + config['DATA_API']['endDt'] = os.getenv('DATA_API_END_DATE') + + # GA4 설정 + if os.getenv('GA4_API_TOKEN'): + config.setdefault('ga4', {}) + config['ga4']['token'] = os.getenv('GA4_API_TOKEN') + if os.getenv('GA4_PROPERTY_ID'): + config.setdefault('ga4', {}) + config['ga4']['property_id'] = int(os.getenv('GA4_PROPERTY_ID')) + if os.getenv('GA4_SERVICE_ACCOUNT_FILE'): + config.setdefault('ga4', {}) + config['ga4']['service_account_file'] = os.getenv('GA4_SERVICE_ACCOUNT_FILE') + + # UPSolution 설정 + if os.getenv('UPSOLUTION_ID'): + config.setdefault('upsolution', {}) + config['upsolution']['id'] = os.getenv('UPSOLUTION_ID') + config['upsolution']['code'] = os.getenv('UPSOLUTION_CODE') + config['upsolution']['pw'] = os.getenv('UPSOLUTION_PW') + + # 시스템 설정 + if os.getenv('MAX_WORKERS'): + config['max_workers'] = int(os.getenv('MAX_WORKERS')) + if os.getenv('DEBUG'): + config['debug'] = os.getenv('DEBUG', 'false').lower() == 'true' + if os.getenv('FORCE_UPDATE'): + config['force_update'] = os.getenv('FORCE_UPDATE', 'false').lower() == 'true' + def retry_on_exception(max_retries: int = 3, delay: float = 1.0, backoff: float = 2.0): """ 지정된 횟수만큼 재시도하는 데코레이터