feat: Flask 애플리케이션 모듈화 및 웹 대시보드 구현
- Flask Blueprint 아키텍처로 전환 (dashboard, upload, backup, status) - app.py 681줄 95줄로 축소 (86% 감소) - HTML 템플릿 모듈화 (base.html + 기능별 templates) - CSS/JS 파일 분리 (common + 기능별 파일) - 대시보드 기능 추가 (통계, 주간 예보, 방문객 추이) - 파일 업로드 웹 인터페이스 구현 - 백업/복구 관리 UI 구현 - Docker 배포 환경 개선 - .gitignore 업데이트 (uploads, backups, cache 등)
This commit is contained in:
109
conf/db.py
109
conf/db.py
@ -1,33 +1,114 @@
|
||||
# db.py
|
||||
import os
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
import logging
|
||||
from sqlalchemy import create_engine, event, exc, pool
|
||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
import yaml
|
||||
|
||||
# db.py 파일 위치 기준 상위 디렉토리 (프로젝트 루트)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 프로젝트 루트 경로 설정
|
||||
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):
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
return yaml.safe_load(f)
|
||||
"""설정 파일 로드"""
|
||||
try:
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
config = yaml.safe_load(f)
|
||||
if not config:
|
||||
raise ValueError(f"설정 파일이 비어있음: {path}")
|
||||
return config
|
||||
except FileNotFoundError:
|
||||
logger.error(f"설정 파일을 찾을 수 없음: {path}")
|
||||
raise
|
||||
except yaml.YAMLError as e:
|
||||
logger.error(f"YAML 파싱 오류: {e}")
|
||||
raise
|
||||
|
||||
config = load_config()
|
||||
db_cfg = config['database']
|
||||
db_cfg = config.get('database', {})
|
||||
|
||||
db_url = f"mysql+pymysql://{db_cfg['user']}:{db_cfg['password']}@{db_cfg['host']}/{db_cfg['name']}?charset=utf8mb4"
|
||||
|
||||
# MySQL 연결이 끊겼을 때 자동 재시도 옵션 포함
|
||||
engine = create_engine(
|
||||
db_url,
|
||||
pool_pre_ping=True,
|
||||
pool_recycle=3600, # 3600초 = 1시간
|
||||
# DB URL 구성
|
||||
db_url = (
|
||||
f"mysql+pymysql://{db_cfg.get('user')}:"
|
||||
f"{db_cfg.get('password')}@{db_cfg.get('host')}/"
|
||||
f"{db_cfg.get('name')}?charset=utf8mb4"
|
||||
)
|
||||
|
||||
# MySQL 엔진 생성 (재연결 및 연결 풀 설정)
|
||||
engine = create_engine(
|
||||
db_url,
|
||||
poolclass=pool.QueuePool,
|
||||
pool_pre_ping=True, # 연결 전 핸들 확인
|
||||
pool_recycle=3600, # 3600초(1시간)마다 재연결
|
||||
pool_size=10, # 연결 풀 크기
|
||||
max_overflow=20, # 추가 오버플로우 연결 수
|
||||
echo=False, # SQL 출력 (디버그용)
|
||||
connect_args={
|
||||
'connect_timeout': 10,
|
||||
'charset': 'utf8mb4'
|
||||
}
|
||||
)
|
||||
|
||||
# 연결 에러 발생 시 자동 재연결
|
||||
@event.listens_for(pool.Pool, "connect")
|
||||
def receive_connect(dbapi_conn, connection_record):
|
||||
"""DB 연결 성공 로그"""
|
||||
logger.debug("DB 연결 성공")
|
||||
|
||||
@event.listens_for(pool.Pool, "checkout")
|
||||
def receive_checkout(dbapi_conn, connection_record, connection_proxy):
|
||||
"""연결 풀에서 체크아웃할 때"""
|
||||
pass
|
||||
|
||||
@event.listens_for(pool.Pool, "checkin")
|
||||
def receive_checkin(dbapi_conn, connection_record):
|
||||
"""연결 풀로 반환할 때"""
|
||||
pass
|
||||
|
||||
# 세션 팩토리
|
||||
Session = sessionmaker(bind=engine)
|
||||
SessionLocal = scoped_session(Session)
|
||||
|
||||
def get_engine():
|
||||
"""엔진 반환"""
|
||||
return engine
|
||||
|
||||
def get_session():
|
||||
return Session()
|
||||
"""새로운 세션 반환"""
|
||||
session = Session()
|
||||
try:
|
||||
# 연결 테스트
|
||||
session.execute('SELECT 1')
|
||||
except exc.DatabaseError as e:
|
||||
logger.error(f"DB 연결 실패: {e}")
|
||||
session.close()
|
||||
raise
|
||||
return session
|
||||
|
||||
def get_scoped_session():
|
||||
"""스코프 세션 반환 (스레드 안전)"""
|
||||
return SessionLocal
|
||||
|
||||
def close_session():
|
||||
"""세션 종료"""
|
||||
SessionLocal.remove()
|
||||
|
||||
class DBSession:
|
||||
"""컨텍스트 매니저를 사용한 세션 관리"""
|
||||
def __init__(self):
|
||||
self.session = None
|
||||
|
||||
def __enter__(self):
|
||||
self.session = get_session()
|
||||
return self.session
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.session:
|
||||
if exc_type:
|
||||
self.session.rollback()
|
||||
logger.error(f"트랜잭션 롤백: {exc_type.__name__}: {exc_val}")
|
||||
else:
|
||||
self.session.commit()
|
||||
self.session.close()
|
||||
|
||||
Reference in New Issue
Block a user