import os from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager from flask_migrate import Migrate from config import config from flask_wtf.csrf import CSRFProtect from flask_cors import CORS import logging from logging.handlers import RotatingFileHandler # 初始化扩展 db = SQLAlchemy() login_manager = LoginManager() migrate = Migrate() csrf = CSRFProtect() cors = CORS() def create_app(config_name='default'): """应用工厂函数""" # 设置模板和静态文件夹 app_dir = os.path.dirname(os.path.abspath(__file__)) template_folder = os.path.join(app_dir, 'web', 'templates') static_folder = os.path.join(os.path.dirname(app_dir), 'static') app = Flask(__name__, template_folder=template_folder, static_folder=static_folder) # 尝试加载.env文件(在配置之前加载,确保环境变量生效) try: from dotenv import load_dotenv if load_dotenv(): print("成功加载.env文件") else: print("未找到或无法加载.env文件") except ImportError: print("python-dotenv未安装,跳过.env文件加载") # 先应用配置对象,再初始化配置 app.config.from_object(config[config_name]) config[config_name].init_app(app) # 特别确保MAX_CONTENT_LENGTH配置正确应用 max_content_length = app.config.get('MAX_CONTENT_LENGTH', 16 * 1024 * 1024) app.config['MAX_CONTENT_LENGTH'] = max_content_length print(f"Setting MAX_CONTENT_LENGTH to: {max_content_length} bytes ({max_content_length / (1024*1024)} MB)") # 动态设置前端域名配置(支持多种环境变量) frontend_domain = os.environ.get('FRONTEND_DOMAIN') or os.environ.get('DOMAIN_NAME') or os.environ.get('SERVER_NAME') or '' # 确保域名不包含协议部分 if frontend_domain.startswith(('http://', 'https://')): frontend_domain = frontend_domain.split('://', 1)[1] app.config['FRONTEND_DOMAIN'] = frontend_domain print(f"Frontend domain set to: {frontend_domain}") # 初始化扩展 db.init_app(app) login_manager.init_app(app) migrate.init_app(app, db) csrf.init_app(app) # 配置CORS以支持域名访问 - 包括登录页面 cors.init_app(app, resources={ r"/api/*": { "origins": ["https://km.taisan.online", "http://km.taisan.online", "http://localhost:5088", "http://127.0.0.1:5088"], "methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"], "allow_headers": ["Content-Type", "Authorization"] }, r"/login": { "origins": ["https://km.taisan.online", "http://km.taisan.online", "http://localhost:5088", "http://127.0.0.1:5088"], "methods": ["GET", "POST", "OPTIONS"], "allow_headers": ["Content-Type", "X-Requested-With"], "supports_credentials": True } }) # 配置登录管理器 login_manager.login_view = 'web.login' # type: ignore login_manager.login_message = '请先登录' login_manager.login_message_category = 'info' login_manager.id_attribute = 'get_id' # 使用 get_id 方法获取用户ID login_manager.session_protection = 'strong' # 启用强会话保护 # 注册蓝图 from app.api import api_bp app.register_blueprint(api_bp, url_prefix=f'/api/{app.config["API_VERSION"]}') csrf.exempt(api_bp) from app.web import web_bp, user_bp app.register_blueprint(web_bp) app.register_blueprint(user_bp) # 注册错误处理器 from app.web.views import register_error_handlers register_error_handlers(app) # 配置日志 if not app.debug and not app.testing: # 确保日志目录存在 if not os.path.exists('logs'): os.mkdir('logs') # 配置文件日志处理器 file_handler = RotatingFileHandler('logs/kamaxitong.log', maxBytes=10240, backupCount=10) file_handler.setFormatter(logging.Formatter( '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]' )) file_handler.setLevel(logging.INFO) app.logger.addHandler(file_handler) app.logger.setLevel(logging.INFO) app.logger.info('KaMiXiTong startup') return app