🎉初次提交

This commit is contained in:
2025-08-03 16:50:56 +08:00
commit 56bdf5388d
67 changed files with 18379 additions and 0 deletions

12
config/__init__.py Normal file
View File

@@ -0,0 +1,12 @@
from .manager import config_manager, Settings
from .models import DatabaseConfig, AUFEConfig, S3Config, LogConfig, AppConfig
__all__ = [
"config_manager",
"Settings",
"DatabaseConfig",
"AUFEConfig",
"S3Config",
"LogConfig",
"AppConfig"
]

68
config/logger.py Normal file
View File

@@ -0,0 +1,68 @@
import sys
from pathlib import Path
from richuru import install
from loguru import logger
from typing import Any, Dict
from .manager import config_manager
def setup_logger():
"""根据配置文件设置loguru日志"""
install()
settings = config_manager.get_settings()
log_config = settings.log
# 移除默认的logger配置
logger.remove()
# 确保日志目录存在
log_dir = Path(log_config.file_path).parent
log_dir.mkdir(parents=True, exist_ok=True)
# 设置控制台输出
if log_config.console_output:
logger.add(
sys.stderr,
format=log_config.format,
level=log_config.level.value,
backtrace=log_config.backtrace,
diagnose=log_config.diagnose,
)
# 设置主日志文件
logger.add(
log_config.file_path,
format=log_config.format,
level=log_config.level.value,
rotation=log_config.rotation,
retention=log_config.retention,
compression=log_config.compression,
backtrace=log_config.backtrace,
diagnose=log_config.diagnose,
)
# 设置额外的日志记录器
for extra_logger in log_config.additional_loggers:
# 确保额外日志目录存在
extra_log_dir = Path(extra_logger["file_path"]).parent
extra_log_dir.mkdir(parents=True, exist_ok=True)
logger.add(
extra_logger["file_path"],
format=log_config.format,
level=extra_logger.get("level", log_config.level.value),
rotation=extra_logger.get("rotation", log_config.rotation),
retention=extra_logger.get("retention", log_config.retention),
compression=extra_logger.get("compression", log_config.compression),
backtrace=log_config.backtrace,
diagnose=log_config.diagnose,
filter=extra_logger.get("filter"),
)
logger.info("日志系统初始化完成")
def get_logger():
"""获取配置好的logger实例"""
return logger

177
config/manager.py Normal file
View File

@@ -0,0 +1,177 @@
import json
import os
from pathlib import Path
from typing import Any, Dict, Optional
from loguru import logger
from pydantic import ValidationError
from .models import Settings
class ConfigManager:
"""配置文件管理器"""
def __init__(self, config_file: str = "config.json"):
self.config_file = Path(config_file)
self._settings: Optional[Settings] = None
self._ensure_config_dir()
def _ensure_config_dir(self):
"""确保配置文件目录存在"""
self.config_file.parent.mkdir(parents=True, exist_ok=True)
def _create_default_config(self) -> Settings:
"""创建默认配置"""
logger.info("正在创建默认配置文件...")
return Settings()
def _save_config(self, settings: Settings):
"""保存配置到文件"""
try:
config_dict = settings.dict()
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(config_dict, f, indent=2, ensure_ascii=False)
logger.info(f"配置已保存到 {self.config_file}")
except Exception as e:
logger.error(f"保存配置文件失败: {e}")
raise
def _load_config(self) -> Settings:
"""从文件加载配置"""
if not self.config_file.exists():
logger.warning(f"配置文件 {self.config_file} 不存在,将创建默认配置")
settings = self._create_default_config()
self._save_config(settings)
return settings
try:
with open(self.config_file, 'r', encoding='utf-8') as f:
config_data = json.load(f)
# 验证并创建Settings对象
settings = Settings(**config_data)
logger.info(f"成功加载配置文件: {self.config_file}")
return settings
except json.JSONDecodeError as e:
logger.error(f"配置文件JSON格式错误: {e}")
raise
except ValidationError as e:
logger.error(f"配置文件验证失败: {e}")
raise
except Exception as e:
logger.error(f"加载配置文件失败: {e}")
raise
def get_settings(self) -> Settings:
"""获取配置设置"""
if self._settings is None:
self._settings = self._load_config()
return self._settings
def reload_config(self) -> Settings:
"""重新加载配置"""
logger.info("正在重新加载配置...")
self._settings = self._load_config()
return self._settings
def update_config(self, **kwargs) -> Settings:
"""更新配置"""
settings = self.get_settings()
# 创建新的配置字典
config_dict = settings.dict()
# 更新指定的配置项
for key, value in kwargs.items():
if '.' in key:
# 支持嵌套键,如 'database.url'
keys = key.split('.')
current = config_dict
for k in keys[:-1]:
if k not in current:
current[k] = {}
current = current[k]
current[keys[-1]] = value
else:
config_dict[key] = value
try:
# 验证更新后的配置
new_settings = Settings(**config_dict)
self._save_config(new_settings)
self._settings = new_settings
logger.info("配置更新成功")
return new_settings
except ValidationError as e:
logger.error(f"配置更新失败,验证错误: {e}")
raise
def validate_config(self) -> bool:
"""验证配置完整性"""
try:
settings = self.get_settings()
# 检查关键配置项
issues = []
# 检查数据库配置
if not settings.database.url:
issues.append("数据库URL未配置")
# 检查S3配置如果需要使用
if settings.s3.bucket_name and not settings.s3.access_key_id:
issues.append("S3配置不完整缺少access_key_id")
if settings.s3.bucket_name and not settings.s3.secret_access_key:
issues.append("S3配置不完整缺少secret_access_key")
# 检查日志配置
log_dir = Path(settings.log.file_path).parent
if not log_dir.exists():
try:
log_dir.mkdir(parents=True, exist_ok=True)
logger.info(f"创建日志目录: {log_dir}")
except Exception as e:
issues.append(f"无法创建日志目录 {log_dir}: {e}")
if issues:
logger.warning("配置验证发现问题:")
for issue in issues:
logger.warning(f" - {issue}")
return False
logger.info("配置验证通过")
return True
except Exception as e:
logger.error(f"配置验证失败: {e}")
return False
def get_config_summary(self) -> Dict[str, Any]:
"""获取配置摘要(隐藏敏感信息)"""
settings = self.get_settings()
config_dict = settings.dict()
# 隐藏敏感信息
sensitive_keys = [
'database.url',
's3.access_key_id',
's3.secret_access_key'
]
def hide_sensitive(data: Dict[str, Any], keys: list, prefix: str = ""):
for key, value in data.items():
current_key = f"{prefix}.{key}" if prefix else key
if current_key in sensitive_keys:
if isinstance(value, str) and value:
data[key] = value[:8] + "..." if len(value) > 8 else "***"
elif isinstance(value, dict):
hide_sensitive(value, keys, current_key)
summary = config_dict.copy()
hide_sensitive(summary, sensitive_keys)
return summary
# 全局配置管理器实例
config_manager = ConfigManager()

151
config/models.py Normal file
View File

@@ -0,0 +1,151 @@
from typing import List, Dict, Any, Optional
from pydantic import BaseModel, Field, field_validator
from enum import Enum
class LogLevel(str, Enum):
"""日志级别枚举"""
TRACE = "TRACE"
DEBUG = "DEBUG"
INFO = "INFO"
SUCCESS = "SUCCESS"
WARNING = "WARNING"
ERROR = "ERROR"
CRITICAL = "CRITICAL"
class DatabaseConfig(BaseModel):
"""数据库配置"""
url: str = Field(
default="mysql+aiomysql://root:123456@localhost:3306/loveac",
description="数据库连接URL"
)
echo: bool = Field(default=False, description="是否启用SQL日志")
pool_size: int = Field(default=10, description="连接池大小")
max_overflow: int = Field(default=20, description="连接池最大溢出")
pool_timeout: int = Field(default=30, description="连接池超时时间(秒)")
pool_recycle: int = Field(default=3600, description="连接回收时间(秒)")
class AUFEConfig(BaseModel):
"""AUFE连接配置"""
default_timeout: int = Field(default=30, description="默认超时时间(秒)")
max_retries: int = Field(default=3, description="最大重试次数")
max_reconnect_retries: int = Field(default=2, description="最大重连次数")
activity_timeout: int = Field(default=300, description="活动超时时间(秒)")
monitor_interval: int = Field(default=60, description="监控间隔(秒)")
retry_base_delay: float = Field(default=1.0, description="重试基础延迟(秒)")
retry_max_delay: float = Field(default=60.0, description="重试最大延迟(秒)")
retry_exponential_base: float = Field(default=2, description="重试指数基数")
# UAAP配置
uaap_base_url: str = Field(
default="http://uaap-aufe-edu-cn.vpn2.aufe.edu.cn:8118/cas",
description="UAAP基础URL"
)
uaap_login_url: str = Field(
default="http://uaap-aufe-edu-cn.vpn2.aufe.edu.cn:8118/cas/login?service=http%3A%2F%2Fjwcxk2.aufe.edu.cn%2Fj_spring_cas_security_check",
description="UAAP登录URL"
)
# 默认请求头
default_headers: Dict[str, str] = Field(
default_factory=lambda: {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
},
description="默认请求头"
)
class S3Config(BaseModel):
"""S3客户端配置"""
access_key_id: str = Field(default="", description="S3访问密钥ID")
secret_access_key: str = Field(default="", description="S3秘密访问密钥")
endpoint_url: Optional[str] = Field(default=None, description="S3终端节点URL")
region_name: str = Field(default="us-east-1", description="S3区域名称")
bucket_name: str = Field(default="", description="默认存储桶名称")
use_ssl: bool = Field(default=True, description="是否使用SSL")
signature_version: str = Field(default="s3v4", description="签名版本")
@field_validator('access_key_id', 'secret_access_key', 'bucket_name')
@classmethod
def validate_required_fields(cls, v):
"""验证必填字段"""
# 允许为空,但应在运行时检查
return v
class LogConfig(BaseModel):
"""日志配置"""
level: LogLevel = Field(default=LogLevel.INFO, description="日志级别")
format: str = Field(
default="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
description="日志格式"
)
file_path: str = Field(default="logs/app.log", description="日志文件路径")
rotation: str = Field(default="10 MB", description="日志轮转大小")
retention: str = Field(default="30 days", description="日志保留时间")
compression: str = Field(default="zip", description="日志压缩格式")
backtrace: bool = Field(default=True, description="是否启用回溯")
diagnose: bool = Field(default=True, description="是否启用诊断")
console_output: bool = Field(default=True, description="是否输出到控制台")
# 额外的日志文件配置
additional_loggers: List[Dict[str, Any]] = Field(
default_factory=lambda: [
{
"file_path": "logs/debug.log",
"level": "DEBUG",
"rotation": "10 MB"
},
{
"file_path": "logs/error.log",
"level": "ERROR",
"rotation": "10 MB"
}
],
description="额外的日志记录器配置"
)
class AppConfig(BaseModel):
"""应用程序配置"""
title: str = Field(default="LoveAC API", description="应用标题")
description: str = Field(default="LoveACAPI API", description="应用描述")
version: str = Field(default="1.0.0", description="应用版本")
debug: bool = Field(default=False, description="是否启用调试模式")
# CORS配置
cors_allow_origins: List[str] = Field(
default_factory=lambda: ["*"],
description="允许的CORS来源"
)
cors_allow_credentials: bool = Field(default=True, description="是否允许CORS凭据")
cors_allow_methods: List[str] = Field(
default_factory=lambda: ["*"],
description="允许的CORS方法"
)
cors_allow_headers: List[str] = Field(
default_factory=lambda: ["*"],
description="允许的CORS头部"
)
# 服务器配置
host: str = Field(default="0.0.0.0", description="服务器主机")
port: int = Field(default=8000, description="服务器端口")
workers: int = Field(default=1, description="工作进程数")
class Settings(BaseModel):
"""主配置类"""
database: DatabaseConfig = Field(default_factory=DatabaseConfig)
aufe: AUFEConfig = Field(default_factory=AUFEConfig)
s3: S3Config = Field(default_factory=S3Config)
log: LogConfig = Field(default_factory=LogConfig)
app: AppConfig = Field(default_factory=AppConfig)
class Config:
json_encoders = {
# 为枚举类型提供JSON编码器
LogLevel: lambda v: v.value
}