refactor: split flask app structure
This commit is contained in:
+116
@@ -0,0 +1,116 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from flask import Flask, abort, jsonify, request
|
||||
|
||||
from .config import Config, DATA_DIR, ensure_dirs
|
||||
from .extensions import db, login_manager
|
||||
from .models import User
|
||||
from .prediction import FEATURES, load_model
|
||||
from .security import csrf_token, validate_csrf_token
|
||||
|
||||
|
||||
def create_app(config_object: type[Config] = Config, *, load_model_on_start: bool = True) -> Flask:
|
||||
ensure_dirs()
|
||||
configure_logging()
|
||||
|
||||
app = Flask(__name__, template_folder="../templates", static_folder="../static")
|
||||
app.config.from_object(config_object)
|
||||
|
||||
if app.config.get("SECRET_KEY_GENERATED"):
|
||||
logging.warning("未设置 SECRET_KEY,已生成临时密钥;服务重启后登录会话将失效。")
|
||||
|
||||
db.init_app(app)
|
||||
login_manager.init_app(app)
|
||||
login_manager.login_view = "main.login"
|
||||
login_manager.login_message = "请先登录后再访问该页面。"
|
||||
|
||||
register_app_hooks(app)
|
||||
|
||||
from .routes import bp
|
||||
|
||||
app.register_blueprint(bp)
|
||||
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
init_admin_user(app)
|
||||
|
||||
if load_model_on_start:
|
||||
try:
|
||||
app.config["RSF_MODEL"] = load_model(app.config["MODEL_PATH"])
|
||||
logging.info("模型加载成功")
|
||||
except Exception as exc:
|
||||
app.config["RSF_MODEL"] = None
|
||||
logging.exception("模型加载失败: %s", exc)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def configure_logging() -> None:
|
||||
logging.basicConfig(
|
||||
filename=str(DATA_DIR / "app.log"),
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(levelname)s - %(message)s",
|
||||
)
|
||||
|
||||
|
||||
def init_admin_user(app: Flask) -> None:
|
||||
admin_username = app.config["ADMIN_USERNAME"]
|
||||
admin_password = app.config["ADMIN_PASSWORD"]
|
||||
if admin_password:
|
||||
admin = User.query.filter_by(username=admin_username).first()
|
||||
if admin is None:
|
||||
admin = User(username=admin_username, is_admin=True)
|
||||
admin.set_password(admin_password)
|
||||
db.session.add(admin)
|
||||
else:
|
||||
admin.is_admin = True
|
||||
if not admin.check_password(admin_password):
|
||||
admin.set_password(admin_password)
|
||||
db.session.commit()
|
||||
elif not User.query.filter_by(is_admin=True).first():
|
||||
logging.warning("未设置 ADMIN_PASSWORD,跳过自动创建管理员账号。")
|
||||
|
||||
default_admin = User.query.filter_by(username="admin", is_admin=True).first()
|
||||
if default_admin and default_admin.check_password("admin123"):
|
||||
logging.warning("检测到默认管理员密码 admin123,请立即通过 ADMIN_PASSWORD 更新。")
|
||||
|
||||
|
||||
def register_app_hooks(app: Flask) -> None:
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id: str):
|
||||
try:
|
||||
return db.session.get(User, int(user_id))
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
@app.context_processor
|
||||
def inject_helpers():
|
||||
def now_year() -> int:
|
||||
return datetime.now().year
|
||||
|
||||
return {
|
||||
"feature_list": FEATURES,
|
||||
"now_year": now_year,
|
||||
"csrf_token": csrf_token,
|
||||
"allow_registration": app.config["ALLOW_REGISTRATION"],
|
||||
}
|
||||
|
||||
@app.errorhandler(413)
|
||||
def handle_file_too_large(_exc):
|
||||
max_mb = app.config["MAX_CONTENT_LENGTH"] // (1024 * 1024)
|
||||
if request.path == "/predict":
|
||||
return jsonify({"error": f"文件过大,请上传 {max_mb}MB 以内的文件。"}), 413
|
||||
return f"文件过大,请上传 {max_mb}MB 以内的文件。", 413
|
||||
|
||||
@app.before_request
|
||||
def protect_csrf():
|
||||
if request.method not in {"POST", "PUT", "PATCH", "DELETE"}:
|
||||
return None
|
||||
if validate_csrf_token():
|
||||
return None
|
||||
if request.path == "/predict":
|
||||
return jsonify({"error": "CSRF 校验失败,请刷新页面后重试。"}), 400
|
||||
abort(400)
|
||||
Reference in New Issue
Block a user