| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- import logging
- from flask import Flask, jsonify
- from flask_cors import CORS
- from flask_sqlalchemy import SQLAlchemy
- from app.config.config import config, current_env
- from app.config.cors import CORS_OPTIONS
- db = SQLAlchemy()
- def create_app():
- """Create and configure the Flask application"""
- app = Flask(__name__)
- # 加载配置
- app.config.from_object(config[current_env])
- # 初始化扩展
- # 配置CORS以解决跨域问题
- CORS(app, **CORS_OPTIONS)
- db.init_app(app)
- # 注册蓝图
- from app.api.business_domain import bp as business_domain_bp
- from app.api.data_factory import bp as data_factory_bp
- from app.api.data_flow import bp as data_flow_bp
- from app.api.data_interface import bp as data_interface_bp
- from app.api.data_service import bp as data_service_bp
- from app.api.data_source import bp as data_source_bp
- from app.api.graph import bp as graph_bp
- from app.api.meta_data import bp as meta_bp
- from app.api.system import bp as system_bp
- app.register_blueprint(meta_bp, url_prefix="/api/meta")
- app.register_blueprint(data_interface_bp, url_prefix="/api/interface")
- app.register_blueprint(graph_bp, url_prefix="/api/graph")
- app.register_blueprint(system_bp, url_prefix="/api/system")
- app.register_blueprint(data_source_bp, url_prefix="/api/datasource")
- app.register_blueprint(data_flow_bp, url_prefix="/api/dataflow")
- app.register_blueprint(business_domain_bp, url_prefix="/api/bd")
- app.register_blueprint(data_factory_bp, url_prefix="/api/datafactory")
- app.register_blueprint(data_service_bp, url_prefix="/api/dataservice")
- # Configure global response headers
- configure_response_headers(app)
- # Configure logging
- configure_logging(app)
- # 添加全局异常处理器
- configure_error_handlers(app)
- # 输出启动信息
- port = app.config["PORT"]
- app.logger.info(f"Starting server in {current_env} mode on port {port}")
- return app
- def configure_response_headers(app):
- """Configure global response headers for JSON content"""
- @app.after_request
- def after_request(response):
- from flask import request
- # 检查是否是API路径
- if request.path.startswith("/api/"):
- # 排除文件下载和特殊响应类型
- excluded_types = [
- "application/octet-stream",
- "application/pdf",
- "image/",
- "text/csv",
- "application/vnd.ms-excel",
- "application/vnd.openxmlformats-officedocument",
- ]
- if response.content_type and any(
- ct in response.content_type for ct in excluded_types
- ):
- # 保持原有的文件类型不变
- pass
- elif response.content_type and "application/json" in response.content_type:
- # 确保JSON响应设置正确的Content-Type和charset
- ct = "application/json; charset=utf-8"
- response.headers["Content-Type"] = ct
- elif (
- not response.content_type
- or response.content_type == "text/html; charset=utf-8"
- or response.content_type == "text/plain"
- ):
- # 对于API路由,默认设置为JSON
- ct = "application/json; charset=utf-8"
- response.headers["Content-Type"] = ct
- # 确保CORS头部不被覆盖
- if "Access-Control-Allow-Origin" not in response.headers:
- # 动态设置Origin,支持任意前端地址
- origin = request.headers.get("Origin")
- if origin:
- # 允许任意Origin(最灵活的配置)
- response.headers["Access-Control-Allow-Origin"] = origin
- else:
- # 如果没有Origin头部,设置为通配符
- response.headers["Access-Control-Allow-Origin"] = "*"
- # 专门处理预检请求(OPTIONS方法)
- if request.method == "OPTIONS":
- origin = request.headers.get("Origin", "*")
- response.headers["Access-Control-Allow-Origin"] = origin
- methods = "GET, POST, PUT, DELETE, OPTIONS"
- response.headers["Access-Control-Allow-Methods"] = methods
- headers = (
- "Content-Type, Authorization, X-Requested-With, "
- "Accept, Origin, Cache-Control, X-File-Name"
- )
- response.headers["Access-Control-Allow-Headers"] = headers
- response.headers["Access-Control-Max-Age"] = "86400"
- return response
- # 根据配置设置凭据支持
- from app.config.cors import ALLOW_ALL_ORIGINS
- if "Access-Control-Allow-Credentials" not in response.headers:
- if ALLOW_ALL_ORIGINS:
- # 通配符时不支持凭据
- response.headers["Access-Control-Allow-Credentials"] = "false"
- else:
- response.headers["Access-Control-Allow-Credentials"] = "true"
- if "Access-Control-Allow-Methods" not in response.headers:
- methods = "GET, POST, PUT, DELETE, OPTIONS"
- response.headers["Access-Control-Allow-Methods"] = methods
- if "Access-Control-Allow-Headers" not in response.headers:
- headers = (
- "Content-Type, Authorization, X-Requested-With, Accept, Origin"
- )
- response.headers["Access-Control-Allow-Headers"] = headers
- # 添加安全头部
- if "X-Content-Type-Options" not in response.headers:
- response.headers["X-Content-Type-Options"] = "nosniff"
- if "X-Frame-Options" not in response.headers:
- response.headers["X-Frame-Options"] = "DENY"
- if "X-XSS-Protection" not in response.headers:
- response.headers["X-XSS-Protection"] = "1; mode=block"
- return response
- def configure_logging(app):
- """Configure logging for the application"""
- if not app.config.get("LOG_ENABLED", True):
- return None
- log_file = app.config.get("LOG_FILE", f"flask_{app.config['FLASK_ENV']}.log")
- log_level_name = app.config.get("LOG_LEVEL", "INFO")
- log_level = getattr(logging, log_level_name)
- log_format = app.config.get(
- "LOG_FORMAT",
- "%(asctime)s - %(levelname)s - %(filename)s - "
- "%(funcName)s - %(lineno)s - %(message)s",
- )
- log_encoding = app.config.get("LOG_ENCODING", "UTF-8")
- log_to_console = app.config.get("LOG_TO_CONSOLE", True)
- # 配置根日志器
- root_logger = logging.getLogger()
- root_logger.setLevel(log_level)
- # 清除所有现有处理器
- root_logger.handlers.clear()
- # 文件处理器 - 只添加到根日志器
- file_handler = logging.FileHandler(log_file, encoding=log_encoding)
- file_handler.setLevel(log_level)
- logging_format = logging.Formatter(log_format)
- file_handler.setFormatter(logging_format)
- root_logger.addHandler(file_handler)
- # 控制台处理器 - 只添加到根日志器
- if log_to_console:
- console = logging.StreamHandler()
- console.setLevel(log_level)
- console.setFormatter(logging_format)
- root_logger.addHandler(console)
- # 确保Flask内部日志器使用我们的配置
- app.logger.handlers.clear() # 移除Flask默认处理器
- # 配置app日志器,但禁止传播到根日志器
- logger = logging.getLogger("app")
- logger.setLevel(log_level)
- logger.handlers.clear() # 清除现有处理器
- logger.propagate = True # 通过根日志器处理
- app.logger.info(f"日志配置完成: 级别={log_level_name}, 文件={log_file}")
- return logger
- def configure_error_handlers(app):
- """Configure global error handlers for the application"""
- @app.errorhandler(Exception)
- def handle_exception(e):
- """全局异常处理器,捕获所有未处理的异常"""
- # 记录详细的错误信息
- app.logger.error(f"未处理的异常: {str(e)}", exc_info=True)
- # 返回标准化的错误响应
- error_response = {
- "success": False,
- "message": f"服务器内部错误: {str(e)}",
- "data": None,
- }
- return jsonify(error_response), 500
- @app.errorhandler(404)
- def handle_not_found(e):
- """处理404错误"""
- app.logger.warning(f"404错误: {str(e)}")
- return jsonify(
- {"success": False, "message": "请求的资源不存在", "data": None}
- ), 404
- @app.errorhandler(500)
- def handle_internal_error(e):
- """处理500错误"""
- app.logger.error(f"500错误: {str(e)}", exc_info=True)
- return jsonify(
- {"success": False, "message": "服务器内部错误", "data": None}
- ), 500
|