Files
recommend/config/settings.py

430 lines
13 KiB
Python
Raw Permalink Normal View History

"""
统一配置管理 - 所有接口配置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引擎配置
# 默认只启用轻量级Tesseract其他引擎需要额外安装深度学习框架
enable_tesseract: bool = True # 轻量级,推荐使用
enable_paddleocr: bool = False # 可选需要PaddlePaddle占用内存较大
enable_easyocr: bool = False # 可选需要PyTorch占用内存很大1-2GB
# 识别参数
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配置 - 使用统一API密钥管理
from config.api_keys import get_api_key_manager
api_manager = get_api_key_manager()
self.api.qwen_api_key = api_manager.get_qwen_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 = api_manager.get_openai_key()
self.api.anthropic_api_key = api_manager.get_anthropic_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("✅ 配置系统测试完成")