OCS 類別裡刪除軟體名稱未更新
BUBU 因客人有反應在 OCS Inventory
的軟體類別有做好分類,但某個分類裡誤填了其他軟體名稱,但刪除了去查看該分類還是有存剛剛所填的軟體名稱。以下是透過 AI 生成程式來修補此問題,原生的不知道是那段有問題造成無法更新。
執行過程
程式是用 python 去執行,因此有需要安裝必要的套件才能正常使用
安裝相關套件
- 安裝必要套件
apt install -y libmariadb-dev python3-venv python3-dev -y
- 建議
python
虛擬環境
python3 -m venv ./venv
- 進入到虛擬環境
source ./venv/bin/activate
- 安裝
mariadb
套件
pip3 install mariadb
程式執行
-
分成兩支程式來處理
-
建立模組
vim ocs_module.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
OCS Inventory 軟體類別清理工具 - 輔助模組
提供用於清理 OCS Inventory 軟體類別的功能函數
"""
import mariadb
import logging
import re
import sys
from datetime import datetime
# 建立日誌記錄器
logger = logging.getLogger("OCS-Cleanup")
def connect_to_database(host, user, password, database, port=3306):
"""
連接到 OCS Inventory 資料庫
參數:
host (str): 資料庫服務器主機名或 IP
user (str): 資料庫使用者名稱
password (str): 資料庫密碼
database (str): 資料庫名稱
port (int): 資料庫端口,默認為 3306
返回:
connection: 資料庫連接對象
"""
try:
print(f"嘗試連接資料庫 {host}:{port}...")
# 嘗試建立與 MariaDB 資料庫的連接
connection = mariadb.connect(
host=host,
user=user,
password=password,
database=database,
port=port
)
print("資料庫連接成功")
logger.info("成功連接到資料庫")
return connection
except mariadb.Error as err:
# 如果連接失敗,記錄錯誤並退出程序
error_msg = f"資料庫連接錯誤: {err}"
print(error_msg)
logger.error(error_msg)
sys.exit(1)
def get_valid_software_expressions(connection, category_id):
"""
獲取指定類別的有效軟體表達式
參數:
connection: 資料庫連接對象
category_id (int): 軟體類別的 ID
返回:
list: 該類別的所有軟體表達式列表
"""
cursor = connection.cursor(dictionary=True)
try:
# 執行 SQL 查詢,獲取指定類別的軟體表達式
cursor.execute(
"SELECT ID, SOFTWARE_EXP, SIGN_VERSION, VERSION, PUBLISHER FROM software_category_exp WHERE CATEGORY_ID = ?",
(category_id,)
)
expressions = cursor.fetchall()
logger.info(f"找到 {len(expressions)} 個類別 {category_id} 的軟體表達式")
return expressions
except mariadb.Error as err:
error_msg = f"查詢軟體表達式錯誤: {err}"
print(error_msg)
logger.error(error_msg)
return []
finally:
cursor.close()
def get_all_software_categories(connection):
"""
獲取所有軟體類別清單
參數:
connection: 資料庫連接對象
返回:
list: 所有軟體類別的列表,每個類別是包含 ID 和 CATEGORY_NAME 的字典
"""
cursor = connection.cursor(dictionary=True)
try:
print("正在查詢所有軟體類別...")
# 執行 SQL 查詢,獲取所有軟體類別
cursor.execute("SELECT ID, CATEGORY_NAME, OS FROM software_categories ORDER BY CATEGORY_NAME")
categories = cursor.fetchall()
print(f"找到 {len(categories)} 個軟體類別")
logger.info(f"找到 {len(categories)} 個軟體類別")
return categories
except mariadb.Error as err:
error_msg = f"查詢軟體類別錯誤: {err}"
print(error_msg)
logger.error(error_msg)
return []
finally:
cursor.close()
def get_linked_software(connection, category_id):
"""
獲取已連結到該類別的軟體
參數:
connection: 資料庫連接對象
category_id (int): 軟體類別的 ID
返回:
list: 連結到該類別的所有軟體列表
"""
cursor = connection.cursor(dictionary=True)
try:
# 執行 SQL 查詢,連接多個表獲取軟體資訊
query = """
SELECT scl.ID, sn.NAME, sp.PUBLISHER, sv.VERSION,
sn.ID as NAME_ID, sp.ID as PUBLISHER_ID, sv.ID as VERSION_ID
FROM software_categories_link scl
JOIN software_name sn ON scl.NAME_ID = sn.ID
JOIN software_publisher sp ON scl.PUBLISHER_ID = sp.ID
JOIN software_version sv ON scl.VERSION_ID = sv.ID
WHERE scl.CATEGORY_ID = ?
"""
cursor.execute(query, (category_id,))
linked_software = cursor.fetchall()
logger.info(f"找到 {len(linked_software)} 個連結到類別 {category_id} 的軟體")
return linked_software
except mariadb.Error as err:
error_msg = f"查詢連結軟體錯誤: {err}"
print(error_msg)
logger.error(error_msg)
return []
finally:
cursor.close()
def convert_to_regex(expression):
"""
將 OCS 通配符轉換為正則表達式
參數:
expression (str): 含有 OCS 通配符的表達式
返回:
str: 轉換後的正則表達式
"""
# 替換通配符: ? 替換為 . (匹配任意單個字元),* 替換為 .* (匹配任意數量的任意字元)
regex = expression.replace("?", ".").replace("*", ".*")
# 添加開始和結束標記,確保完全匹配
return f"^{regex}$"
def should_keep_software(software, expressions, debug=False):
"""
根據表達式判斷軟體是否應保留
參數:
software (dict): 軟體資訊
expressions (list): 有效的軟體表達式列表
debug (bool): 是否輸出詳細調試資訊
返回:
tuple: (是否保留, 匹配的表達式)
"""
software_name = software['NAME']
software_publisher = software['PUBLISHER']
software_version = software['VERSION']
if debug:
logger.debug(f"檢查軟體: {software_name}")
logger.debug(f"軟體字元表示: {repr(software_name)}")
for exp in expressions:
name_pattern = convert_to_regex(exp['SOFTWARE_EXP'])
if debug:
logger.debug(f"比對表達式: {exp['SOFTWARE_EXP']} -> 正則: {name_pattern}")
logger.debug(f"表達式字元表示: {repr(exp['SOFTWARE_EXP'])}")
if re.match(name_pattern, software_name, re.IGNORECASE):
if debug:
logger.debug(f"名稱匹配成功")
# 如果定義了發行商條件,檢查發行商是否匹配
if exp['PUBLISHER'] and exp['PUBLISHER'] != software_publisher:
if debug:
logger.debug(f"發行商不匹配: 期望 {exp['PUBLISHER']}, 實際 {software_publisher}")
continue
# 如果定義了版本條件,檢查版本是否匹配
if exp['VERSION']:
if exp['SIGN_VERSION'] == '=' and exp['VERSION'] != software_version:
if debug:
logger.debug(f"版本不匹配: 期望 {exp['VERSION']}, 實際 {software_version}")
continue
# 如果通過所有條件,表示應保留該軟體
if debug:
logger.debug(f"所有條件匹配,軟體將被保留")
return True, exp['SOFTWARE_EXP']
# 如果沒有匹配任何表達式,表示不應保留
if debug:
logger.debug(f"沒有匹配任何表達式,軟體將被刪除")
return False, None
def delete_invalid_links(connection, category_id, link_ids_to_delete):
"""
刪除不再有效的軟體連結
參數:
connection: 資料庫連接對象
category_id (int): 軟體類別的 ID
link_ids_to_delete (list): 要刪除的連結 ID 列表
返回:
int: 成功刪除的記錄數
"""
if not link_ids_to_delete:
logger.info("沒有需要刪除的無效連結")
return 0
cursor = connection.cursor()
try:
# 建立用於 IN 子句的參數占位符
placeholders = ', '.join(['?'] * len(link_ids_to_delete))
# 構建刪除 SQL 查詢
delete_query = f"DELETE FROM software_categories_link WHERE ID IN ({placeholders})"
cursor.execute(delete_query, link_ids_to_delete)
connection.commit()
deleted_count = cursor.rowcount
logger.info(f"成功刪除 {deleted_count} 個無效連結")
return deleted_count
except mariadb.Error as err:
error_msg = f"刪除無效連結錯誤: {err}"
print(error_msg)
logger.error(error_msg)
connection.rollback()
return 0
finally:
cursor.close()
def reset_software_link_categories(connection, category_id, invalid_combinations):
"""
將不再有效的軟體組合在 software_link 表中的 CATEGORY_ID 重置為 null
參數:
connection: 資料庫連接對象
category_id (int): 軟體類別的 ID
invalid_combinations (list): 無效的軟體組合列表,每個組合是 (NAME_ID, PUBLISHER_ID, VERSION_ID) 的元組
返回:
int: 成功更新的記錄數
"""
if not invalid_combinations:
logger.info("沒有需要在 software_link 中重置的無效組合")
return 0
cursor = connection.cursor()
try:
update_count = 0
# 對每個無效組合執行更新
for combo in invalid_combinations:
name_id, publisher_id, version_id = combo
# 構建更新 SQL 查詢,將 CATEGORY_ID 設為 NULL
reset_query = """
UPDATE software_link
SET CATEGORY_ID = NULL
WHERE NAME_ID = ? AND PUBLISHER_ID = ? AND VERSION_ID = ?
AND CATEGORY_ID = ?
"""
cursor.execute(reset_query, (name_id, publisher_id, version_id, category_id))
update_count += cursor.rowcount
connection.commit()
logger.info(f"已將 {update_count} 筆 software_link 資料中的 CATEGORY_ID 重置為 null")
return update_count
except mariadb.Error as err:
error_msg = f"重置 software_link 中的 CATEGORY_ID 錯誤: {err}"
print(error_msg)
logger.error(error_msg)
connection.rollback()
return 0
finally:
cursor.close()
def display_categories_menu(categories):
"""
顯示軟體類別選單
參數:
categories (list): 所有軟體類別的列表
返回:
None
"""
print("\n=== 軟體類別清單 ===")
print("ID\t類別名稱\t\t作業系統")
print("-" * 60)
for category in categories:
# 格式化類別資訊以對齊顯示
category_name = category['CATEGORY_NAME']
if len(category_name) > 15:
category_name = category_name[:12] + "..."
os_info = category['OS'] if category['OS'] else "所有系統"
print(f"{category['ID']}\t{category_name.ljust(15)}\t{os_info}")
print("-" * 60)
def check_match_status(connection, category_id, category_name=None):
"""
檢查軟體表達式與實際軟體的匹配情況
參數:
connection: 資料庫連接對象
category_id (int): 軟體類別的 ID
category_name (str, optional): 類別名稱,如果提供則顯示
返回:
None,但會在終端顯示匹配狀態
"""
# 獲取表達式
expressions = get_valid_software_expressions(connection, category_id)
if not expressions:
print(f"類別 {category_id} 沒有定義任何軟體表達式")
return
# 獲取連結的軟體
linked_software = get_linked_software(connection, category_id)
if not linked_software:
print(f"類別 {category_id} 沒有連結任何軟體")
return
category_info = f"'{category_name}' " if category_name else ""
print(f"\n=== 匹配檢查 (類別 {category_info}ID: {category_id}) ===")
print(f"找到 {len(expressions)} 個表達式和 {len(linked_software)} 個連結軟體")
print("\n表達式清單:")
for i, exp in enumerate(expressions, 1):
pub_info = f", 發行商: {exp['PUBLISHER']}" if exp['PUBLISHER'] else ""
ver_info = f", 版本: {exp['SIGN_VERSION']}{exp['VERSION']}" if exp['VERSION'] else ""
print(f"{i}. 表達式: {exp['SOFTWARE_EXP']}{pub_info}{ver_info}")
print("\n軟體匹配狀態:")
to_be_deleted = 0
for i, software in enumerate(linked_software, 1):
name = software['NAME']
publisher = software['PUBLISHER']
version = software['VERSION']
keep, matching_expression = should_keep_software(software, expressions, debug=False)
status = "將被保留" if keep else "將被刪除"
match_info = f", 匹配表達式: {matching_expression}" if keep else ""
if not keep:
to_be_deleted += 1
print(f"{i}. 名稱: {name}")
print(f" 發行商: {publisher}, 版本: {version}")
print(f" 狀態: {status}{match_info}")
# 僅當軟體將被刪除時輸出字元編碼檢查
if not keep:
print(f" 字元編碼檢查: {repr(name)}")
print()
print(f"總計: {len(linked_software)} 個軟體中,{to_be_deleted} 個將被刪除,{len(linked_software) - to_be_deleted} 個將被保留")
def cleanup_category(connection, category_id, dry_run=False, show_match=False):
"""
清理指定類別中不符合表達式的軟體連結
參數:
connection: 資料庫連接對象
category_id (int): 軟體類別的 ID
dry_run (bool): 如果為 True,只模擬執行但不實際修改資料庫
show_match (bool): 是否顯示匹配狀態詳情
返回:
bool: 操作是否成功
"""
# 獲取該類別的有效軟體表達式
expressions = get_valid_software_expressions(connection, category_id)
if not expressions:
logger.warning(f"類別 {category_id} 沒有定義任何軟體表達式,無法清理")
print(f"錯誤: 類別 {category_id} 沒有定義任何軟體表達式,無法清理")
return False
# 獲取已連結到該類別的軟體
linked_software = get_linked_software(connection, category_id)
if not linked_software:
logger.info(f"類別 {category_id} 沒有連結任何軟體,無需清理")
print(f"類別 {category_id} 沒有連結任何軟體,無需清理")
return True
# 如果需要顯示匹配狀態
if show_match:
# 查詢獲取類別名稱
cursor = connection.cursor(dictionary=True)
try:
cursor.execute("SELECT CATEGORY_NAME FROM software_categories WHERE ID = ?", (category_id,))
result = cursor.fetchone()
category_name = result['CATEGORY_NAME'] if result else None
except Exception as e:
logger.error(f"獲取類別名稱錯誤: {e}")
print(f"獲取類別名稱錯誤: {e}")
category_name = None
finally:
cursor.close()
# 顯示匹配狀態
check_match_status(connection, category_id, category_name)
# 找出不應保留的軟體連結
link_ids_to_delete = []
invalid_combinations = []
software_to_delete = [] # 存儲要刪除的軟體詳細資訊
for software in linked_software:
# 檢查軟體是否匹配任何表達式
keep, _ = should_keep_software(software, expressions)
if not keep:
link_ids_to_delete.append(software['ID'])
invalid_combinations.append((software['NAME_ID'], software['PUBLISHER_ID'], software['VERSION_ID']))
# 記錄將被刪除的軟體資訊
logger.info(f"將刪除連結: ID={software['ID']}, 名稱={software['NAME']}, "
f"發行商={software['PUBLISHER']}, 版本={software['VERSION']}")
# 將要刪除的軟體資訊添加到列表中
software_to_delete.append(software)
# 如果有要刪除的項目,顯示詳細信息
if software_to_delete:
print("\n=== 即將刪除的軟體連結 ===")
print(f"總共 {len(software_to_delete)} 個項目將被刪除:")
print("-" * 80)
print(f"{'ID':<8} {'軟體名稱':<40} {'發行商':<20} {'版本':<10}")
print("-" * 80)
for sw in software_to_delete:
name = sw['NAME']
if len(name) > 37:
name = name[:34] + "..."
publisher = sw['PUBLISHER']
if len(publisher) > 17:
publisher = publisher[:14] + "..."
version = sw['VERSION']
if len(version) > 7:
version = version[:4] + "..."
print(f"{sw['ID']:<8} {name:<40} {publisher:<20} {version:<10}")
print("-" * 80)
print(f"這些項目與類別 {category_id} 的表達式不匹配,將被解除連結。")
# 再次確認是否要繼續
if not dry_run:
confirm = input("\n確認要刪除這些項目嗎? (y/n): ")
if confirm.lower() != 'y':
print("已取消刪除操作。")
return True
else:
print("沒有找到需要刪除的軟體連結")
return True
if dry_run:
logger.info(f"[模擬模式] 將刪除 {len(link_ids_to_delete)} 個無效連結")
logger.info(f"[模擬模式] 將重置 {len(invalid_combinations)} 個軟體組合在 software_link 中的 CATEGORY_ID")
print(f"[模擬模式] 將刪除 {len(link_ids_to_delete)} 個無效連結")
print(f"[模擬模式] 將重置 {len(invalid_combinations)} 個軟體組合在 software_link 中的 CATEGORY_ID")
return True
# 執行刪除操作
deleted_count = delete_invalid_links(connection, category_id, link_ids_to_delete)
# 重置 software_link 表中對應的 CATEGORY_ID 為 null
if deleted_count > 0:
reset_count = reset_software_link_categories(connection, category_id, invalid_combinations)
print(f"成功刪除 {deleted_count} 個無效連結")
print(f"已將 {reset_count} 筆 software_link 資料中的 CATEGORY_ID 重置為 null")
else:
print("沒有找到需要刪除的無效連結")
return True
- 建立腳本
vim ocs_cleanup.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
OCS Inventory 軟體類別清理工具
用於清理 OCS Inventory 軟體類別中不符合表達式的軟體連結
執行方式:
1. 直接執行 python ocs_cleanup.py 進入互動模式
2. 使用命令行參數 python ocs_cleanup.py --category-id 1 --dry-run
作者: System Engineer
版本: 1.0
日期: 2025-04-28
"""
import os
import sys
import logging
import argparse
from datetime import datetime
# 匯入模組檔案中的函數
try:
from ocs_module import (
connect_to_database, get_all_software_categories, get_valid_software_expressions,
get_linked_software, check_match_status, cleanup_category, display_categories_menu,
logger # 從模組匯入日誌記錄器
)
print("成功匯入 OCS 模組")
except ImportError as e:
print(f"錯誤: 無法匯入 OCS 模組,請確保 ocs_module.py 文件在同一目錄中: {e}")
sys.exit(1)
# 程式開始執行
print("OCS 軟體類別清理工具啟動...")
# 資料庫連線設定
DB_CONFIG = {
'host': '127.0.0.1', # 資料庫服務器的 IP 地址
'user': 'user', # 資料庫使用者名稱 (請根據實際環境替換)
'password': 'your_db_password', # 資料庫密碼 (請根據實際環境替換)
'database': 'ocsweb', # 要連接的資料庫名稱
'port': 3306 # 資料庫服務器端口
}
# 日誌設定
LOG_CONFIG = {
'log_dir': 'logs', # 日誌檔案存放的目錄
'log_filename': 'ocs_cleanup_{datetime}.log', # 日誌檔案名稱格式
'log_level': logging.INFO # 日誌級別
}
# 設定日誌系統
try:
# 確保日誌目錄存在
os.makedirs(LOG_CONFIG['log_dir'], exist_ok=True)
print(f"日誌目錄已確認: {LOG_CONFIG['log_dir']}")
# 生成日誌檔案名稱
log_filename = LOG_CONFIG['log_filename'].format(datetime=datetime.now().strftime('%Y%m%d_%H%M%S'))
log_filepath = os.path.join(LOG_CONFIG['log_dir'], log_filename)
# 配置日誌處理器
file_handler = logging.FileHandler(log_filepath)
console_handler = logging.StreamHandler(sys.stdout)
# 設定日誌格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
# 添加處理器到日誌記錄器
logger.setLevel(LOG_CONFIG['log_level'])
logger.addHandler(file_handler)
logger.addHandler(console_handler)
logger.info("日誌系統已初始化")
print(f"日誌將被寫入: {log_filepath}")
except Exception as e:
print(f"日誌系統初始化失敗: {e}")
print("將繼續執行但可能無法記錄日誌")
def interactive_mode(connection):
"""
互動模式,讓使用者通過選單選擇操作和類別
參數:
connection: 資料庫連接對象
返回:
None
"""
try:
print("正在啟動互動模式...")
while True:
# 獲取所有軟體類別
categories = get_all_software_categories(connection)
if not categories:
print("未找到任何軟體類別,請先在 OCS Inventory 中建立軟體類別。")
logger.warning("未找到任何軟體類別")
break
# 顯示主選單
print("\n=== OCS 軟體類別清理工具 ===")
print("1. 顯示所有軟體類別")
print("2. 檢查類別匹配狀態")
print("3. 清理類別")
print("4. 模擬清理(不實際修改資料庫)")
print("q. 退出程式")
choice = input("\n請選擇操作 (1-4 或 q): ")
if choice.lower() == 'q':
print("程式已退出。")
break
elif choice == '1':
# 顯示所有類別
display_categories_menu(categories)
elif choice in ['2', '3', '4']:
# 顯示類別選單供選擇
display_categories_menu(categories)
# 提示用戶選擇類別
category_input = input("\n請輸入要操作的軟體類別ID (輸入 b 返回主選單): ")
if category_input.lower() == 'b':
continue
try:
category_id = int(category_input)
# 檢查選擇的類別是否存在
category_exists = False
category_name = ""
for cat in categories:
if cat['ID'] == category_id:
category_exists = True
category_name = cat['CATEGORY_NAME']
break
if not category_exists:
print(f"錯誤: ID 為 {category_id} 的類別不存在,請重新選擇。")
continue
if choice == '2':
# 檢查匹配狀態
check_match_status(connection, category_id, category_name)
elif choice == '3' or choice == '4':
dry_run = (choice == '4') # 如果選擇 4,則為模擬模式
if dry_run:
print(f"\n=== 模擬清理類別 '{category_name}' (ID: {category_id}) ===")
else:
print(f"\n=== 清理類別 '{category_name}' (ID: {category_id}) ===")
# 先顯示匹配狀態
check_match_status(connection, category_id, category_name)
# 確認是否要清理該類別
confirm = input(f"\n您確定要{'模擬' if dry_run else ''}清理類別 '{category_name}' (ID: {category_id}) 嗎? (y/n): ")
if confirm.lower() != 'y':
print("已取消清理操作。")
continue
# 執行清理操作
logger.info(f"開始{'模擬' if dry_run else ''}清理類別 '{category_name}' (ID: {category_id})")
print(f"\n開始{'模擬' if dry_run else ''}清理類別 '{category_name}'...")
success = cleanup_category(connection, category_id, dry_run, False)
if success:
logger.info(f"類別 '{category_name}' (ID: {category_id}) {'模擬' if dry_run else ''}清理完成")
print(f"類別 '{category_name}' {'模擬' if dry_run else ''}清理完成,日誌已儲存到: {log_filepath}")
else:
logger.error(f"類別 '{category_name}' (ID: {category_id}) 清理失敗")
print(f"類別 '{category_name}' 清理失敗,請查看日誌: {log_filepath}")
except ValueError:
print("錯誤: 類別ID必須是整數。")
else:
print("無效的選擇,請重新輸入。")
except KeyboardInterrupt:
print("\n程式已被使用者中斷。")
except Exception as e:
logger.error(f"互動模式發生錯誤: {e}")
print(f"發生錯誤: {e}")
print(f"詳細錯誤已記錄到日誌檔案: {log_filepath}")
def main():
"""
主函數,處理命令行參數並執行清理操作
"""
# 建立命令行參數解析器
parser = argparse.ArgumentParser(description='OCS Inventory 軟體類別清理工具')
# 添加各種命令行參數
parser.add_argument('--host', default=DB_CONFIG['host'], help=f'資料庫主機位址 (預設: {DB_CONFIG["host"]})')
parser.add_argument('--user', default=DB_CONFIG['user'], help=f'資料庫使用者名稱 (預設: {DB_CONFIG["user"]})')
parser.add_argument('--password', default=DB_CONFIG['password'], help='資料庫密碼 (預設: [已配置])')
parser.add_argument('--database', default=DB_CONFIG['database'], help=f'資料庫名稱 (預設: {DB_CONFIG["database"]})')
parser.add_argument('--port', type=int, default=DB_CONFIG['port'], help=f'資料庫連接埠 (預設: {DB_CONFIG["port"]})')
parser.add_argument('--category-id', type=int, help='要清理的軟體類別 ID')
parser.add_argument('--dry-run', action='store_true', help='模擬執行但不實際修改資料庫')
parser.add_argument('--check-only', action='store_true', help='只檢查匹配狀態,不執行清理')
parser.add_argument('--log-dir', default=LOG_CONFIG['log_dir'], help=f'日誌檔案目錄 (預設: {LOG_CONFIG["log_dir"]})')
parser.add_argument('--debug', action='store_true', help='啟用調試模式,顯示更詳細的日誌')
args = parser.parse_args() # 解析命令行參數
# 如果啟用調試模式,設置日誌級別為 DEBUG
if args.debug:
logger.setLevel(logging.DEBUG)
logger.debug("調試模式已啟用")
# 連接資料庫
connection = connect_to_database(
args.host, args.user, args.password, args.database, args.port
)
try:
if args.category_id:
# 如果指定了類別 ID
if args.check_only:
# 如果只檢查匹配狀態
check_match_status(connection, args.category_id)
else:
# 執行清理操作
if args.dry_run:
logger.info("=== 模擬模式 ===") # 如果是模擬模式,記錄提示
print("=== 模擬模式 ===")
# 先顯示匹配狀態
check_match_status(connection, args.category_id)
# 不需要在這裡詢問是否繼續,因為在cleanup_category函式中已加入了詳細確認步驟
success = cleanup_category(connection, args.category_id, args.dry_run, False) # 執行清理
if success:
# 如果清理成功,記錄成功信息
logger.info(f"類別 {args.category_id} 清理" + ("模擬 " if args.dry_run else "") + "完成")
else:
# 如果清理失敗,記錄失敗信息
logger.error(f"類別 {args.category_id} 清理失敗")
else:
# 如果沒有指定類別 ID,進入互動模式
interactive_mode(connection)
finally:
connection.close() # 確保在退出前關閉資料庫連接
if __name__ == "__main__":
# 如果直接執行此程式
try:
print("程式入口點啟動...")
if len(sys.argv) == 1:
# 無命令行參數,使用預設設定並啟動互動模式
try:
print("以預設設定啟動互動模式...")
# 使用預設設定連接資料庫
connection = connect_to_database(
DB_CONFIG['host'],
DB_CONFIG['user'],
DB_CONFIG['password'],
DB_CONFIG['database'],
DB_CONFIG['port']
)
# 進入互動模式
interactive_mode(connection)
# 確保關閉連接
try:
connection.close()
print("資料庫連接已關閉")
except:
pass
except Exception as e:
# 捕獲並記錄其他任何異常
error_msg = f"執行時發生錯誤: {e}"
print(error_msg)
try:
logger.error(error_msg)
print(f"詳細錯誤已記錄到日誌檔案: {log_filepath}")
except:
print("無法記錄錯誤到日誌檔案")
else:
# 如果有提供命令行參數,使用命令行參數執行主函數
print("以命令行參數啟動...")
main()
except Exception as e:
print(f"主程式發生未捕獲的異常: {e}")
print("請檢查您的環境設定並重試")