chore(cleanup): 移除 legacy 5888 測試入口
All checks were successful
CD Pipeline / deploy (push) Successful in 1m36s
All checks were successful
CD Pipeline / deploy (push) Successful in 1m36s
This commit is contained in:
@@ -72,7 +72,7 @@ pip install -r requirements.txt
|
||||
└── 當日業績/
|
||||
```
|
||||
|
||||
2. 開啟網頁介面:http://localhost:5888/auto_import
|
||||
2. 開啟網頁介面:http://localhost/auto_import
|
||||
|
||||
3. 設定:
|
||||
- **Google Drive 資料夾路徑**:`業績報表/當日業績`
|
||||
@@ -118,7 +118,7 @@ python3 test_google_drive.py
|
||||
|
||||
### 監控進度
|
||||
|
||||
開啟:http://localhost:5888/auto_import
|
||||
開啟:http://localhost/auto_import
|
||||
|
||||
可以看到:
|
||||
- ✅ 匯入配置
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> 本文件定義專案開發的核心準則與不可違反的規範
|
||||
> **建立日期**: 2026-01-12
|
||||
> **當前版本**: V10.21 (模組化治理守門版)
|
||||
> **當前版本**: V10.22 (Legacy 5888 入口清理版)
|
||||
> **最後更新**: 2026-04-30
|
||||
|
||||
---
|
||||
|
||||
@@ -126,7 +126,7 @@ drive_service.authenticate()
|
||||
|
||||
### 5.2 在系統中配置路徑
|
||||
|
||||
1. 開啟瀏覽器,前往:http://localhost:5888/auto_import
|
||||
1. 開啟瀏覽器,前往:http://localhost/auto_import
|
||||
2. 在「匯入配置」區域設定:
|
||||
- **Google Drive 資料夾路徑**:`業績報表/當日業績`
|
||||
- **檔案名稱模式**:`即時業績_當日`(選填,用於過濾檔案)
|
||||
@@ -268,7 +268,7 @@ tail -f logs/system.log | grep AutoImport
|
||||
|
||||
### 查看匯入統計
|
||||
|
||||
前往網頁介面:http://localhost:5888/auto_import
|
||||
前往網頁介面:http://localhost/auto_import
|
||||
|
||||
### 查詢排程統計
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
- AI metrics baseline 觀測:`/metrics` 在尚無 AI 自動化事件時仍輸出 `momo_ai_*` zero-baseline series,避免重啟後 Grafana/Prometheus 看不到 metric names。
|
||||
- ElephantAlpha transient fallback:NVIDIA NIM timeout、connection error、429 與 5xx 會嘗試下一個 fallback model;400 等非暫時性請求錯誤不重試。
|
||||
- 模組化治理守門:新增 `docs/guides/modularization_governance.md`、`docs/memory/code_modularization_inventory_20260430.md` 與 `tests/test_modularization_governance.py`,盤點並鎖住 15 個 >800 行 Python 大檔。
|
||||
- Legacy 5888 入口清理:刪除 `tests/main_test.py` standalone Flask 死碼,測試與自動匯入文件改用 Port 80 入口。
|
||||
|
||||
【下次待辦】
|
||||
- 依 inventory 優先拆 `routes/openclaw_bot_routes.py`、`routes/sales_routes.py`、`scheduler.py`。
|
||||
|
||||
6
app.py
6
app.py
@@ -26,7 +26,7 @@ sys.path.insert(0, BASE_DIR)
|
||||
|
||||
# 自動檢核並建立必要目錄
|
||||
try:
|
||||
for folder in ['database', 'services', 'crawler', 'logs', 'data', 'web/templates', 'web/static']:
|
||||
for folder in ['database', 'services', 'crawler', 'logs', 'data', 'templates', 'web/static']:
|
||||
folder_path = os.path.join(BASE_DIR, folder)
|
||||
if not os.path.exists(folder_path):
|
||||
os.makedirs(folder_path)
|
||||
@@ -95,8 +95,8 @@ except Exception as e:
|
||||
sys_log.error(f"無法檢測磁碟空間: {e}")
|
||||
|
||||
# 🚩 系統版本定義 (備份與顯示用)
|
||||
# 🚩 2026-04-30 V10.21: Modularization governance guardrail
|
||||
SYSTEM_VERSION = "V10.21"
|
||||
# 🚩 2026-04-30 V10.22: Legacy standalone test and port cleanup
|
||||
SYSTEM_VERSION = "V10.22"
|
||||
|
||||
# ==========================================
|
||||
# 🔒 SQL Injection 防護函數
|
||||
|
||||
@@ -254,7 +254,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.21"
|
||||
SYSTEM_VERSION = "V10.22"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> **最後更新**: 2026-04-30 (台北時間)
|
||||
> **狀態**: 🟢 四 AI Agent 自動化閉環已落地 — EventRouter / AutoHeal / OpenClaw Memory / ElephantAlpha bridge / Prometheus metrics / Smoke Dashboard / Smoke Trend Management / Telegram Summary / Grafana provisioning / Prometheus scrape / CD Gunicorn 掛載具測試覆蓋
|
||||
> **適用版本**: V10.20 ElephantAlpha transient fallback 版
|
||||
> **適用版本**: V10.22 Legacy 5888 入口清理版
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
- **AI metrics baseline 觀測**: `/metrics` 在尚無 AI 自動化事件時仍輸出 `momo_ai_*` zero-baseline series,避免 app 重啟後 Grafana/Prometheus 看不到 metric names。
|
||||
- **ElephantAlpha transient fallback**: NVIDIA NIM primary model timeout、connection error、429 與 5xx 會嘗試下一個 fallback model,400 等非暫時性請求錯誤不重試。
|
||||
- **模組化治理守門**: 盤點 15 個超過 800 行 Python 大檔,新增 `docs/guides/modularization_governance.md` 與 `tests/test_modularization_governance.py`,防止未分類巨檔再長出來。
|
||||
- **Legacy 5888 入口清理**: 刪除 `tests/main_test.py` standalone Flask 死碼,測試與自動匯入文件改用 Port 80 `/auto_import` 入口。
|
||||
|
||||
### 2026-04-28~29:Phase 3e 重構大戰 + daily_sales cache 隱形 bug 根除
|
||||
- **app.py 縮減 -10.8%**: 7,386 → 6,590 行,11 commits 全綠零 502。
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
import os
|
||||
from flask import Flask, render_template, session, redirect, url_for, send_from_directory
|
||||
from database.manager import DatabaseManager
|
||||
from auth import init_auth_routes, login_required
|
||||
from config import SECRET_KEY, DATA_DIR, LOG_DIR, EXCEL_EXPORT_DIR
|
||||
|
||||
# --- 1. 初始化路徑與 Flask ---
|
||||
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
|
||||
TEMPLATE_DIR = os.path.join(BASE_DIR, 'web', 'templates')
|
||||
STATIC_DIR = os.path.join(BASE_DIR, 'web', 'static')
|
||||
|
||||
app = Flask(__name__,
|
||||
template_folder=TEMPLATE_DIR,
|
||||
static_folder=STATIC_DIR)
|
||||
|
||||
app.secret_key = SECRET_KEY
|
||||
|
||||
# 初始化權限路由
|
||||
init_auth_routes(app)
|
||||
|
||||
# 初始化資料庫管理員
|
||||
db_mgr = DatabaseManager()
|
||||
|
||||
def get_db_size():
|
||||
"""獲取資料庫實體檔案大小"""
|
||||
db_path = os.path.join(DATA_DIR, 'momo_database.db')
|
||||
if os.path.exists(db_path):
|
||||
size_bytes = os.path.getsize(db_path)
|
||||
return f"{size_bytes / (1024 * 1024):.2f} MB"
|
||||
return "0.00 MB"
|
||||
|
||||
# --- 2. 路由定義 ---
|
||||
|
||||
@app.route('/')
|
||||
@login_required
|
||||
def dashboard():
|
||||
"""主看板:包含統計數據與商品明細清單"""
|
||||
stats = {
|
||||
"total_products": 0,
|
||||
"price_changes": 0,
|
||||
"db_size": get_db_size()
|
||||
}
|
||||
recent_products = []
|
||||
|
||||
session_db = db_mgr.Session()
|
||||
try:
|
||||
from database.models import Product, PriceRecord
|
||||
from datetime import datetime
|
||||
from sqlalchemy import desc
|
||||
|
||||
# A. 計算統計數據
|
||||
stats["total_products"] = session_db.query(Product).count()
|
||||
|
||||
today_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
stats["price_changes"] = session_db.query(PriceRecord).filter(
|
||||
PriceRecord.timestamp >= today_start
|
||||
).count()
|
||||
|
||||
# B. 抓取最新 50 筆商品明細
|
||||
# 邏輯:抓取 Product,並針對每個 Product 找出最後一筆價格
|
||||
products_query = session_db.query(Product).order_by(Product.id.desc()).limit(50).all()
|
||||
|
||||
for p in products_query:
|
||||
# 取得該商品在 PriceRecord 表中最新的一筆紀錄
|
||||
latest_price_rec = session_db.query(PriceRecord)\
|
||||
.filter_by(product_id=p.id)\
|
||||
.order_by(desc(PriceRecord.timestamp))\
|
||||
.first()
|
||||
|
||||
recent_products.append({
|
||||
"category": p.category,
|
||||
"name": p.name,
|
||||
"price": f"{latest_price_rec.price:,.0f}" if latest_price_rec else "N/A",
|
||||
"url": p.url,
|
||||
"time": latest_price_rec.timestamp.strftime('%m/%d %H:%M') if latest_price_rec else "-"
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
app.logger.error(f"❌ 看板數據讀取失敗: {e}")
|
||||
finally:
|
||||
session_db.close()
|
||||
|
||||
return render_template('dashboard.html', stats=stats, products=recent_products)
|
||||
|
||||
@app.route('/logs')
|
||||
@login_required
|
||||
def view_logs():
|
||||
"""日誌查看頁面"""
|
||||
log_path = os.path.join(LOG_DIR, 'system.log')
|
||||
content = "⚠️ 尚無日誌紀錄。"
|
||||
if os.path.exists(log_path):
|
||||
try:
|
||||
with open(log_path, 'r', encoding='utf-8') as f:
|
||||
f.seek(0, os.SEEK_END)
|
||||
size = f.tell()
|
||||
f.seek(max(0, size - 15000)) # 讀取最後 1.5 萬字元
|
||||
content = f.read()
|
||||
except Exception as e:
|
||||
content = f"讀取日誌出錯: {e}"
|
||||
return render_template('logs.html', log_content=content)
|
||||
|
||||
@app.route('/download/<path:filename>')
|
||||
@login_required
|
||||
def download_excel(filename):
|
||||
"""Excel 報表下載"""
|
||||
return send_from_directory(EXCEL_EXPORT_DIR, filename)
|
||||
|
||||
# --- 3. 啟動自我檢查 ---
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("\n" + "="*50)
|
||||
print("🚀 MOMO 伺服器啟動中...")
|
||||
print(f"📂 模板目錄: {TEMPLATE_DIR}")
|
||||
|
||||
# 檢查核心檔案
|
||||
for f in ['dashboard.html', 'login.html']:
|
||||
path = os.path.join(TEMPLATE_DIR, f)
|
||||
status = "✅ 存在" if os.path.exists(path) else "❌ 缺失"
|
||||
size = f"({os.path.getsize(path)} bytes)" if os.path.exists(path) else ""
|
||||
print(f" - {f}: {status} {size}")
|
||||
|
||||
print("="*50 + "\n")
|
||||
|
||||
app.run(host='0.0.0.0', port=5888, debug=True)
|
||||
@@ -90,7 +90,7 @@ def main():
|
||||
print("=" * 60)
|
||||
print()
|
||||
print("🌐 開啟網頁介面查看詳細資訊:")
|
||||
print(" http://localhost:5888/auto_import")
|
||||
print(" http://localhost/auto_import")
|
||||
print()
|
||||
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ try:
|
||||
print("Token 已儲存至: config/google_token.pickle")
|
||||
print()
|
||||
print("現在可以:")
|
||||
print(" 1. 在網頁介面測試連接: http://localhost:5888/auto_import")
|
||||
print(" 1. 在網頁介面測試連接: http://localhost/auto_import")
|
||||
print(" 2. 執行測試腳本: python3 test_google_drive.py")
|
||||
print()
|
||||
else:
|
||||
|
||||
@@ -84,11 +84,38 @@ def test_tracked_backup_artifacts_stay_removed():
|
||||
"app.py.backup_login_required",
|
||||
"vendor_stockout_list.html.backup",
|
||||
"database/edm_dashboard.html",
|
||||
"tests/main_test.py",
|
||||
]
|
||||
|
||||
assert [path for path in forbidden_artifacts if (ROOT / path).exists()] == []
|
||||
|
||||
|
||||
def test_active_code_no_longer_references_legacy_5888_port():
|
||||
active_paths = [
|
||||
ROOT / "app.py",
|
||||
ROOT / "tests",
|
||||
ROOT / "AUTO_IMPORT_README.md",
|
||||
ROOT / "GOOGLE_DRIVE_SETUP.md",
|
||||
]
|
||||
|
||||
offenders = []
|
||||
for active_path in active_paths:
|
||||
paths = active_path.rglob("*") if active_path.is_dir() else [active_path]
|
||||
for path in paths:
|
||||
if (
|
||||
not path.is_file()
|
||||
or path == Path(__file__).resolve()
|
||||
or "__pycache__" in path.parts
|
||||
or path.suffix == ".pyc"
|
||||
):
|
||||
continue
|
||||
content = path.read_text(encoding="utf-8", errors="ignore")
|
||||
if "5888" in content:
|
||||
offenders.append(str(path.relative_to(ROOT)))
|
||||
|
||||
assert offenders == []
|
||||
|
||||
|
||||
def test_executable_scripts_do_not_use_remove_orphans():
|
||||
script_paths = [
|
||||
ROOT / "scripts",
|
||||
|
||||
Reference in New Issue
Block a user