From 4a648ea6bff131f6e4384fd1e871d73fa45598dc Mon Sep 17 00:00:00 2001 From: ogt Date: Mon, 27 Apr 2026 21:28:23 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20fix=20reverse=20dependencies=20?= =?UTF-8?q?=E2=80=94=20logger=5Fmanager=E2=86=92utils,=20dashboard=5Fservi?= =?UTF-8?q?ce=20extraction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move SystemLogger implementation to utils/logger_manager.py (pure utility, no deps) - services/logger_manager.py becomes a backward-compat re-export shim - database/manager.py and database/vendor_manager.py now import from utils layer - Extract get_dashboard_stats() to services/dashboard_service.py - services/task_runner.py no longer imports from routes layer - routes/dashboard_routes.py get_dashboard_stats() delegates to service layer Co-Authored-By: Claude Sonnet 4.6 --- database/manager.py | 2 +- database/vendor_manager.py | 2 +- routes/dashboard_routes.py | 13 ++------ services/dashboard_service.py | 22 ++++++++++++++ services/logger_manager.py | 57 +++-------------------------------- services/task_runner.py | 2 +- utils/logger_manager.py | 53 ++++++++++++++++++++++++++++++++ 7 files changed, 85 insertions(+), 66 deletions(-) create mode 100644 services/dashboard_service.py create mode 100644 utils/logger_manager.py diff --git a/database/manager.py b/database/manager.py index 61f1666..4d17e80 100644 --- a/database/manager.py +++ b/database/manager.py @@ -15,7 +15,7 @@ from .ppt_reports import PPTReport # noqa: F401 - 確保 ppt_reports 表被 Bas from .vendor_models import VendorStockout # noqa: F401 - 確保 vendor_stockout 表被 Base.metadata 管理 # 🚩 導入優化後的日誌管理模組 -from services.logger_manager import SystemLogger +from utils.logger_manager import SystemLogger # 初始化資料庫模組專用 Logger sys_log = SystemLogger("Database").get_logger() diff --git a/database/vendor_manager.py b/database/vendor_manager.py index 05c9b95..2823275 100644 --- a/database/vendor_manager.py +++ b/database/vendor_manager.py @@ -12,7 +12,7 @@ from datetime import datetime from .vendor_models import Base, VendorStockout, VendorList, VendorEmail, EmailSendLog # 導入日誌管理模組 -from services.logger_manager import SystemLogger +from utils.logger_manager import SystemLogger # 初始化日誌 sys_log = SystemLogger("VendorDatabase").get_logger() diff --git a/routes/dashboard_routes.py b/routes/dashboard_routes.py index bb670cf..5381dda 100644 --- a/routes/dashboard_routes.py +++ b/routes/dashboard_routes.py @@ -465,16 +465,9 @@ def get_full_dashboard_data(): def get_dashboard_stats(): - """計算看板統計數據 (供通知使用)""" - data = get_full_dashboard_data() - if data: - return { - 'new': data['today_new_products'], - 'up': len(data['increase_items_all']), - 'down': len(data['decrease_items_all']), - 'delisted': data['today_delisted_count'] - } - return {'new': 0, 'up': 0, 'down': 0, 'delisted': 0} + """計算看板統計數據 (供通知使用) — backward-compat wrapper""" + from services.dashboard_service import get_dashboard_stats as _get_dashboard_stats + return _get_dashboard_stats() # ========================================== diff --git a/services/dashboard_service.py b/services/dashboard_service.py new file mode 100644 index 0000000..43005b4 --- /dev/null +++ b/services/dashboard_service.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +看板統計服務 +提供 get_dashboard_stats() 給 task_runner 等 service 層使用, +避免 services 直接 import routes 層(反向依賴)。 +""" + + +def get_dashboard_stats(): + """計算看板統計數據 (供通知使用)""" + # lazy import 避免循環依賴;routes 在 Flask app 啟動後才可用 + from routes.dashboard_routes import get_full_dashboard_data + data = get_full_dashboard_data() + if data: + return { + 'new': data['today_new_products'], + 'up': len(data['increase_items_all']), + 'down': len(data['decrease_items_all']), + 'delisted': data['today_delisted_count'] + } + return {'new': 0, 'up': 0, 'down': 0, 'delisted': 0} diff --git a/services/logger_manager.py b/services/logger_manager.py index 42ffef2..cf1e5ca 100644 --- a/services/logger_manager.py +++ b/services/logger_manager.py @@ -1,53 +1,4 @@ -import logging -import os -import sys -from logging.handlers import RotatingFileHandler - -class SystemLogger: - """ - 自定義系統日誌管理器 - 封裝 logging 模組,提供統一的日誌格式與輸出設定 (Console + File) - """ - def __init__(self, name="System", log_file='system.log'): - self.logger = logging.getLogger(name) - self.logger.setLevel(logging.DEBUG) - - # 避免重複添加 Handler (防止日誌重複輸出) - if not self.logger.handlers: - # 取得專案根目錄 (假設此檔案位於 services/ 目錄下,往上兩層為根目錄) - base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - log_dir = os.path.join(base_dir, 'logs') - - # 確保 logs 目錄存在 - if not os.path.exists(log_dir): - try: - os.makedirs(log_dir) - except OSError: - pass # 忽略無法建立目錄的錯誤,改由 Console 輸出 - - file_path = os.path.join(log_dir, log_file) - - # 1. 檔案輸出 (RotatingFileHandler) - 支援自動切割 - try: - file_handler = RotatingFileHandler( - file_path, - maxBytes=10*1024*1024, # 10MB 切割 - backupCount=5, - encoding='utf-8' - ) - file_handler.setLevel(logging.DEBUG) - formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') - file_handler.setFormatter(formatter) - self.logger.addHandler(file_handler) - except Exception as e: - print(f"⚠️ Logger Init Warning: 無法建立日誌檔案 ({e})") - - # 2. 控制台輸出 (StreamHandler) - console_handler = logging.StreamHandler(sys.stdout) - console_handler.setLevel(logging.INFO) - console_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') - console_handler.setFormatter(console_formatter) - self.logger.addHandler(console_handler) - - def get_logger(self): - return self.logger \ No newline at end of file +# backward-compat re-export — 實作已移至 utils/logger_manager.py +# 保留此檔案確保既有 `from services.logger_manager import SystemLogger` 不受影響 +from utils.logger_manager import SystemLogger # noqa: F401 +from utils.logger_manager import * # noqa: F401, F403 diff --git a/services/task_runner.py b/services/task_runner.py index a75ab70..c5764bb 100644 --- a/services/task_runner.py +++ b/services/task_runner.py @@ -39,7 +39,7 @@ def run_momo_task_with_notification(): import services.notification_manager importlib.reload(services.notification_manager) from services.notification_manager import NotificationManager - from routes.dashboard_routes import get_dashboard_stats + from services.dashboard_service import get_dashboard_stats stats = get_dashboard_stats() diff --git a/utils/logger_manager.py b/utils/logger_manager.py new file mode 100644 index 0000000..233fcb8 --- /dev/null +++ b/utils/logger_manager.py @@ -0,0 +1,53 @@ +import logging +import os +import sys +from logging.handlers import RotatingFileHandler + +class SystemLogger: + """ + 自定義系統日誌管理器 + 封裝 logging 模組,提供統一的日誌格式與輸出設定 (Console + File) + """ + def __init__(self, name="System", log_file='system.log'): + self.logger = logging.getLogger(name) + self.logger.setLevel(logging.DEBUG) + + # 避免重複添加 Handler (防止日誌重複輸出) + if not self.logger.handlers: + # 取得專案根目錄 (假設此檔案位於 utils/ 目錄下,往上兩層為根目錄) + base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + log_dir = os.path.join(base_dir, 'logs') + + # 確保 logs 目錄存在 + if not os.path.exists(log_dir): + try: + os.makedirs(log_dir) + except OSError: + pass # 忽略無法建立目錄的錯誤,改由 Console 輸出 + + file_path = os.path.join(log_dir, log_file) + + # 1. 檔案輸出 (RotatingFileHandler) - 支援自動切割 + try: + file_handler = RotatingFileHandler( + file_path, + maxBytes=10*1024*1024, # 10MB 切割 + backupCount=5, + encoding='utf-8' + ) + file_handler.setLevel(logging.DEBUG) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + file_handler.setFormatter(formatter) + self.logger.addHandler(file_handler) + except Exception as e: + print(f"⚠️ Logger Init Warning: 無法建立日誌檔案 ({e})") + + # 2. 控制台輸出 (StreamHandler) + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setLevel(logging.INFO) + console_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') + console_handler.setFormatter(console_formatter) + self.logger.addHandler(console_handler) + + def get_logger(self): + return self.logger