Files
recommend/config/settings.py

429 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
统一配置管理 - 所有接口、配置、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配置 - 使用统一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("✅ 配置系统测试完成")