refactor(p1-01e): repair_database_schema 抽到 database/schema_repair.py
All checks were successful
CD Pipeline / deploy (push) Successful in 1m7s
All checks were successful
CD Pipeline / deploy (push) Successful in 1m7s
- 80 行 schema 修復邏輯抽出,搭配 _ensure_column helper 去除 7 個 promo_products 欄位重複碼塊 - app.py 改為 from database.schema_repair import repair_database_schema 維持原呼叫 - 行為 100% 對齊(含 SQLite WAL 啟用、products.created_at 補資料) 行數變化: app.py 7,151 → 7,070 (-81)
This commit is contained in:
85
app.py
85
app.py
@@ -159,89 +159,8 @@ from utils.security import ( # noqa: E402
|
||||
validate_upload_file,
|
||||
)
|
||||
|
||||
# 🚩 資料庫結構自動修復 (V9.53 新增)
|
||||
def repair_database_schema():
|
||||
db = DatabaseManager()
|
||||
engine = db.engine
|
||||
from sqlalchemy import inspect, text
|
||||
from config import DATABASE_TYPE
|
||||
try:
|
||||
# 🚩 V9.96: 啟用 SQLite WAL 模式以解決 database is locked 問題 (僅 SQLite)
|
||||
if DATABASE_TYPE == 'sqlite':
|
||||
with engine.connect() as conn:
|
||||
# 啟用 WAL 模式 (Write-Ahead Logging)
|
||||
conn.execute(text("PRAGMA journal_mode=WAL"))
|
||||
conn.commit()
|
||||
sys_log.info("[Database] [WAL] ✅ SQLite WAL 模式已啟用 | 提升並發寫入效能")
|
||||
else:
|
||||
sys_log.info(f"[Database] ✅ 使用 {DATABASE_TYPE.upper()} 資料庫")
|
||||
|
||||
inspector = inspect(engine)
|
||||
# V9.70: 檢查 products 表
|
||||
if 'products' in inspector.get_table_names():
|
||||
product_columns = [c['name'] for c in inspector.get_columns('products')]
|
||||
if 'image_url' not in product_columns:
|
||||
sys_log.warning("[Database] [Schema] ⚠️ 偵測到 products 表缺少 image_url 欄位 | 正在自動修復...")
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text("ALTER TABLE products ADD COLUMN image_url TEXT"))
|
||||
conn.commit()
|
||||
sys_log.info("[Database] [Schema] ✅ products.image_url 欄位修復完成")
|
||||
|
||||
if 'created_at' not in product_columns:
|
||||
sys_log.warning("[Database] [Schema] ⚠️ 偵測到 products 表缺少 created_at 欄位 | 正在自動修復...")
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text("ALTER TABLE products ADD COLUMN created_at DATETIME"))
|
||||
conn.execute(text("UPDATE products SET created_at = updated_at WHERE created_at IS NULL"))
|
||||
conn.commit()
|
||||
sys_log.info("[Database] [Schema] ✅ products.created_at 欄位修復完成")
|
||||
|
||||
if 'promo_products' in inspector.get_table_names():
|
||||
columns = [c['name'] for c in inspector.get_columns('promo_products')]
|
||||
if 'url' not in columns:
|
||||
sys_log.warning("⚠️ 偵測到 promo_products 表缺少 url 欄位,正在自動修復...")
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text("ALTER TABLE promo_products ADD COLUMN url TEXT"))
|
||||
conn.commit()
|
||||
sys_log.info("✅ url 欄位修復完成")
|
||||
if 'image_url' not in columns:
|
||||
sys_log.warning("⚠️ 偵測到 promo_products 表缺少 image_url 欄位,正在自動修復...")
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text("ALTER TABLE promo_products ADD COLUMN image_url TEXT"))
|
||||
conn.commit()
|
||||
sys_log.info("✅ image_url 欄位修復完成")
|
||||
if 'previous_price' not in columns:
|
||||
sys_log.warning("⚠️ 偵測到 promo_products 表缺少 previous_price 欄位,正在自動修復...")
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text("ALTER TABLE promo_products ADD COLUMN previous_price INTEGER"))
|
||||
conn.commit()
|
||||
sys_log.info("✅ previous_price 欄位修復完成")
|
||||
if 'session_time_text' not in columns:
|
||||
sys_log.warning("⚠️ 偵測到 promo_products 表缺少 session_time_text 欄位,正在自動修復...")
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text("ALTER TABLE promo_products ADD COLUMN session_time_text TEXT"))
|
||||
conn.commit()
|
||||
sys_log.info("✅ session_time_text 欄位修復完成")
|
||||
if 'remain_qty' not in columns:
|
||||
sys_log.warning("⚠️ 偵測到 promo_products 表缺少 remain_qty 欄位,正在自動修復...")
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text("ALTER TABLE promo_products ADD COLUMN remain_qty INTEGER"))
|
||||
conn.commit()
|
||||
sys_log.info("✅ remain_qty 欄位修復完成")
|
||||
if 'discount_text' not in columns:
|
||||
sys_log.warning("⚠️ 偵測到 promo_products 表缺少 discount_text 欄位,正在自動修復...")
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text("ALTER TABLE promo_products ADD COLUMN discount_text TEXT"))
|
||||
conn.commit()
|
||||
sys_log.info("✅ discount_text 欄位修復完成")
|
||||
if 'page_type' not in columns:
|
||||
sys_log.warning("⚠️ 偵測到 promo_products 表缺少 page_type 欄位,正在自動修復...")
|
||||
with engine.connect() as conn:
|
||||
# 將既有資料預設為 'edm'
|
||||
conn.execute(text("ALTER TABLE promo_products ADD COLUMN page_type TEXT DEFAULT 'edm'"))
|
||||
conn.commit()
|
||||
sys_log.info("✅ page_type 欄位修復完成")
|
||||
except Exception as e:
|
||||
sys_log.error(f"[Database] [Schema] ❌ 資料庫修復失敗 | Error: {e}")
|
||||
# 🚩 資料庫結構自動修復 (V9.53 新增) — 實作搬至 database/schema_repair.py
|
||||
from database.schema_repair import repair_database_schema # noqa: E402, F401
|
||||
|
||||
# 從環境變數讀取 NGROK_AUTH_TOKEN(如果未設定則使用原值,但會發出警告)
|
||||
NGROK_AUTH_TOKEN = os.getenv('NGROK_AUTH_TOKEN', '36e27NM5V7sUJ8QxJIAAWCp7sUv_3brtcrBarYvcP3SbvFKhF')
|
||||
|
||||
75
database/schema_repair.py
Normal file
75
database/schema_repair.py
Normal file
@@ -0,0 +1,75 @@
|
||||
"""啟動時資料庫 schema 自動修復。
|
||||
|
||||
從 app.py 抽出的 repair_database_schema()。
|
||||
歷史背景:早期 V9.53 起為了讓部署不必執行 migration,在啟動時動態檢查欄位並
|
||||
ALTER TABLE 補上。長期應改走 Alembic / Migration script,但這個 fallback 還活著。
|
||||
|
||||
呼叫端:app.py 啟動末段呼叫一次。
|
||||
"""
|
||||
from utils.logger_manager import SystemLogger
|
||||
|
||||
_log = SystemLogger("SchemaRepair").get_logger()
|
||||
|
||||
|
||||
def _ensure_column(engine, text_fn, table, column, ddl, post_sql=None):
|
||||
"""補上單一欄位 helper:若不存在則 ALTER TABLE。"""
|
||||
from sqlalchemy import inspect
|
||||
inspector = inspect(engine)
|
||||
if table not in inspector.get_table_names():
|
||||
return False
|
||||
columns = [c['name'] for c in inspector.get_columns(table)]
|
||||
if column in columns:
|
||||
return False
|
||||
_log.warning(f"[Database] [Schema] ⚠️ 偵測到 {table} 表缺少 {column} 欄位 | 正在自動修復...")
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text_fn(ddl))
|
||||
if post_sql:
|
||||
conn.execute(text_fn(post_sql))
|
||||
conn.commit()
|
||||
_log.info(f"[Database] [Schema] ✅ {table}.{column} 欄位修復完成")
|
||||
return True
|
||||
|
||||
|
||||
def repair_database_schema():
|
||||
"""啟動時資料庫結構自動修復(V9.53)。"""
|
||||
from database.manager import DatabaseManager
|
||||
from sqlalchemy import inspect, text
|
||||
from config import DATABASE_TYPE
|
||||
|
||||
db = DatabaseManager()
|
||||
engine = db.engine
|
||||
|
||||
try:
|
||||
# V9.96: SQLite WAL 模式(解決 database is locked)
|
||||
if DATABASE_TYPE == 'sqlite':
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text("PRAGMA journal_mode=WAL"))
|
||||
conn.commit()
|
||||
_log.info("[Database] [WAL] ✅ SQLite WAL 模式已啟用 | 提升並發寫入效能")
|
||||
else:
|
||||
_log.info(f"[Database] ✅ 使用 {DATABASE_TYPE.upper()} 資料庫")
|
||||
|
||||
inspector = inspect(engine)
|
||||
|
||||
# V9.70: products 表
|
||||
_ensure_column(engine, text, 'products', 'image_url',
|
||||
"ALTER TABLE products ADD COLUMN image_url TEXT")
|
||||
_ensure_column(engine, text, 'products', 'created_at',
|
||||
"ALTER TABLE products ADD COLUMN created_at DATETIME",
|
||||
post_sql="UPDATE products SET created_at = updated_at WHERE created_at IS NULL")
|
||||
|
||||
# promo_products 表
|
||||
promo_columns = [
|
||||
('url', "ALTER TABLE promo_products ADD COLUMN url TEXT"),
|
||||
('image_url', "ALTER TABLE promo_products ADD COLUMN image_url TEXT"),
|
||||
('previous_price', "ALTER TABLE promo_products ADD COLUMN previous_price INTEGER"),
|
||||
('session_time_text', "ALTER TABLE promo_products ADD COLUMN session_time_text TEXT"),
|
||||
('remain_qty', "ALTER TABLE promo_products ADD COLUMN remain_qty INTEGER"),
|
||||
('discount_text', "ALTER TABLE promo_products ADD COLUMN discount_text TEXT"),
|
||||
('page_type', "ALTER TABLE promo_products ADD COLUMN page_type TEXT DEFAULT 'edm'"),
|
||||
]
|
||||
for col_name, ddl in promo_columns:
|
||||
_ensure_column(engine, text, 'promo_products', col_name, ddl)
|
||||
|
||||
except Exception as e:
|
||||
_log.error(f"[Database] [Schema] ❌ 資料庫修復失敗 | Error: {e}")
|
||||
Reference in New Issue
Block a user