提供基本前后端骨架

This commit is contained in:
hisatri
2026-01-06 23:49:23 +08:00
parent 84d4ccc226
commit 06f8176e23
89 changed files with 19293 additions and 2 deletions

202
app/core/config.py Normal file
View File

@@ -0,0 +1,202 @@
"""
应用配置管理
使用 Pydantic Settings 进行类型安全的配置管理,
集成 ConfigLoader 支持环境变量和 YAML 配置文件。
配置优先级(从高到低):
1. 环境变量(前缀 SATONANO_
2. config.yaml 文件
3. .env 文件
4. 默认值
"""
from functools import lru_cache
from pathlib import Path
from typing import Any, Literal
from pydantic import Field, computed_field
from pydantic_settings import BaseSettings, SettingsConfigDict
from .config_loader import create_config_loader
def _yaml_settings_source() -> dict[str, Any]:
"""
YAML 配置源工厂函数
为 Pydantic Settings 提供 YAML 配置数据
"""
# 查找配置文件路径
config_paths = [
Path("config.yaml"),
Path("config.yml"),
Path(__file__).parent.parent.parent / "config.yaml",
Path(__file__).parent.parent.parent / "config.yml",
]
yaml_path = None
for path in config_paths:
if path.exists():
yaml_path = path
break
if yaml_path is None:
return {}
# 使用 ConfigLoader 加载配置(不带环境变量前缀,让 pydantic 处理)
loader = create_config_loader(
yaml_path=yaml_path,
env_prefix="", # 不读取环境变量,由 pydantic-settings 处理
yaml_required=False,
)
return loader.load()
class Settings(BaseSettings):
"""应用配置"""
model_config = SettingsConfigDict(
env_prefix="SATONANO_",
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore",
)
# 应用基础配置
app_name: str = "SatoNano"
app_version: str = "0.1.0"
debug: bool = False
environment: Literal["development", "staging", "production"] = "development"
# API 配置
api_v1_prefix: str = "/api/v1"
# 安全配置
secret_key: str = Field(
default="CHANGE-THIS-SECRET-KEY-IN-PRODUCTION",
description="JWT 签名密钥,生产环境必须更改",
)
algorithm: str = "HS256"
access_token_expire_minutes: int = 30
refresh_token_expire_days: int = 7
# 数据库配置
database_type: Literal["sqlite", "mysql", "postgresql"] = "sqlite"
database_host: str = "localhost"
database_port: int = 3306
database_name: str = "satonano"
database_username: str = ""
database_password: str = ""
database_sqlite_path: str = "./satonano.db"
database_echo: bool = False
# OAuth2 配置 (Linux.do)
oauth2_client_id: str = ""
oauth2_client_secret: str = ""
oauth2_callback_path: str = "/oauth2/callback"
# 首选端点
oauth2_authorize_endpoint: str = "https://connect.linux.do/oauth2/authorize"
oauth2_token_endpoint: str = "https://connect.linux.do/oauth2/token"
oauth2_user_info_endpoint: str = "https://connect.linux.do/api/user"
# 备用端点
oauth2_authorize_endpoint_reserve: str = "https://connect.linuxdo.org/oauth2/authorize"
oauth2_token_endpoint_reserve: str = "https://connect.linuxdo.org/oauth2/token"
oauth2_user_info_endpoint_reserve: str = "https://connect.linuxdo.org/api/user"
oauth2_request_timeout: int = 10 # 请求超时时间(秒)
# 密码策略
password_min_length: int = 8
password_max_length: int = 128
password_require_uppercase: bool = True
password_require_lowercase: bool = True
password_require_digit: bool = True
password_require_special: bool = False
# 用户名策略
username_min_length: int = 3
username_max_length: int = 32
# 前端静态文件配置
frontend_static_path: str = "./frontend/out"
@computed_field
@property
def database_url(self) -> str:
"""
动态构建数据库连接 URL
根据 database_type 自动生成对应的连接字符串
"""
if self.database_type == "sqlite":
return f"sqlite+aiosqlite:///{self.database_sqlite_path}"
if self.database_type == "mysql":
return (
f"mysql+aiomysql://{self.database_username}:{self.database_password}"
f"@{self.database_host}:{self.database_port}/{self.database_name}"
)
if self.database_type == "postgresql":
return (
f"postgresql+asyncpg://{self.database_username}:{self.database_password}"
f"@{self.database_host}:{self.database_port}/{self.database_name}"
)
return f"sqlite+aiosqlite:///{self.database_sqlite_path}"
@computed_field
@property
def is_production(self) -> bool:
"""是否为生产环境"""
return self.environment == "production"
@classmethod
def settings_customise_sources(
cls,
settings_cls: type[BaseSettings],
init_settings: Any,
env_settings: Any,
dotenv_settings: Any,
file_secret_settings: Any,
) -> tuple[Any, ...]:
"""
自定义配置源优先级
优先级顺序(从高到低):
1. 初始化参数
2. 环境变量
3. YAML 文件
4. .env 文件
5. 文件密钥
"""
class YamlConfigSettingsSource:
"""YAML 配置源适配器"""
def __init__(self, settings_cls: type[BaseSettings]) -> None:
self.settings_cls = settings_cls
self._yaml_data: dict[str, Any] | None = None
def __call__(self) -> dict[str, Any]:
if self._yaml_data is None:
self._yaml_data = _yaml_settings_source()
return self._yaml_data
return (
init_settings,
env_settings,
YamlConfigSettingsSource(settings_cls),
dotenv_settings,
file_secret_settings,
)
@lru_cache
def get_settings() -> Settings:
"""获取配置单例"""
return Settings()
settings = get_settings()