import argparse import configparser import os import sys from typing import Literal from dotenv import load_dotenv from pydantic import computed_field from pydantic_settings import BaseSettings class AppSettings(BaseSettings): """ 应用配置 """ app_env: str = 'dev' app_name: str = 'ShiErFeng-FastAPI' app_root_path: str = '/dev-api' app_host: str = '0.0.0.0' app_port: int = 9099 app_version: str = '1.0.0' app_reload: bool = True app_workers: int = 1 app_ip_location_query: bool = True app_same_time_login: bool = True app_demo_mode: bool = False app_disable_swagger: bool = False app_disable_redoc: bool = False app_trusted_proxy_ips: str = '127.0.0.1,::1' app_trusted_proxy_hops: int = 1 class JwtSettings(BaseSettings): """ Jwt配置 """ jwt_secret_key: str = 'b01c66dc2c58dc6a0aabfe2144256be36226de378bf87f72c0c795dda67f4d55' jwt_algorithm: str = 'HS256' jwt_expire_minutes: int = 1440 jwt_redis_expire_minutes: int = 30 class DataBaseSettings(BaseSettings): """ 数据库配置 """ db_type: Literal['mysql', 'postgresql'] = 'mysql' db_host: str = '127.0.0.1' db_port: int = 3306 db_username: str = 'root' db_password: str = 'mysqlroot' db_database: str = 'shierfeng-fastapi' db_echo: bool = True db_max_overflow: int = 10 db_pool_size: int = 50 db_pool_recycle: int = 3600 db_pool_timeout: int = 30 @computed_field @property def sqlglot_parse_dialect(self) -> str: if self.db_type == 'postgresql': return 'postgres' return self.db_type class RedisSettings(BaseSettings): """ Redis配置 """ redis_host: str = '127.0.0.1' redis_port: int = 6379 redis_username: str = '' redis_password: str = '' redis_database: int = 2 class LogSettings(BaseSettings): """ 日志与队列配置 """ log_mask_enabled: bool = True log_mask_placeholder: str = '******' log_mask_fields: str = ( 'password,old_password,new_password,confirm_password,api_key,token,access_token,refresh_token,' 'authorization,client_secret,secret,secret_key,private_key,private_key_pem,credential,credentials,' 'sms_code,captcha_code,system_prompt' ) log_partial_mask_fields: str = 'phonenumber,phone,mobile,email' log_config_secret_patterns: str = 'password,token,secret,key,private,credential,access,jwt,captcha,sms' log_stream_key: str = 'log:stream' log_stream_group: str = 'log_aggregator' log_stream_consumer_prefix: str = 'worker' log_stream_batch_size: int = 100 log_stream_block_ms: int = 2000 log_stream_maxlen: int = 100000 log_stream_claim_idle_ms: int = 60000 log_stream_claim_interval_ms: int = 5000 log_stream_claim_batch_size: int = 100 log_stream_dedup_ttl: int = 3600 log_stream_dedup_prefix: str = 'log:dedup' loguru_json: bool = False loguru_level: str = 'INFO' loguru_stdout: bool = True log_file_enabled: bool = True log_file_base_dir: str = 'logs' loguru_rotation: str = '50MB' loguru_retention: str = '30 days' loguru_compression: str = 'zip' log_instance_id: str = 'prod' log_service_name: str = 'shierfeng-fastapi-backend' log_worker_id: str = 'auto' class TransportCryptoSettings(BaseSettings): """ 传输层加解密配置 """ transport_crypto_enabled: bool = True transport_crypto_mode: Literal['off', 'optional', 'required'] = 'optional' transport_crypto_algorithm: str = 'RSA_OAEP_AES_256_GCM' transport_crypto_kid: str = 'default' transport_crypto_public_key: str = '' transport_crypto_private_key: str = '' transport_crypto_legacy_key_pairs: str = '[]' transport_crypto_rsa_key_size: int = 2048 transport_crypto_public_key_ttl_seconds: int = 3600 transport_crypto_frontend_config_ttl_seconds: int = 300 transport_crypto_max_get_url_length: int = 4096 transport_crypto_clock_skew_seconds: int = 120 transport_crypto_replay_ttl_seconds: int = 300 transport_crypto_enabled_paths: str = '' transport_crypto_required_paths: str = '' transport_crypto_exclude_paths: str = ( '/openapi.json,/docs,/docs/oauth2-redirect,/redoc,' '/transport/crypto/frontend-config,/transport/crypto/public-key,/common/download,/common/download/resource' ) class GenSettings: """ 代码生成配置 """ author = 'insistence' package_name = 'module_admin.system' auto_remove_pre = False table_prefix = 'sys_' allow_overwrite = False GEN_PATH = 'vf_admin/gen_path' def __init__(self) -> None: if not os.path.exists(self.GEN_PATH): os.makedirs(self.GEN_PATH) class UploadSettings: """ 上传配置 """ UPLOAD_PREFIX = '/profile' UPLOAD_PATH = 'vf_admin/upload_path' UPLOAD_MACHINE = 'A' DEFAULT_ALLOWED_EXTENSION = [ # 图片 'bmp', 'gif', 'jpg', 'jpeg', 'png', # word excel powerpoint 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'html', 'htm', 'txt', # 压缩文件 'rar', 'zip', 'gz', 'bz2', # 视频格式 'mp4', 'avi', 'rmvb', # pdf 'pdf', ] DOWNLOAD_PATH = 'vf_admin/download_path' def __init__(self) -> None: if not os.path.exists(self.UPLOAD_PATH): os.makedirs(self.UPLOAD_PATH) if not os.path.exists(self.DOWNLOAD_PATH): os.makedirs(self.DOWNLOAD_PATH) class CachePathConfig: """ 缓存目录配置 """ PATH = os.path.join(os.path.abspath(os.getcwd()), 'caches') PATHSTR = 'caches' class GetConfig: """ 获取配置 """ def __init__(self) -> None: self.parse_cli_args() def get_app_config(self) -> AppSettings: """ 获取应用配置 """ # 实例化应用配置模型 return AppSettings() def get_jwt_config(self) -> JwtSettings: """ 获取Jwt配置 """ # 实例化Jwt配置模型 return JwtSettings() def get_database_config(self) -> DataBaseSettings: """ 获取数据库配置 """ # 实例化数据库配置模型 return DataBaseSettings() def get_redis_config(self) -> RedisSettings: """ 获取Redis配置 """ # 实例化Redis配置模型 return RedisSettings() def get_log_config(self) -> LogSettings: """ 获取日志配置 """ return LogSettings() def get_transport_crypto_config(self) -> TransportCryptoSettings: """ 获取传输层加解密配置 """ return TransportCryptoSettings() def get_gen_config(self) -> GenSettings: """ 获取代码生成配置 """ # 实例化代码生成配置 return GenSettings() def get_upload_config(self) -> UploadSettings: """ 获取上传配置 """ # 实例上传配置 return UploadSettings() @staticmethod def parse_cli_args() -> None: """ 解析命令行参数 """ # 检查是否在alembic环境中运行,如果是则跳过参数解析 if 'alembic' in sys.argv[0] or any('alembic' in arg for arg in sys.argv): ini_config = configparser.ConfigParser() ini_config.read('alembic.ini', encoding='utf-8') if 'settings' in ini_config: # 获取env选项 env_value = ini_config['settings'].get('env') os.environ['APP_ENV'] = env_value if env_value else 'dev' elif 'uvicorn' in sys.argv[0]: # 使用uvicorn启动时,命令行参数需要按照uvicorn的文档进行配置,无法自定义参数 pass else: # 使用argparse定义命令行参数 parser = argparse.ArgumentParser(description='命令行参数') parser.add_argument('--env', type=str, default='', help='运行环境') # 解析命令行参数 args, _ = parser.parse_known_args() # 设置环境变量,如果未设置命令行参数,默认APP_ENV为dev os.environ['APP_ENV'] = args.env if args.env else 'dev' # 读取运行环境 run_env = os.environ.get('APP_ENV', '') # 运行环境未指定时默认加载.env.dev env_file = '.env.dev' # 运行环境不为空时按命令行参数加载对应.env文件 if run_env != '': env_file = f'.env.{run_env}' # 加载配置 load_dotenv(env_file) # 实例化获取配置类 get_config = GetConfig() # 应用配置 AppConfig = get_config.get_app_config() # Jwt配置 JwtConfig = get_config.get_jwt_config() # 数据库配置 DataBaseConfig = get_config.get_database_config() # Redis配置 RedisConfig = get_config.get_redis_config() # 日志配置 LogConfig = get_config.get_log_config() # 传输层加解密配置 TransportCryptoConfig = get_config.get_transport_crypto_config() # 代码生成配置 GenConfig = get_config.get_gen_config() # 上传配置 UploadConfig = get_config.get_upload_config()