426 lines
13 KiB
Python
426 lines
13 KiB
Python
|
|
"""
|
|||
|
|
统一配置管理 - 所有接口、配置、SQL入口的集中管理
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import os
|
|||
|
|
import sqlite3
|
|||
|
|
from pathlib import Path
|
|||
|
|
from typing import Optional, Dict, Any
|
|||
|
|
from dataclasses import dataclass
|
|||
|
|
import logging
|
|||
|
|
|
|||
|
|
# 尝试加载环境变量
|
|||
|
|
try:
|
|||
|
|
from dotenv import load_dotenv
|
|||
|
|
load_dotenv()
|
|||
|
|
except Exception:
|
|||
|
|
# 如果没有.env文件或加载失败,使用默认配置
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
# 配置日志
|
|||
|
|
logging.basicConfig(level=logging.INFO)
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class DatabaseConfig:
|
|||
|
|
"""数据库配置"""
|
|||
|
|
# 数据库路径
|
|||
|
|
database_path: str = "data/app.db"
|
|||
|
|
database_url: str = "sqlite:///./data/app.db"
|
|||
|
|
|
|||
|
|
# 数据库连接参数
|
|||
|
|
connection_timeout: int = 30
|
|||
|
|
check_same_thread: bool = False
|
|||
|
|
|
|||
|
|
# 表配置
|
|||
|
|
enable_foreign_keys: bool = True
|
|||
|
|
enable_wal_mode: bool = True
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class APIConfig:
|
|||
|
|
"""API接口配置"""
|
|||
|
|
# 千问大模型配置
|
|||
|
|
qwen_api_key: Optional[str] = None
|
|||
|
|
qwen_base_url: str = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
|||
|
|
qwen_model: str = "qwen-plus-latest"
|
|||
|
|
qwen_temperature: float = 0.7
|
|||
|
|
qwen_max_tokens: int = 2000
|
|||
|
|
|
|||
|
|
# OpenAI配置(备用)
|
|||
|
|
openai_api_key: Optional[str] = None
|
|||
|
|
openai_model: str = "gpt-4"
|
|||
|
|
|
|||
|
|
# Anthropic配置(备用)
|
|||
|
|
anthropic_api_key: Optional[str] = None
|
|||
|
|
anthropic_model: str = "claude-3-sonnet-20240229"
|
|||
|
|
|
|||
|
|
# API超时配置
|
|||
|
|
api_timeout: int = 30
|
|||
|
|
api_retry_count: int = 3
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class AppConfig:
|
|||
|
|
"""应用配置"""
|
|||
|
|
# 应用基本信息
|
|||
|
|
app_name: str = "个性化饮食推荐助手"
|
|||
|
|
version: str = "1.0.0"
|
|||
|
|
debug: bool = True
|
|||
|
|
|
|||
|
|
# 路径配置
|
|||
|
|
model_path: str = "models/"
|
|||
|
|
log_path: str = "logs/"
|
|||
|
|
data_path: str = "data/"
|
|||
|
|
user_data_path: str = "data/users/"
|
|||
|
|
training_data_path: str = "data/training/"
|
|||
|
|
|
|||
|
|
# 日志配置
|
|||
|
|
log_level: str = "INFO"
|
|||
|
|
log_format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|||
|
|
log_file: str = "logs/app.log"
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class MLConfig:
|
|||
|
|
"""机器学习配置"""
|
|||
|
|
# 推荐系统配置
|
|||
|
|
max_recommendations: int = 5
|
|||
|
|
min_training_samples: int = 10
|
|||
|
|
model_update_threshold: int = 50
|
|||
|
|
|
|||
|
|
# 模型配置
|
|||
|
|
model_save_format: str = "joblib"
|
|||
|
|
enable_model_caching: bool = True
|
|||
|
|
model_cache_size: int = 100
|
|||
|
|
|
|||
|
|
# 特征工程配置
|
|||
|
|
tfidf_max_features: int = 1000
|
|||
|
|
tfidf_ngram_range: tuple = (1, 2)
|
|||
|
|
similarity_threshold: float = 0.7
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class OCRConfig:
|
|||
|
|
"""OCR识别配置"""
|
|||
|
|
# OCR引擎配置
|
|||
|
|
enable_tesseract: bool = True
|
|||
|
|
enable_paddleocr: bool = True
|
|||
|
|
enable_easyocr: bool = True
|
|||
|
|
|
|||
|
|
# 识别参数
|
|||
|
|
min_confidence: float = 0.6
|
|||
|
|
max_processing_time: float = 30.0
|
|||
|
|
|
|||
|
|
# 图片处理配置
|
|||
|
|
image_max_size: tuple = (1920, 1080)
|
|||
|
|
image_quality: int = 95
|
|||
|
|
enable_image_preprocessing: bool = True
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class UIConfig:
|
|||
|
|
"""界面配置"""
|
|||
|
|
# 移动端界面配置
|
|||
|
|
mobile_width: int = 375
|
|||
|
|
mobile_height: int = 812
|
|||
|
|
|
|||
|
|
# 主题配置
|
|||
|
|
theme_mode: str = "light" # light, dark, system
|
|||
|
|
color_theme: str = "blue"
|
|||
|
|
|
|||
|
|
# 圆角配置
|
|||
|
|
corner_radius_small: int = 8
|
|||
|
|
corner_radius_medium: int = 12
|
|||
|
|
corner_radius_large: int = 15
|
|||
|
|
corner_radius_xlarge: int = 20
|
|||
|
|
corner_radius_xxlarge: int = 25
|
|||
|
|
|
|||
|
|
|
|||
|
|
class UnifiedConfig:
|
|||
|
|
"""统一配置管理类"""
|
|||
|
|
|
|||
|
|
def __init__(self):
|
|||
|
|
self.database = DatabaseConfig()
|
|||
|
|
self.api = APIConfig()
|
|||
|
|
self.app = AppConfig()
|
|||
|
|
self.ml = MLConfig()
|
|||
|
|
self.ocr = OCRConfig()
|
|||
|
|
self.ui = UIConfig()
|
|||
|
|
|
|||
|
|
# 从环境变量加载配置
|
|||
|
|
self._load_from_env()
|
|||
|
|
|
|||
|
|
# 创建必要目录
|
|||
|
|
self._create_directories()
|
|||
|
|
|
|||
|
|
def _load_from_env(self):
|
|||
|
|
"""从环境变量加载配置"""
|
|||
|
|
# 数据库配置
|
|||
|
|
if os.getenv('DATABASE_PATH'):
|
|||
|
|
self.database.database_path = os.getenv('DATABASE_PATH')
|
|||
|
|
|
|||
|
|
# API配置
|
|||
|
|
self.api.qwen_api_key = os.getenv('QWEN_API_KEY')
|
|||
|
|
self.api.qwen_base_url = os.getenv('QWEN_BASE_URL', self.api.qwen_base_url)
|
|||
|
|
self.api.qwen_model = os.getenv('QWEN_MODEL', self.api.qwen_model)
|
|||
|
|
|
|||
|
|
self.api.openai_api_key = os.getenv('OPENAI_API_KEY')
|
|||
|
|
self.api.anthropic_api_key = os.getenv('ANTHROPIC_API_KEY')
|
|||
|
|
|
|||
|
|
# 应用配置
|
|||
|
|
if os.getenv('DEBUG'):
|
|||
|
|
self.app.debug = os.getenv('DEBUG').lower() == 'true'
|
|||
|
|
|
|||
|
|
if os.getenv('LOG_LEVEL'):
|
|||
|
|
self.app.log_level = os.getenv('LOG_LEVEL')
|
|||
|
|
|
|||
|
|
# ML配置
|
|||
|
|
if os.getenv('MAX_RECOMMENDATIONS'):
|
|||
|
|
self.ml.max_recommendations = int(os.getenv('MAX_RECOMMENDATIONS'))
|
|||
|
|
|
|||
|
|
if os.getenv('MIN_TRAINING_SAMPLES'):
|
|||
|
|
self.ml.min_training_samples = int(os.getenv('MIN_TRAINING_SAMPLES'))
|
|||
|
|
|
|||
|
|
def _create_directories(self):
|
|||
|
|
"""创建必要的目录"""
|
|||
|
|
directories = [
|
|||
|
|
self.app.data_path,
|
|||
|
|
self.app.user_data_path,
|
|||
|
|
self.app.training_data_path,
|
|||
|
|
self.app.model_path,
|
|||
|
|
self.app.log_path,
|
|||
|
|
Path(self.database.database_path).parent
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
for directory in directories:
|
|||
|
|
Path(directory).mkdir(parents=True, exist_ok=True)
|
|||
|
|
|
|||
|
|
def get_database_connection(self) -> sqlite3.Connection:
|
|||
|
|
"""获取数据库连接"""
|
|||
|
|
try:
|
|||
|
|
conn = sqlite3.connect(
|
|||
|
|
self.database.database_path,
|
|||
|
|
timeout=self.database.connection_timeout,
|
|||
|
|
check_same_thread=self.database.check_same_thread
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 启用外键约束
|
|||
|
|
if self.database.enable_foreign_keys:
|
|||
|
|
conn.execute("PRAGMA foreign_keys = ON")
|
|||
|
|
|
|||
|
|
# 启用WAL模式
|
|||
|
|
if self.database.enable_wal_mode:
|
|||
|
|
conn.execute("PRAGMA journal_mode = WAL")
|
|||
|
|
|
|||
|
|
return conn
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"数据库连接失败: {e}")
|
|||
|
|
raise
|
|||
|
|
|
|||
|
|
def get_api_config(self, provider: str = "qwen") -> Dict[str, Any]:
|
|||
|
|
"""获取API配置"""
|
|||
|
|
if provider == "qwen":
|
|||
|
|
return {
|
|||
|
|
"api_key": self.api.qwen_api_key,
|
|||
|
|
"base_url": self.api.qwen_base_url,
|
|||
|
|
"model": self.api.qwen_model,
|
|||
|
|
"temperature": self.api.qwen_temperature,
|
|||
|
|
"max_tokens": self.api.qwen_max_tokens,
|
|||
|
|
"timeout": self.api.api_timeout,
|
|||
|
|
"retry_count": self.api.api_retry_count
|
|||
|
|
}
|
|||
|
|
elif provider == "openai":
|
|||
|
|
return {
|
|||
|
|
"api_key": self.api.openai_api_key,
|
|||
|
|
"model": self.api.openai_model,
|
|||
|
|
"timeout": self.api.api_timeout,
|
|||
|
|
"retry_count": self.api.api_retry_count
|
|||
|
|
}
|
|||
|
|
elif provider == "anthropic":
|
|||
|
|
return {
|
|||
|
|
"api_key": self.api.anthropic_api_key,
|
|||
|
|
"model": self.api.anthropic_model,
|
|||
|
|
"timeout": self.api.api_timeout,
|
|||
|
|
"retry_count": self.api.api_retry_count
|
|||
|
|
}
|
|||
|
|
else:
|
|||
|
|
raise ValueError(f"不支持的API提供商: {provider}")
|
|||
|
|
|
|||
|
|
def is_api_available(self, provider: str = "qwen") -> bool:
|
|||
|
|
"""检查API是否可用"""
|
|||
|
|
if provider == "qwen":
|
|||
|
|
return self.api.qwen_api_key is not None
|
|||
|
|
elif provider == "openai":
|
|||
|
|
return self.api.openai_api_key is not None
|
|||
|
|
elif provider == "anthropic":
|
|||
|
|
return self.api.anthropic_api_key is not None
|
|||
|
|
else:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def get_ocr_config(self) -> Dict[str, Any]:
|
|||
|
|
"""获取OCR配置"""
|
|||
|
|
return {
|
|||
|
|
"enable_tesseract": self.ocr.enable_tesseract,
|
|||
|
|
"enable_paddleocr": self.ocr.enable_paddleocr,
|
|||
|
|
"enable_easyocr": self.ocr.enable_easyocr,
|
|||
|
|
"min_confidence": self.ocr.min_confidence,
|
|||
|
|
"max_processing_time": self.ocr.max_processing_time,
|
|||
|
|
"image_max_size": self.ocr.image_max_size,
|
|||
|
|
"image_quality": self.ocr.image_quality,
|
|||
|
|
"enable_preprocessing": self.ocr.enable_image_preprocessing
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def get_ui_config(self) -> Dict[str, Any]:
|
|||
|
|
"""获取界面配置"""
|
|||
|
|
return {
|
|||
|
|
"mobile_width": self.ui.mobile_width,
|
|||
|
|
"mobile_height": self.ui.mobile_height,
|
|||
|
|
"theme_mode": self.ui.theme_mode,
|
|||
|
|
"color_theme": self.ui.color_theme,
|
|||
|
|
"corner_radius": {
|
|||
|
|
"small": self.ui.corner_radius_small,
|
|||
|
|
"medium": self.ui.corner_radius_medium,
|
|||
|
|
"large": self.ui.corner_radius_large,
|
|||
|
|
"xlarge": self.ui.corner_radius_xlarge,
|
|||
|
|
"xxlarge": self.ui.corner_radius_xxlarge
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def validate_config(self) -> bool:
|
|||
|
|
"""验证配置有效性"""
|
|||
|
|
try:
|
|||
|
|
# 检查数据库路径
|
|||
|
|
db_path = Path(self.database.database_path)
|
|||
|
|
if not db_path.parent.exists():
|
|||
|
|
db_path.parent.mkdir(parents=True, exist_ok=True)
|
|||
|
|
|
|||
|
|
# 检查API配置
|
|||
|
|
if not self.is_api_available("qwen"):
|
|||
|
|
logger.warning("千问API密钥未配置,部分功能可能不可用")
|
|||
|
|
|
|||
|
|
# 检查目录权限
|
|||
|
|
for directory in [self.app.data_path, self.app.model_path, self.app.log_path]:
|
|||
|
|
if not Path(directory).exists():
|
|||
|
|
Path(directory).mkdir(parents=True, exist_ok=True)
|
|||
|
|
|
|||
|
|
logger.info("配置验证通过")
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"配置验证失败: {e}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def save_config_to_file(self, file_path: str = "config/app_config.json"):
|
|||
|
|
"""保存配置到文件"""
|
|||
|
|
import json
|
|||
|
|
|
|||
|
|
config_dict = {
|
|||
|
|
"database": self.database.__dict__,
|
|||
|
|
"api": self.api.__dict__,
|
|||
|
|
"app": self.app.__dict__,
|
|||
|
|
"ml": self.ml.__dict__,
|
|||
|
|
"ocr": self.ocr.__dict__,
|
|||
|
|
"ui": self.ui.__dict__
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Path(file_path).parent.mkdir(parents=True, exist_ok=True)
|
|||
|
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|||
|
|
json.dump(config_dict, f, ensure_ascii=False, indent=2)
|
|||
|
|
|
|||
|
|
logger.info(f"配置已保存到: {file_path}")
|
|||
|
|
|
|||
|
|
def load_config_from_file(self, file_path: str = "config/app_config.json"):
|
|||
|
|
"""从文件加载配置"""
|
|||
|
|
import json
|
|||
|
|
|
|||
|
|
if not Path(file_path).exists():
|
|||
|
|
logger.warning(f"配置文件不存在: {file_path}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|||
|
|
config_dict = json.load(f)
|
|||
|
|
|
|||
|
|
# 更新配置
|
|||
|
|
if "database" in config_dict:
|
|||
|
|
for key, value in config_dict["database"].items():
|
|||
|
|
if hasattr(self.database, key):
|
|||
|
|
setattr(self.database, key, value)
|
|||
|
|
|
|||
|
|
if "api" in config_dict:
|
|||
|
|
for key, value in config_dict["api"].items():
|
|||
|
|
if hasattr(self.api, key):
|
|||
|
|
setattr(self.api, key, value)
|
|||
|
|
|
|||
|
|
# 其他配置类似处理...
|
|||
|
|
|
|||
|
|
logger.info(f"配置已从文件加载: {file_path}")
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"加载配置文件失败: {e}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 全局配置实例
|
|||
|
|
_config_instance: Optional[UnifiedConfig] = None
|
|||
|
|
|
|||
|
|
|
|||
|
|
def get_config() -> UnifiedConfig:
|
|||
|
|
"""获取全局配置实例"""
|
|||
|
|
global _config_instance
|
|||
|
|
if _config_instance is None:
|
|||
|
|
_config_instance = UnifiedConfig()
|
|||
|
|
_config_instance.validate_config()
|
|||
|
|
return _config_instance
|
|||
|
|
|
|||
|
|
|
|||
|
|
def reload_config():
|
|||
|
|
"""重新加载配置"""
|
|||
|
|
global _config_instance
|
|||
|
|
_config_instance = None
|
|||
|
|
return get_config()
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 便捷函数
|
|||
|
|
def get_database_connection() -> sqlite3.Connection:
|
|||
|
|
"""获取数据库连接"""
|
|||
|
|
return get_config().get_database_connection()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def get_api_config(provider: str = "qwen") -> Dict[str, Any]:
|
|||
|
|
"""获取API配置"""
|
|||
|
|
return get_config().get_api_config(provider)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def is_api_available(provider: str = "qwen") -> bool:
|
|||
|
|
"""检查API是否可用"""
|
|||
|
|
return get_config().is_api_available(provider)
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
# 测试配置系统
|
|||
|
|
print("=== 统一配置管理测试 ===")
|
|||
|
|
|
|||
|
|
config = get_config()
|
|||
|
|
|
|||
|
|
print(f"✅ 应用名称: {config.app.app_name}")
|
|||
|
|
print(f"✅ 数据库路径: {config.database.database_path}")
|
|||
|
|
print(f"✅ 千问API可用: {config.is_api_available('qwen')}")
|
|||
|
|
print(f"✅ OpenAI API可用: {config.is_api_available('openai')}")
|
|||
|
|
|
|||
|
|
# 测试数据库连接
|
|||
|
|
try:
|
|||
|
|
conn = config.get_database_connection()
|
|||
|
|
print("✅ 数据库连接成功")
|
|||
|
|
conn.close()
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"❌ 数据库连接失败: {e}")
|
|||
|
|
|
|||
|
|
# 保存配置
|
|||
|
|
config.save_config_to_file()
|
|||
|
|
|
|||
|
|
print("✅ 配置系统测试完成")
|