Some checks failed
CD Pipeline / deploy (push) Failing after 59s
- 建立 Gitea Actions CD pipeline (.gitea/workflows/cd.yaml) - 部署模式: rsync Python 檔案至 188 → docker restart (volume mount) - Dockerfile/requirements 變動時自動重建 Docker image - 部署通知: Telegram (開始/成功/失敗) - 健康檢查: https://mo.wooo.work/health (最多 5 次重試) - 同步最新 CLAUDE.md / ADR-008 / memory (2026-04-19) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
191 lines
7.3 KiB
Python
191 lines
7.3 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
路由模組 - Blueprint 註冊中心
|
||
將所有路由模組集中管理,方便 app.py 統一註冊
|
||
|
||
重構狀態:
|
||
所有模組已獨立化並啟用:
|
||
- system_routes: 系統設定、日誌、備份 (完全獨立)
|
||
- edm_routes: EDM 與節慶儀表板 (完全獨立)
|
||
- monthly_routes: 月結分析 (完全獨立)
|
||
- dashboard_routes: 首頁商品看板 (完全獨立)
|
||
- daily_sales_routes: 當日業績分析 (完全獨立)
|
||
- api_routes: 通用 API (使用 task_runner, dashboard_routes)
|
||
- export_routes: 匯出功能 (使用 dashboard_routes, cache_service)
|
||
- import_routes: 匯入功能 (使用 cache_service)
|
||
- sales_routes: 業績分析 (延遲導入 app.py 複雜函數)
|
||
|
||
啟用方式:
|
||
1. 在 config.py 中將對應的 USE_MODULAR_ROUTES[key] 設為 True
|
||
2. 重啟應用程式
|
||
3. app.py 中的對應路由會自動被跳過 (cleanup_duplicate_routes)
|
||
4. url_for 向後相容透過 register_endpoint_aliases 實現
|
||
"""
|
||
|
||
from config import USE_MODULAR_ROUTES
|
||
|
||
# 記錄已被模組化路由覆蓋的端點名稱
|
||
MODULAR_ENDPOINTS = set()
|
||
|
||
|
||
def register_blueprints(app):
|
||
"""
|
||
註冊所有 Blueprint 到 Flask app
|
||
|
||
Args:
|
||
app: Flask 應用程式實例
|
||
"""
|
||
global MODULAR_ENDPOINTS
|
||
registered = []
|
||
errors = []
|
||
|
||
# ============================================================
|
||
# 第一階段:完全獨立的模組(無 app.py 依賴)
|
||
# 透過 config.USE_MODULAR_ROUTES 控制是否啟用
|
||
# ============================================================
|
||
|
||
# 系統管理路由 (設定、日誌、備份、分類管理)
|
||
if USE_MODULAR_ROUTES.get('system', False):
|
||
try:
|
||
from routes.system_routes import system_bp
|
||
app.register_blueprint(system_bp)
|
||
registered.append('system_routes')
|
||
# 記錄此模組覆蓋的端點
|
||
MODULAR_ENDPOINTS.update([
|
||
'health_check', 'prometheus_metrics', 'settings', 'system_settings_page',
|
||
'add_category', 'update_category', 'delete_category', 'test_url',
|
||
'show_logs', 'get_logs_api', 'trigger_backup', 'download_backup'
|
||
])
|
||
except ImportError as e:
|
||
errors.append(f"system_routes: {e}")
|
||
|
||
# EDM 與節慶儀表板路由
|
||
if USE_MODULAR_ROUTES.get('edm', False):
|
||
try:
|
||
from routes.edm_routes import edm_bp
|
||
app.register_blueprint(edm_bp)
|
||
registered.append('edm_routes')
|
||
MODULAR_ENDPOINTS.update(['edm_dashboard', 'festival_dashboard'])
|
||
except ImportError as e:
|
||
errors.append(f"edm_routes: {e}")
|
||
|
||
# 月結分析路由
|
||
if USE_MODULAR_ROUTES.get('monthly', False):
|
||
try:
|
||
from routes.monthly_routes import monthly_bp
|
||
app.register_blueprint(monthly_bp)
|
||
registered.append('monthly_routes')
|
||
MODULAR_ENDPOINTS.update(['monthly_summary_analysis_page', 'get_monthly_summary_data'])
|
||
except ImportError as e:
|
||
errors.append(f"monthly_routes: {e}")
|
||
|
||
# 商品看板路由 (首頁)
|
||
if USE_MODULAR_ROUTES.get('dashboard', False):
|
||
try:
|
||
from routes.dashboard_routes import dashboard_bp
|
||
app.register_blueprint(dashboard_bp)
|
||
registered.append('dashboard_routes')
|
||
MODULAR_ENDPOINTS.update(['index', 'brand_assets'])
|
||
except ImportError as e:
|
||
errors.append(f"dashboard_routes: {e}")
|
||
|
||
# 當日業績路由
|
||
if USE_MODULAR_ROUTES.get('daily_sales', False):
|
||
try:
|
||
from routes.daily_sales_routes import daily_sales_bp
|
||
app.register_blueprint(daily_sales_bp)
|
||
registered.append('daily_sales_routes')
|
||
MODULAR_ENDPOINTS.update([
|
||
'daily_sales', 'export_daily_sales_category', 'export_marketing_summary_excel'
|
||
])
|
||
except ImportError as e:
|
||
errors.append(f"daily_sales_routes: {e}")
|
||
|
||
# ============================================================
|
||
# 第二階段:有 app.py 依賴的模組(暫時保留在 app.py)
|
||
# ============================================================
|
||
|
||
# 通用 API 路由 - 依賴: scheduled_job_wrapper, get_dashboard_stats
|
||
if USE_MODULAR_ROUTES.get('api', False):
|
||
try:
|
||
from routes.api_routes import api_bp
|
||
app.register_blueprint(api_bp)
|
||
registered.append('api_routes')
|
||
MODULAR_ENDPOINTS.update([
|
||
'trigger_task', 'trigger_edm_task', 'trigger_festival_task',
|
||
'trigger_momo_notification', 'trigger_edm_notification', 'test_notification',
|
||
'get_price_history', 'get_price_change_details'
|
||
])
|
||
except ImportError as e:
|
||
errors.append(f"api_routes: {e}")
|
||
|
||
# 匯出功能路由 - 依賴: get_consolidated_data, _SALES_PROCESSED_CACHE
|
||
if USE_MODULAR_ROUTES.get('export', False):
|
||
try:
|
||
from routes.export_routes import export_bp
|
||
app.register_blueprint(export_bp)
|
||
registered.append('export_routes')
|
||
except ImportError as e:
|
||
errors.append(f"export_routes: {e}")
|
||
|
||
# 匯入功能路由 - 依賴: _SALES_DF_CACHE, _SALES_PROCESSED_CACHE
|
||
if USE_MODULAR_ROUTES.get('import', False):
|
||
try:
|
||
from routes.import_routes import import_bp
|
||
app.register_blueprint(import_bp)
|
||
registered.append('import_routes')
|
||
except ImportError as e:
|
||
errors.append(f"import_routes: {e}")
|
||
|
||
# 業績分析路由 - 依賴: 多個複雜函數
|
||
if USE_MODULAR_ROUTES.get('sales', False):
|
||
try:
|
||
from routes.sales_routes import sales_bp
|
||
app.register_blueprint(sales_bp)
|
||
registered.append('sales_routes')
|
||
MODULAR_ENDPOINTS.update([
|
||
'sales_analysis', 'growth_analysis',
|
||
'api_sales_table_data', 'api_sales_table_data_pandas',
|
||
'api_sales_top_detail', 'api_export_top_detail', 'api_yoy_comparison'
|
||
])
|
||
except ImportError as e:
|
||
errors.append(f"sales_routes: {e}")
|
||
|
||
# AI 推薦路由 - Ollama LLM 整合
|
||
if USE_MODULAR_ROUTES.get('ai', False):
|
||
try:
|
||
from routes.ai_routes import ai_bp
|
||
app.register_blueprint(ai_bp)
|
||
registered.append('ai_routes')
|
||
MODULAR_ENDPOINTS.update([
|
||
'ai_recommend', 'ai_status', 'ai_trends', 'ai_weather',
|
||
'ai_generate_copy', 'ai_recommend_products', 'ai_analyze_weather_products',
|
||
'ai_batch_generate_copy'
|
||
])
|
||
except ImportError as e:
|
||
errors.append(f"ai_routes: {e}")
|
||
|
||
# 輸出註冊結果
|
||
if registered:
|
||
print(f"✅ 已註冊路由模組: {', '.join(registered)}")
|
||
if errors:
|
||
for err in errors:
|
||
print(f"⚠️ 模組載入失敗: {err}")
|
||
|
||
if not registered and not errors:
|
||
print("ℹ️ 路由模組重構中,透過 config.USE_MODULAR_ROUTES 控制啟用")
|
||
|
||
|
||
def is_endpoint_modular(endpoint_name):
|
||
"""
|
||
檢查指定的端點是否已被模組化路由覆蓋
|
||
|
||
Args:
|
||
endpoint_name: 端點函數名稱
|
||
|
||
Returns:
|
||
bool: True 表示已被模組化,app.py 應跳過此路由
|
||
"""
|
||
return endpoint_name in MODULAR_ENDPOINTS
|