- 在 applications/config.py 中通过 PLUGIN_ENABLE_FOLDERS 启用 4 个业务插件
- 删除 framework 内 applications/view/system/nav.py 与 templates/system/nav/* 模板
- applications/extensions/init_plugs 移除重复的 broadcast_execute('event_init'/'event_finish')
(由 init_bps / init_script 触发,避免 Blueprint 注册冲突)
- applications/view/system/index.py:未登录访问 / 直接渲染前台公开聚合页
- templates/system/index.html:移动端首屏即折叠侧边栏
- 新增 5 个 model(Nav 字段 status→enable 兼容、NavCategory、About、Friend、VisitLog/PageStat、NavClick)
- 新增 2 个 schema(NavCategorySchema、FriendOutSchema/ManageSchema)
- admin_about/admin_friend/admin_nav_category 在 init.json 中以「网站管理」分组挂载
- .gitignore 补充一次性备份、调试截图、探针截图不入库
132 lines
4.6 KiB
Python
132 lines
4.6 KiB
Python
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)
|
||
|
||
# ---- 插件 ----
|
||
# 站点业务模块(导航 / 友链 / 关于 / 访问统计)均通过 plugins 方式开发,启用即可挂载路由
|
||
PLUGIN_ENABLE_FOLDERS = ["navManager", "friendManager", "aboutManager", "siteStats"]
|
||
|
||
|
||
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)
|