__init__.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. from flask import Flask, jsonify
  2. from flask_sqlalchemy import SQLAlchemy
  3. from flask_cors import CORS
  4. import logging
  5. from app.config.config import config, current_env
  6. from app.config.cors import CORS_OPTIONS
  7. import os
  8. db = SQLAlchemy()
  9. def create_app():
  10. """Create and configure the Flask application"""
  11. app = Flask(__name__)
  12. # 加载配置
  13. app.config.from_object(config[current_env])
  14. # 初始化扩展
  15. # 配置CORS以解决跨域问题
  16. CORS(app, **CORS_OPTIONS)
  17. db.init_app(app)
  18. # 注册蓝图
  19. from app.api.meta_data import bp as meta_bp
  20. from app.api.data_resource import bp as resource_bp
  21. from app.api.data_model import bp as data_model_bp
  22. from app.api.data_interface import bp as data_interface_bp
  23. from app.api.data_metric import bp as data_metric_bp
  24. from app.api.production_line import bp as production_line_bp
  25. from app.api.graph import bp as graph_bp
  26. from app.api.system import bp as system_bp
  27. from app.api.data_source import bp as data_source_bp
  28. from app.api.data_parse import bp as data_parse_bp
  29. from app.api.data_flow import bp as data_flow_bp
  30. app.register_blueprint(meta_bp, url_prefix='/api/meta')
  31. app.register_blueprint(resource_bp, url_prefix='/api/resource')
  32. app.register_blueprint(data_model_bp, url_prefix='/api/model')
  33. app.register_blueprint(data_interface_bp, url_prefix='/api/interface')
  34. app.register_blueprint(data_metric_bp, url_prefix='/api/metric')
  35. app.register_blueprint(production_line_bp, url_prefix='/api/pipeline')
  36. app.register_blueprint(graph_bp, url_prefix='/api/graph')
  37. app.register_blueprint(system_bp, url_prefix='/api/system')
  38. app.register_blueprint(data_source_bp, url_prefix='/api/datasource')
  39. app.register_blueprint(data_parse_bp, url_prefix='/api/parse')
  40. app.register_blueprint(data_flow_bp, url_prefix='/api/dataflow')
  41. # Configure global response headers
  42. configure_response_headers(app)
  43. # Configure logging
  44. configure_logging(app)
  45. # 添加全局异常处理器
  46. configure_error_handlers(app)
  47. # 输出启动信息
  48. app.logger.info(f"Starting server in {current_env} mode on port {app.config['PORT']}")
  49. return app
  50. def configure_response_headers(app):
  51. """Configure global response headers for JSON content"""
  52. @app.after_request
  53. def after_request(response):
  54. from flask import request
  55. # 检查是否是API路径
  56. if request.path.startswith('/api/'):
  57. # 排除文件下载和特殊响应类型
  58. if (response.content_type and
  59. any(ct in response.content_type for ct in [
  60. 'application/octet-stream',
  61. 'application/pdf',
  62. 'image/',
  63. 'text/csv',
  64. 'application/vnd.ms-excel',
  65. 'application/vnd.openxmlformats-officedocument'
  66. ])):
  67. # 保持原有的文件类型不变
  68. pass
  69. elif response.content_type and 'application/json' in response.content_type:
  70. # 确保JSON响应设置正确的Content-Type和charset
  71. response.headers['Content-Type'] = 'application/json; charset=utf-8'
  72. elif (not response.content_type or
  73. response.content_type == 'text/html; charset=utf-8' or
  74. response.content_type == 'text/plain'):
  75. # 对于API路由,如果没有明确设置Content-Type或设置为HTML,默认设置为JSON
  76. response.headers['Content-Type'] = 'application/json; charset=utf-8'
  77. # 确保CORS头部不被覆盖
  78. if 'Access-Control-Allow-Origin' not in response.headers:
  79. # 动态设置Origin,支持任意前端地址
  80. origin = request.headers.get('Origin')
  81. if origin:
  82. # 允许任意Origin(最灵活的配置)
  83. response.headers['Access-Control-Allow-Origin'] = origin
  84. else:
  85. # 如果没有Origin头部,设置为通配符
  86. response.headers['Access-Control-Allow-Origin'] = '*'
  87. # 专门处理预检请求(OPTIONS方法)
  88. if request.method == 'OPTIONS':
  89. response.headers['Access-Control-Allow-Origin'] = request.headers.get('Origin', '*')
  90. response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
  91. response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, X-Requested-With, Accept, Origin, Cache-Control, X-File-Name'
  92. response.headers['Access-Control-Max-Age'] = '86400'
  93. return response
  94. # 根据配置设置凭据支持
  95. from app.config.cors import ALLOW_ALL_ORIGINS
  96. if 'Access-Control-Allow-Credentials' not in response.headers:
  97. if ALLOW_ALL_ORIGINS:
  98. response.headers['Access-Control-Allow-Credentials'] = 'false' # 通配符时不支持凭据
  99. else:
  100. response.headers['Access-Control-Allow-Credentials'] = 'true'
  101. if 'Access-Control-Allow-Methods' not in response.headers:
  102. response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
  103. if 'Access-Control-Allow-Headers' not in response.headers:
  104. response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, X-Requested-With, Accept, Origin'
  105. # 添加安全头部
  106. if 'X-Content-Type-Options' not in response.headers:
  107. response.headers['X-Content-Type-Options'] = 'nosniff'
  108. if 'X-Frame-Options' not in response.headers:
  109. response.headers['X-Frame-Options'] = 'DENY'
  110. if 'X-XSS-Protection' not in response.headers:
  111. response.headers['X-XSS-Protection'] = '1; mode=block'
  112. return response
  113. def configure_logging(app):
  114. """Configure logging for the application"""
  115. if not app.config.get('LOG_ENABLED', True):
  116. return None
  117. log_file = app.config.get('LOG_FILE', f'flask_{app.config["FLASK_ENV"]}.log')
  118. log_level_name = app.config.get('LOG_LEVEL', 'INFO')
  119. log_level = getattr(logging, log_level_name)
  120. log_format = app.config.get('LOG_FORMAT', '%(asctime)s - %(levelname)s - %(filename)s - %(funcName)s - %(lineno)s - %(message)s')
  121. log_encoding = app.config.get('LOG_ENCODING', 'UTF-8')
  122. log_to_console = app.config.get('LOG_TO_CONSOLE', True)
  123. # 配置根日志器
  124. root_logger = logging.getLogger()
  125. root_logger.setLevel(log_level)
  126. # 清除所有现有处理器
  127. root_logger.handlers.clear()
  128. # 文件处理器 - 只添加到根日志器
  129. file_handler = logging.FileHandler(log_file, encoding=log_encoding)
  130. file_handler.setLevel(log_level)
  131. logging_format = logging.Formatter(log_format)
  132. file_handler.setFormatter(logging_format)
  133. root_logger.addHandler(file_handler)
  134. # 控制台处理器 - 只添加到根日志器
  135. if log_to_console:
  136. console = logging.StreamHandler()
  137. console.setLevel(log_level)
  138. console.setFormatter(logging_format)
  139. root_logger.addHandler(console)
  140. # 确保Flask内部日志器使用我们的配置
  141. app.logger.handlers.clear() # 移除Flask默认处理器
  142. # 配置app日志器,但禁止传播到根日志器
  143. logger = logging.getLogger("app")
  144. logger.setLevel(log_level)
  145. logger.handlers.clear() # 清除现有处理器
  146. logger.propagate = True # 通过根日志器处理
  147. app.logger.info(f"日志配置完成: 级别={log_level_name}, 文件={log_file}")
  148. return logger
  149. def configure_error_handlers(app):
  150. """Configure global error handlers for the application"""
  151. @app.errorhandler(Exception)
  152. def handle_exception(e):
  153. """全局异常处理器,捕获所有未处理的异常"""
  154. # 记录详细的错误信息
  155. app.logger.error(f"未处理的异常: {str(e)}", exc_info=True)
  156. # 返回标准化的错误响应
  157. error_response = {
  158. 'success': False,
  159. 'message': f'服务器内部错误: {str(e)}',
  160. 'data': None
  161. }
  162. return jsonify(error_response), 500
  163. @app.errorhandler(404)
  164. def handle_not_found(e):
  165. """处理404错误"""
  166. app.logger.warning(f"404错误: {str(e)}")
  167. return jsonify({
  168. 'success': False,
  169. 'message': '请求的资源不存在',
  170. 'data': None
  171. }), 404
  172. @app.errorhandler(500)
  173. def handle_internal_error(e):
  174. """处理500错误"""
  175. app.logger.error(f"500错误: {str(e)}", exc_info=True)
  176. return jsonify({
  177. 'success': False,
  178. 'message': '服务器内部错误',
  179. 'data': None
  180. }), 500