import logging import os import secrets from datetime import timedelta def _env_bool(key: str, default: bool = False) -> bool: """读取布尔环境变量,兼容 true/1/yes/on(大小写不敏感)。""" val = os.environ.get(key) if val is None: return default return val.strip().lower() in {"1", "true", "yes", "on"} def _env_str(key: str, default: str = "") -> str: val = os.environ.get(key) return val if val is not None and val != "" else default class BaseConfig: """通用配置:所有环境共有的项。子类按需覆盖。""" # ---- 应用基础 ---- SUPERADMIN = 'admin' SYSTEM_NAME = 'Pear Admin' SYSTEM_PANEL_LINKS = [ {"icon": "layui-icon layui-icon-auz", "title": "官方网站", "href": "http://www.pearadmin.com"}, {"icon": "layui-icon layui-icon-auz", "title": "开发文档", "href": "http://www.pearadmin.com"}, {"icon": "layui-icon layui-icon-auz", "title": "开源地址", "href": "https://gitee.com/Jmysy/Pear-Admin-Layui"}, ] # ---- 上传 ---- UPLOADED_PHOTOS_DEST = 'static/upload' UPLOADED_FILES_ALLOW = ['gif', 'jpg', 'jpeg', 'png', 'webp'] UPLOADS_AUTOSERVE = True # ---- JSON ---- JSON_AS_ASCII = False # ---- 数据库 ---- # 默认走项目根目录下的 pear.db(与 instance/ 同一级,Flask 2.x 行为)。 # 生产环境推荐通过 SQLALCHEMY_DATABASE_URI 环境变量切换到 MySQL。 SQLALCHEMY_DATABASE_URI = 'sqlite:///../pear.db' SQLALCHEMY_TRACK_MODIFICATIONS = False # ---- 日志 ---- LOG_LEVEL = logging.WARN # ---- Session ---- PERMANENT_SESSION_LIFETIME = timedelta(days=7) SESSION_TYPE = "filesystem" SESSION_PERMANENT = False SESSION_USE_SIGNER = True # ---- 安全 / 密钥 ---- # SECRET_KEY 的优先级: # 1. 环境变量 SECRET_KEY # 2. .env 中的 SECRET_KEY # 3. 自动生成一个临时随机值(仅用于开发启动;生产模式强制要求外部传入) SECRET_KEY = os.environ.get("SECRET_KEY") or "pear-system-flask" # ---- Mail(默认占位;环境变量可覆盖)---- MAIL_SERVER = _env_str("MAIL_SERVER", "smtp.qq.com") MAIL_USE_TLS = _env_bool("MAIL_USE_TLS", False) MAIL_USE_SSL = _env_bool("MAIL_USE_SSL", True) MAIL_PORT = int(_env_str("MAIL_PORT", "465")) MAIL_USERNAME = _env_str("MAIL_USERNAME", "123@qq.com") MAIL_PASSWORD = _env_str("MAIL_PASSWORD", "XXXXX") # QQ 邮箱授权码 MAIL_DEFAULT_SENDER = _env_str("MAIL_DEFAULT_SENDER", MAIL_USERNAME) # ---- 插件 ---- PLUGIN_ENABLE_FOLDERS = [] class DevConfig(BaseConfig): """开发环境配置:DEBUG=True,详细日志。""" DEBUG = True TESTING = False LOG_LEVEL = logging.INFO # 开发环境下 SECRET_KEY 自动生成(每次启动都不同,仅用于本地调试)。 SECRET_KEY = os.environ.get("SECRET_KEY") or secrets.token_urlsafe(48) class ProConfig(BaseConfig): """生产环境配置:DEBUG=False,强制要求外部传入 SECRET_KEY。""" DEBUG = False TESTING = False LOG_LEVEL = logging.WARN # 生产推荐使用 MySQL,例如: # SQLALCHEMY_DATABASE_URI=mysql+pymysql://user:pass@host:3306/pear?charset=utf8mb4 SQLALCHEMY_DATABASE_URI = os.environ.get( "SQLALCHEMY_DATABASE_URI", BaseConfig.SQLALCHEMY_DATABASE_URI, ) @classmethod def init_app(cls, app): """Flask 在 app.config.from_object 之后调用此钩子。 生产模式要求:必须从环境变量传入强随机 SECRET_KEY, 否则启动失败 —— 防止线上仍用默认密钥。 """ secret = os.environ.get("SECRET_KEY") if not secret or secret == "pear-system-flask": raise RuntimeError( "[ProConfig] 生产环境必须通过环境变量 SECRET_KEY 传入强随机密钥," "禁止使用默认值。可用以下命令生成:\n" " python -c \"import secrets; print(secrets.token_urlsafe(48))\"" ) app.config["SECRET_KEY"] = secret # 暴露给外部按名字取 config_map = { "dev": DevConfig, "development": DevConfig, "pro": ProConfig, "production": ProConfig, } def get_config_by_name(name: str): """根据名字取配置类,未知名字退回 DevConfig。""" if not name: return DevConfig return config_map.get(name.strip().lower(), DevConfig)