fix: Dockerfile chmod 에러 수정 및 환경변수 지원 추가
- Dockerfile: chmod 명령어에 RUN 추가 - .env.example: 모든 설정 항목 및 자세한 주석 추가 - config.yaml: 각 설정 항목에 대한 상세 주석 추가 - config.sample.yaml: 샘플 파일 주석 개선 - conf/db.py: 환경변수 우선 적용 기능 추가 - lib/common.py: load_config에 환경변수 오버라이드 지원 - 환경변수로 모든 설정값 제어 가능 (DB, API, POS 등)
This commit is contained in:
79
.env.example
79
.env.example
@ -1,21 +1,66 @@
|
|||||||
# Database Configuration
|
# ===== Database Configuration =====
|
||||||
DB_HOST=mariadb
|
# MariaDB 데이터베이스 연결 정보
|
||||||
DB_PORT=3306
|
DB_HOST=mariadb # 데이터베이스 호스트명 (Docker 서비스명 또는 localhost)
|
||||||
DB_NAME=firstgarden
|
DB_PORT=3306 # MariaDB 포트 (기본값: 3306)
|
||||||
DB_USER=firstgarden
|
DB_NAME=firstgarden # 데이터베이스 이름
|
||||||
DB_PASSWORD=Fg9576861!
|
DB_USER=firstgarden # 데이터베이스 사용자명
|
||||||
DB_ROOT_PASSWORD=rootpassword
|
DB_PASSWORD=Fg9576861! # 데이터베이스 비밀번호
|
||||||
|
DB_ROOT_PASSWORD=rootpassword # MariaDB root 비밀번호 (Docker 컨테이너용)
|
||||||
|
|
||||||
# Logging
|
# ===== Database Table Configuration =====
|
||||||
LOG_LEVEL=INFO
|
TABLE_PREFIX=fg_manager_static_ # 테이블명 접두사
|
||||||
|
|
||||||
# Timezone
|
# ===== Data.go.kr API Configuration =====
|
||||||
TZ=Asia/Seoul
|
# 공공데이터포털 API 키 (대기질, 날씨 데이터 수집용)
|
||||||
|
DATA_API_SERVICE_KEY=mHrZoSnzVc+2S4dpCe3A1CgI9cAu1BRttqRdoEy9RGbnKAKyQT4sqcESDqqY3grgBGQMuLeEgWIS3Qxi8rcDVA==
|
||||||
|
DATA_API_START_DATE=20170101 # 데이터 수집 시작 날짜 (YYYYMMDD)
|
||||||
|
DATA_API_END_DATE=20250701 # 데이터 수집 종료 날짜 (YYYYMMDD)
|
||||||
|
|
||||||
# Python Configuration
|
# 대기질 측정소 (쉼표로 구분)
|
||||||
PYTHONUNBUFFERED=1
|
AIR_STATION_NAMES=운정
|
||||||
PYTHONDONTWRITEBYTECODE=1
|
|
||||||
|
|
||||||
# API Keys (keep secure, use actual values in production)
|
# 날씨 관측소 ID (쉼표로 구분)
|
||||||
# DATA_API_SERVICE_KEY=your_service_key_here
|
WEATHER_STN_IDS=99
|
||||||
# GA4_API_TOKEN=your_ga4_token_here
|
|
||||||
|
# ===== 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 파일 생성 비활성화
|
||||||
|
|||||||
@ -69,7 +69,8 @@ except Exception as e:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
" && exit 0 || exit 1
|
" && exit 0 || exit 1
|
||||||
EOF
|
EOF
|
||||||
chmod +x /app/healthcheck.sh
|
|
||||||
|
RUN chmod +x /app/healthcheck.sh
|
||||||
|
|
||||||
# 컨테이너 시작 스크립트 생성
|
# 컨테이너 시작 스크립트 생성
|
||||||
# Flask 웹 서버(포트 8889) + 크론 + 파일 감시 서비스 병렬 실행
|
# Flask 웹 서버(포트 8889) + 크론 + 파일 감시 서비스 병렬 실행
|
||||||
|
|||||||
@ -1,42 +1,90 @@
|
|||||||
# 데이터베이스 접속 정보
|
# ===================================================================
|
||||||
|
# First Garden 정적 데이터 관리 시스템 설정 파일 (샘플)
|
||||||
|
# ===================================================================
|
||||||
|
# 이 파일을 config.yaml로 복사한 후 실제 값으로 수정하세요.
|
||||||
|
# 민감한 정보(비밀번호, API 키)는 .env 파일 사용을 권장합니다.
|
||||||
|
# ===================================================================
|
||||||
|
|
||||||
|
# ===== 데이터베이스 접속 정보 =====
|
||||||
|
# MariaDB/MySQL 데이터베이스 연결 설정
|
||||||
|
# 환경변수로 덮어쓰기 가능: DB_HOST, DB_USER, DB_PASSWORD, DB_NAME
|
||||||
database:
|
database:
|
||||||
host: # DB 호스트명 (docker-compose에서 사용하는 서비스명 mariadb)
|
host: localhost # DB 호스트명 (Docker: mariadb, 로컬: localhost)
|
||||||
user: # DB 사용자명
|
user: your_db_user # DB 사용자명
|
||||||
password: # DB 비밀번호
|
password: your_db_password # DB 비밀번호
|
||||||
name: # 사용할 데이터베이스 이름
|
name: your_db_name # 사용할 데이터베이스 이름
|
||||||
|
|
||||||
# table 이름 정의
|
# ===== 테이블 설정 =====
|
||||||
table_prefix: DB 접두어
|
# 모든 테이블명 앞에 붙는 접두사
|
||||||
|
table_prefix: fg_manager_static_
|
||||||
|
|
||||||
|
# 사용되는 테이블 목록 (참고용)
|
||||||
tables:
|
tables:
|
||||||
air: 대기정보 테이블
|
air: 대기정보 테이블 # 미세먼지 등 대기질 데이터
|
||||||
weather: 종관기상관측 테이블
|
weather: 종관기상관측 테이블 # 기온, 강수량 등 날씨 데이터
|
||||||
ga4: GA4 테이블
|
ga4: GA4 테이블 # Google Analytics 방문자 데이터
|
||||||
pos: POS 데이터 테이블
|
pos: POS 데이터 테이블 # 매출 및 상품 데이터
|
||||||
pos_deactivate: 입장처리에서 반영하지 않을 데이터를 관리할 목록 테이블
|
pos_deactivate: 비활성 데이터 목록 # 입장 처리에서 제외할 데이터
|
||||||
holiday: holiday
|
holiday: 휴일 정보 테이블 # 공휴일 및 휴무일 정보
|
||||||
|
|
||||||
# 대기환경 API 설정
|
# ===== 공공데이터포털 API 설정 =====
|
||||||
|
# Data.go.kr 에서 발급받은 API 키
|
||||||
|
# 환경변수: DATA_API_SERVICE_KEY
|
||||||
DATA_API:
|
DATA_API:
|
||||||
serviceKey: "API_KEY"
|
serviceKey: "YOUR_API_KEY_HERE" # 공공데이터포털 API 인증키
|
||||||
startDt: "20170101"
|
startDt: "20170101" # 데이터 수집 시작 날짜 (YYYYMMDD)
|
||||||
endDt: "20250701"
|
endDt: "20250701" # 데이터 수집 종료 날짜 (YYYYMMDD)
|
||||||
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
|
|
||||||
|
|
||||||
max_workers: 4 # 병렬 처리할 worker 수
|
# 대기질 측정소 설정
|
||||||
debug: true # 디버그 모드 여부 (true/false)
|
air:
|
||||||
force_update: false # 중복된 날짜의 데이터를 덮어씌우려면 true, 아니면 false
|
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" # 계정 비밀번호
|
||||||
|
|||||||
12
conf/db.py
12
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')
|
CONFIG_PATH = os.path.join(BASE_DIR, 'conf', 'config.yaml')
|
||||||
|
|
||||||
def load_config(path=CONFIG_PATH):
|
def load_config(path=CONFIG_PATH):
|
||||||
"""설정 파일 로드"""
|
"""설정 파일 로드 (환경변수 우선)"""
|
||||||
try:
|
try:
|
||||||
|
# config.yaml 로드
|
||||||
with open(path, 'r', encoding='utf-8') as f:
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
config = yaml.safe_load(f)
|
config = yaml.safe_load(f)
|
||||||
if not config:
|
if not config:
|
||||||
raise ValueError(f"설정 파일이 비어있음: {path}")
|
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
|
return config
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logger.error(f"설정 파일을 찾을 수 없음: {path}")
|
logger.error(f"설정 파일을 찾을 수 없음: {path}")
|
||||||
|
|||||||
@ -40,6 +40,7 @@ def get_logger(name: str) -> logging.Logger:
|
|||||||
def load_config(config_path: str = None) -> dict:
|
def load_config(config_path: str = None) -> dict:
|
||||||
"""
|
"""
|
||||||
conf/config.yaml 파일을 UTF-8로 읽어 파이썬 dict로 반환
|
conf/config.yaml 파일을 UTF-8로 읽어 파이썬 dict로 반환
|
||||||
|
환경변수가 있으면 우선 적용
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
config_path: 설정 파일 경로 (없으면 기본값 사용)
|
config_path: 설정 파일 경로 (없으면 기본값 사용)
|
||||||
@ -59,12 +60,67 @@ def load_config(config_path: str = None) -> dict:
|
|||||||
config = yaml.safe_load(f)
|
config = yaml.safe_load(f)
|
||||||
if config is None:
|
if config is None:
|
||||||
raise ValueError(f"설정 파일이 비어있음: {config_path}")
|
raise ValueError(f"설정 파일이 비어있음: {config_path}")
|
||||||
|
|
||||||
|
# 환경변수로 설정 덮어쓰기
|
||||||
|
_apply_env_overrides(config)
|
||||||
|
|
||||||
return config
|
return config
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
raise FileNotFoundError(f"설정 파일을 찾을 수 없음: {config_path}")
|
raise FileNotFoundError(f"설정 파일을 찾을 수 없음: {config_path}")
|
||||||
except yaml.YAMLError as e:
|
except yaml.YAMLError as e:
|
||||||
raise yaml.YAMLError(f"YAML 파싱 오류: {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):
|
def retry_on_exception(max_retries: int = 3, delay: float = 1.0, backoff: float = 2.0):
|
||||||
"""
|
"""
|
||||||
지정된 횟수만큼 재시도하는 데코레이터
|
지정된 횟수만큼 재시도하는 데코레이터
|
||||||
|
|||||||
Reference in New Issue
Block a user